Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Wrong model in the non-regression tests #778

Closed
Halbaroth opened this issue Aug 10, 2023 · 6 comments · Fixed by #1019
Closed

Wrong model in the non-regression tests #778

Halbaroth opened this issue Aug 10, 2023 · 6 comments · Fixed by #1019
Assignees
Labels
bug models This issue is related to model generation.
Milestone

Comments

@Halbaroth
Copy link
Collaborator

Alt-Ergo outputs a wrong model on this SMTLIB input:

(set-logic QF_UF)
(set-option :produce-models true) ; enable model generation
(declare-const p Bool)
(declare-const q Bool)
(assert (=> (not p) (not q)))
(check-sat)
(get-model)

The output:

unknown
(
)

and the command : dune exec -- alt-ergo --frontend dolmen --produce-models

@Halbaroth Halbaroth added the bug label Aug 10, 2023
@Halbaroth Halbaroth added this to the 2.5.0 milestone Aug 10, 2023
@Halbaroth Halbaroth added the models This issue is related to model generation. label Aug 10, 2023
@Halbaroth Halbaroth self-assigned this Aug 10, 2023
@bclement-ocp
Copy link
Collaborator

This is a dupe of #777, no? We probably should have a single issue for model unsoundness (although in this case the model is not unsound, it is incomplete).

Should we postpone these to 2.6.0? Although these are unfortunate, we don't claim anything on the soundness (or absence thereof) of models in 2.5.0, and the release is already way past due.

@Halbaroth
Copy link
Collaborator Author

I don't think so. I guess AE failed to produce a complete model because functions returning boolean values are converted into predicates.

@bclement-ocp
Copy link
Collaborator

I am not saying the cause is the same in both cases, but that I think it would make more sense to consolidate all the incorrect models into a single issue rather than having one issue for each possible cause. Since the outcomes are related, it would be easier to have a single place to go to for all the info/insight/etc. we have on this rather than have it be scattered.

@Halbaroth
Copy link
Collaborator Author

Halbaroth commented Aug 24, 2023

After investigating more, I think I found the origin of the bug. The problem comes from case-splits. Consider this very simple problem:

(set-logic QF_UF)
(set-option :produce-models true) ; enable model generation
(declare-const p Bool)
(declare-const q Bool)
(assert (or p q))
(check-sat)
(get-model)

Alt-Ergo produces the partial model { p = true } but we expect something as { p = true; q = whatever }. While reasoning on p, AE found that true satisfies this formula and it didn't explore at all the other branch, which means it didn't add the q in the UF and AE ignores that the term q exists during the model generation phase.

If we permute p an q in the above problem, we got the partial model { q = true } and replacing the or gate by an and gate produces a complete model.

@Halbaroth
Copy link
Collaborator Author

Halbaroth commented Aug 28, 2023

I wrote a fix for this issue that is not completely satisfactory. The idea is really simple:

  • The frontend has to remember which terms have been declared by the user. Notice that a term cannot be both declared and defined in the SMT standard or the native language. I create a separate hash table to collect declared terms in d_cnf.
  • I converted this table into a list and feeds output_concrete_model with it.
  • I iterate on this list in Models.output_concrete_model after producing all the terms from the union-find.
  • If the declared term is not in the model yet, I produce a dummy value for it and print it.

This solution is not satisfactory for many reasons:

  • First of all, we iterate twice on the list of declared terms.
  • We have to transmit these terms while printing the model.
  • The missing terms are not in the map of the model.

I have a better solution that requires substantially more work.

Besides, saving the declared terms in the frontend fixes another issue caused by the preprocess phase of AE. Consider the input:

(set-logic ALL)
(set-option :produce-models true)
(declare-const p Bool)
(declare-const q Bool)
(assert (and (or p (not p)) q))
(check-sat)
(get-model)

Alt-Ergo will not produce a value for p as the tautology or p (not p) is wiped out by the preprocess phase.

@bclement-ocp
Copy link
Collaborator

That sounds right, we do need to collect the defined stuff in the frontend because we lose that information later.

The hash table shouldn't be necessary, collecting the Term_decl in a list should be enough (although there is probably some shenanigans wrt push/pop commands that must be taken care of -- but that is also true for the hash table).

Instead of iterating on the list in Models.output_concrete_model with custom printing code, why not use it to fill in default values in Uf.compute_concrete_model, instead of using empty initial maps for fprofs / cprofs / carrays? These should then get overwritten by Uf.compute_concrete_model as appropriate.

@Halbaroth Halbaroth modified the milestones: 2.5.0, 2.6.0 Oct 18, 2023
Halbaroth added a commit to Halbaroth/alt-ergo that referenced this issue Jan 2, 2024
This PR fixes the issue OCamlPro#778. The suggested solution by this PR is as follows:
- Collect user-declared symbols in `D_cnf`;
- As the function `make` of the module `D_cnf` returns a list of
  commands, we add a new command `Decl` in `Frontend` to declare
  symbols;
- The declared symbols are given to the SAT solver through a new
  function `declaration` in `sat_solver_sig`.
- These symbols are collected in vectors and we keep indexes in another
  PR in order to manage properly the push/pop mechanism. Notice that
  we have to use vectors too in the environment of FunSAT because the
  current implementation of push/pop commands in this SAT solver doesn't
  use the persistency of its environment to restore previous
  environments while processing `pop` command.
- Finally the declared symbols are given to `Uf` and we create the empty
  model with them.

Another annoying point comes from the current datastructure used in
`ModelMap`. The type of `ModelMap` saves model values in the form of graphs.
A graph is a finite set of constraints but there is no appropriate
representation for the graph without constraints. Let's imagine we have
the input:
```smt2
(set-option :produce-models true)
(set-logic ALL)
(declare-fun (Int) Int)
(check-sat)
(get-model)
```
We expect an output as follows:
```smt2
(
  (define-fun f (_x Int) (@A as Int))
)
```
But `Graph.empty` cannot hold the abstract value `@a`. A naive solution
consists in using an ADT:
```ocaml
type graph =
| Empty of Id.typed
| G of Graph.t
```
But we'll add an extra indirection. Probably the best solution would be
to use a custom datastructure instead of a map to store the constraints.
In this PR, we detect in `ModelMap.add` if the only constraint in the
graph is of the form `something -> abstract value`. In this case we
remove this constraint.

This solution isn't perfect as explained in issue OCamlPro#1018. The biggest
drawback of this solution is the fact we have to declare symbols in the
SAT API, otherwise models returned by `get_model` could be
incomplete.
Halbaroth added a commit to Halbaroth/alt-ergo that referenced this issue Jan 2, 2024
This PR fixes the issue OCamlPro#778. The suggested solution by this PR is as follows:
- Collect user-declared symbols in `D_cnf`;
- As the function `make` of the module `D_cnf` returns a list of
  commands, we add a new command `Decl` in `Frontend` to declare
  symbols;
- The declared symbols are given to the SAT solver through a new
  function `declaration` in `sat_solver_sig`.
- These symbols are collected in vectors and we keep indexes in another
  PR in order to manage properly the push/pop mechanism. Notice that
  we have to use vectors too in the environment of FunSAT because the
  current implementation of push/pop commands in this SAT solver doesn't
  use the persistency of its environment to restore previous
  environments while processing `pop` command.
- Finally the declared symbols are given to `Uf` and we create the empty
  model with them.

Another annoying point comes from the current datastructure used in
`ModelMap`. The type of `ModelMap` saves model values in the form of graphs.
A graph is a finite set of constraints but there is no appropriate
representation for the graph without constraints. Let's imagine we have
the input:
```smt2
(set-option :produce-models true)
(set-logic ALL)
(declare-fun (Int) Int)
(check-sat)
(get-model)
```
We expect an output as follows:
```smt2
(
  (define-fun f (_x Int) (@A as Int))
)
```
But `Graph.empty` cannot hold the abstract value `@a`. A naive solution
consists in using an ADT:
```ocaml
type graph =
| Empty of Id.typed
| G of Graph.t
```
But we'll add an extra indirection. Probably the best solution would be
to use a custom datastructure instead of a map to store the constraints.
In this PR, we detect in `ModelMap.add` if the only constraint in the
graph is of the form `something -> abstract value`. In this case we
remove this constraint.

This solution isn't perfect as explained in issue OCamlPro#1018. The biggest
drawback of this solution is the fact we have to declare symbols in the
SAT API, otherwise models returned by `get_model` could be
incomplete.
Halbaroth added a commit to Halbaroth/alt-ergo that referenced this issue Jan 2, 2024
This PR fixes the issue OCamlPro#778. The suggested solution by this PR is as follows:
- Collect user-declared symbols in `D_cnf`;
- As the function `make` of the module `D_cnf` returns a list of
  commands, we add a new command `Decl` in `Frontend` to declare
  symbols;
- The declared symbols are given to the SAT solver through a new
  function `declaration` in `sat_solver_sig`.
- These symbols are collected in vectors and we keep indexes in another
  PR in order to manage properly the push/pop mechanism.
- Finally the declared symbols are given to `Uf` and we create the empty
  model with them.

Another annoying point comes from the current datastructure used in
`ModelMap`. The type of `ModelMap` saves model values in the form of graphs.
A graph is a finite set of constraints but there is no appropriate
representation for the graph without constraints. Let's imagine we have
the input:
```smt2
(set-option :produce-models true)
(set-logic ALL)
(declare-fun (Int) Int)
(check-sat)
(get-model)
```
We expect an output as follows:
```smt2
(
  (define-fun f (_x Int) (@A as Int))
)
```
But `Graph.empty` cannot hold the abstract value `@a`. A naive solution
consists in using an ADT:
```ocaml
type graph =
| Empty of Id.typed
| G of Graph.t
```
But we'll add an extra indirection. Probably the best solution would be
to use a custom datastructure instead of a map to store the constraints.
In this PR, we detect in `ModelMap.add` if the only constraint in the
graph is of the form `something -> abstract value`. In this case we
remove this constraint.

This solution isn't perfect as explained in issue OCamlPro#1018. The biggest
drawback of this solution is the fact we have to declare symbols in the
SAT API, otherwise models returned by `get_model` could be
incomplete.
@Halbaroth Halbaroth linked a pull request Jan 3, 2024 that will close this issue
Halbaroth added a commit to Halbaroth/alt-ergo that referenced this issue Jan 3, 2024
This PR fixes the issue OCamlPro#778. The suggested solution by this PR is as follows:
- Collect user-declared symbols in `D_cnf`;
- As the function `make` of the module `D_cnf` returns a list of
  commands, we add a new command `Decl` in `Frontend` to declare
  symbols;
- The declared symbols are given to the SAT solver through a new
  function `declaration` in `sat_solver_sig`.
- These symbols are collected in vectors and we keep indexes in another
  PR in order to manage properly the push/pop mechanism.
- Finally the declared symbols are given to `Uf` and we create the empty
  model with them.

Another annoying point comes from the current datastructure used in
`ModelMap`. The type of `ModelMap` saves model values in the form of graphs.
A graph is a finite set of constraints but there is no appropriate
representation for the graph without constraints. Let's imagine we have
the input:
```smt2
(set-option :produce-models true)
(set-logic ALL)
(declare-fun (Int) Int)
(check-sat)
(get-model)
```
We expect an output as follows:
```smt2
(
  (define-fun f (_x Int) (@A as Int))
)
```
But `Graph.empty` cannot hold the abstract value `@a`. A naive solution
consists in using an ADT:
```ocaml
type graph =
| Empty of Id.typed
| G of Graph.t
```
But we'll add an extra indirection. Probably the best solution would be
to use a custom datastructure instead of a map to store the constraints.
In this PR, we detect in `ModelMap.add` if the only constraint in the
graph is of the form `something -> abstract value`. In this case we
remove this constraint.

This solution isn't perfect as explained in issue OCamlPro#1018. The biggest
drawback of this solution is the fact we have to declare symbols in the
SAT API, otherwise models returned by `get_model` could be
incomplete.
Halbaroth added a commit to Halbaroth/alt-ergo that referenced this issue Jan 17, 2024
This PR fixes the issue OCamlPro#778. The suggested solution by this PR is as follows:
- Collect user-declared symbols in `D_cnf`;
- As the function `make` of the module `D_cnf` returns a list of
  commands, we add a new command `Decl` in `Frontend` to declare
  symbols;
- The declared symbols are given to the SAT solver through a new
  function `declaration` in `sat_solver_sig`.
- These symbols are collected in vectors and we keep indexes in another
  PR in order to manage properly the push/pop mechanism.
- Finally the declared symbols are given to `Uf` and we create the empty
  model with them.

Another annoying point comes from the current datastructure used in
`ModelMap`. The type of `ModelMap` saves model values in the form of graphs.
A graph is a finite set of constraints but there is no appropriate
representation for the graph without constraints. Let's imagine we have
the input:
```smt2
(set-option :produce-models true)
(set-logic ALL)
(declare-fun (Int) Int)
(check-sat)
(get-model)
```
We expect an output as follows:
```smt2
(
  (define-fun f (_x Int) (@A as Int))
)
```
But `Graph.empty` cannot hold the abstract value `@a`. A naive solution
consists in using an ADT:
```ocaml
type graph =
| Empty of Id.typed
| G of Graph.t
```
But we'll add an extra indirection. Probably the best solution would be
to use a custom datastructure instead of a map to store the constraints.
In this PR, we detect in `ModelMap.add` if the only constraint in the
graph is of the form `something -> abstract value`. In this case we
remove this constraint.

This solution isn't perfect as explained in issue OCamlPro#1018. The biggest
drawback of this solution is the fact we have to declare symbols in the
SAT API, otherwise models returned by `get_model` could be
incomplete.
Halbaroth added a commit that referenced this issue Jan 18, 2024
* Move typed symbols from ModelMap to Id

* Generate complete model

This PR fixes the issue #778. The suggested solution by this PR is as follows:
- Collect user-declared symbols in `D_cnf`;
- As the function `make` of the module `D_cnf` returns a list of
  commands, we add a new command `Decl` in `Frontend` to declare
  symbols;
- The declared symbols are given to the SAT solver through a new
  function `declaration` in `sat_solver_sig`.
- These symbols are collected in vectors and we keep indexes in another
  PR in order to manage properly the push/pop mechanism.
- Finally the declared symbols are given to `Uf` and we create the empty
  model with them.

Another annoying point comes from the current datastructure used in
`ModelMap`. The type of `ModelMap` saves model values in the form of graphs.
A graph is a finite set of constraints but there is no appropriate
representation for the graph without constraints. Let's imagine we have
the input:
```smt2
(set-option :produce-models true)
(set-logic ALL)
(declare-fun (Int) Int)
(check-sat)
(get-model)
```
We expect an output as follows:
```smt2
(
  (define-fun f (_x Int) (@A as Int))
)
```
But `Graph.empty` cannot hold the abstract value `@a`. A naive solution
consists in using an ADT:
```ocaml
type graph =
| Empty of Id.typed
| G of Graph.t
```
But we'll add an extra indirection. Probably the best solution would be
to use a custom datastructure instead of a map to store the constraints.
In this PR, we detect in `ModelMap.add` if the only constraint in the
graph is of the form `something -> abstract value`. In this case we
remove this constraint.

This solution isn't perfect as explained in issue #1018. The biggest
drawback of this solution is the fact we have to declare symbols in the
SAT API, otherwise models returned by `get_model` could be
incomplete.

* Move comment

* Remove useless option

* Remove useless DeclSet module

* We aren't in C++ :)

* Split the test

* Use a stack of the stdlib for `declare_ids`

* Use a stack of the stdlib for declared symbols in SatML

* Use an ADT to discriminate free graph and constant graph

* Avoid list concatenation

* Add a new error in case of stack underflow

* Don't perform substitution into free graphes
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug models This issue is related to model generation.
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants