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

feat: get-assignment statement #848

Merged
merged 15 commits into from
Nov 9, 2023
142 changes: 141 additions & 1 deletion src/bin/common/solving_loop.ml
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,52 @@ let enable_maxsmt b =
else
DStd.Extensions.Smtlib2.(disable maxsmt)

(* Dolmen util *)

(** Returns the list of terms inside a dolmen binder. *)
let terms_of_dolmen_binder = function
| DStd.Expr.Let_seq l | Let_par l ->
List.map snd l
| Lambda _ | Exists _ | Forall _ -> []

(** Adds the named terms of the term [term] to the map accumulator [acc] *)
let get_named_of_term
(acc : DStd.Expr.term Util.MS.t)
(term : DStd.Expr.term) =
let rec loop acc terms_to_check =
match terms_to_check with
| [] -> acc
| [] :: rest -> loop acc rest
| (term :: tl) :: rest ->
let terms_to_check = tl :: rest in
let terms_to_check =
match term.DStd.Expr.term_descr with
| DStd.Expr.Var _ | Cst _ -> terms_to_check
| App (t, _, tl) -> (t :: tl) :: terms_to_check
| Binder (b, t) ->
(t :: terms_of_dolmen_binder b) :: terms_to_check
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this going to do the right thing wrt capture of bound variables (e.g. what happens if I do (let ((x 2)) (+ (! x :named name))))?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did not think about the semantics of named terms in bindings. Do you think we should not record the named formulas in bindings for now?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we don't have an easy way of checking capture avoidance then yes, that would be better (this is already what we do for quantifiers if I am not mistaken).

| Match (t, plt) -> (t :: (List.map snd plt)) :: terms_to_check
in
match DStd.Expr.Term.get_tag term DStd.Expr.Tags.named with
| None -> loop acc terms_to_check
| Some name -> loop (Util.MS.add name term acc) terms_to_check
in
loop acc [[term]]

(** Adds the named terms of the statement [stmt] to the map accumulator [acc] *)
let get_named_of_stmt
~(acc : DStd.Expr.term Util.MS.t)
(stmt : Typer_Pipe.typechecked D_loop.Typer_Pipe.stmt) =
match stmt.contents with
| `Hyp f | `Goal f -> get_named_of_term acc f
| `Clause l -> List.fold_left get_named_of_term acc l
| `Solve (l1, l2) ->
List.fold_left
get_named_of_term
(List.fold_left get_named_of_term acc l1)
l2
| _ -> acc

(* We currently use the full state of the solver as model. *)
type model = Model : 'a sat_module * 'a -> model

Expand Down Expand Up @@ -297,6 +343,14 @@ let main () =
State.create_key ~pipe:"" "optimize"
in

let get_assignment: bool State.key =
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be called produce_assignments rather than get_assignment, no?

State.create_key ~pipe:"" "get_assignment"
in

let named_terms: DStd.Expr.term Util.MS.t State.key =
State.create_key ~pipe:"" "named_terms"
in

let debug_parsed_pipe st c =
if State.get State.debug st then
Format.eprintf "[logic][parsed][%a] @[<hov>%a@]@."
Expand Down Expand Up @@ -409,6 +463,8 @@ let main () =
|> State.set solver_ctx_key solver_ctx
|> State.set partial_model_key partial_model
|> State.set optimize_key (O.get_optimize ())
|> State.set get_assignment false
|> State.set named_terms Util.MS.empty
|> State.init ~debug ~report_style ~reports ~max_warn ~time_limit
~size_limit ~response_file
|> Parser.init
Expand Down Expand Up @@ -536,10 +592,18 @@ let main () =
solver;
st
)
| ":produce-assignments", Symbol { name = Simple b; _ } ->
begin
match bool_of_string_opt b with
| None ->
print_wrn_opt ~name:":verbosity" st_loc "integer" value;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
print_wrn_opt ~name:":verbosity" st_loc "integer" value;
print_wrn_opt ~name:":verbosity" st_loc "boolean" value;

st
| Some b ->
State.set get_assignment b st
end
| (":global-declarations"
| ":interactive-mode"
| ":produce-assertions"
| ":produce-assignments"
| ":produce-proofs"
| ":produce-unsat-assumptions"
| ":print-success"
Expand Down Expand Up @@ -650,13 +714,58 @@ let main () =
unsupported_opt name
in

(* Fetches the term value in the current model. *)
let evaluate_term get_value name term =
let ae_form =
D_cnf.make_form
name
term
Loc.dummy
~decl_kind:Expr.Dgoal
in
match get_value ae_form with
| None -> "unknown" (* Not in the standard, but useful for recording when
Alt-Ergo fails to guess the value of a term. *)
| Some v -> Fmt.to_to_string Expr.print v
in

let print_terms_assignments =
Fmt.list
~sep:(fun fmt _ -> Fmt.pf fmt "@,")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Missed it earlier but (fun ppf _ -> Fmt.pf ppf "@,") is Fmt.cut.

(fun fmt (name, v) -> Fmt.pf fmt "(%s %s)" name v)
in

let handle_get_assignment ~get_value st =
let assignments =
Util.MS.fold
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: you can probably leverage Fmt.iter_bindings here.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I preferred to build the list and print it afterwards to make the code more readable

(fun name term acc ->
if DStd.Expr.Ty.equal term.DStd.Expr.term_ty DStd.Expr.Ty.bool then
(name, evaluate_term get_value name term) :: acc
else
acc
)
(State.get named_terms st)
[]
in
Printer.print_std
"(@[<v 0>%a@])@,"
print_terms_assignments
assignments
in

let handle_stmt :
Frontend.used_context -> State.t ->
_ D_loop.Typer_Pipe.stmt -> State.t =
let goal_cnt = ref 0 in
fun all_context st td ->
let file_loc = (State.get State.logic_file st).loc in
let solver_ctx = State.get solver_ctx_key st in
let st =
let named_terms_map =
get_named_of_stmt ~acc:(State.get named_terms st) td
in
State.set named_terms named_terms_map st
in
match td with
(* When the next statement is a goal, the solver is called and provided
the goal and the current context *)
Expand Down Expand Up @@ -763,6 +872,8 @@ let main () =
|> State.set partial_model_key None
|> State.set solver_ctx_key empty_solver_ctx
|> State.set optimize_key (O.get_optimize ())
|> State.set get_assignment false
|> State.set named_terms Util.MS.empty

| {contents = `Exit; _} -> raise Exit

Expand All @@ -778,6 +889,35 @@ let main () =
handle_get_info st kind;
st

| {contents = `Get_assignment; _} ->
begin
match State.get partial_model_key st with
| Some Model ((module SAT), partial_model) ->
begin
match SAT.get_model partial_model with
| Some _ ->
if State.get get_assignment st then
handle_get_assignment
~get_value:(SAT.get_value partial_model)
st
else
recoverable_error
"Produce assignments disabled; \
add (set-option :produce-assignments true)";
st
| _ ->
recoverable_error
"Model generation disactivated, cannot execute \
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

disabled, not deactivated. We may want to mention (set-option :produce-model true) here.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Although do we actually need this case? It looks like we don't care about the result of SAT.get_model and I think it should work without?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The command should only be available in SAT mode, is that the good way to check it?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As the name indicates, the partial_model_key should only contain a value if we are in SAT mode, there is no need to also check SAT.get_model (even though AFAIK the call to SAT.get_model cannot fail currently).

get-assignment.";
st
end
| None ->
(* TODO: add the location of the statement. *)
recoverable_error
"No model produced, cannot execute get-assignment.";
st
end

| {contents = `Other (custom, args); _} ->
handle_custom_statement custom args st

Expand Down
7 changes: 7 additions & 0 deletions src/lib/frontend/d_cnf.mli
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,13 @@
val clear_cache : unit -> unit
(** Empties the internal cache of the module. *)

val make_form :
string ->
D_loop.DStd.Expr.term ->
Loc.t ->
decl_kind:Expr.decl_kind ->
Expr.t

val make :
D_loop.DStd.Loc.file ->
Commands.sat_tdecl list ->
Expand Down
10 changes: 10 additions & 0 deletions src/lib/reasoners/fun_sat.ml
Original file line number Diff line number Diff line change
Expand Up @@ -1847,6 +1847,16 @@ module Make (Th : Theory.S) : Sat_solver_sig.S = struct

let get_unknown_reason env = env.unknown_reason

let get_value env t =
match E.type_info t with
| Ty.Tbool ->
begin
match ME.find_opt t env.gamma with
| None -> Some E.faux
| Some _ -> Some E.vrai
end
| _ -> None

let reinit_ctx () =
(* all_models_sat_env := None; *)
(* latest_saved_env := None;
Expand Down
4 changes: 4 additions & 0 deletions src/lib/reasoners/sat_solver_sig.ml
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,10 @@ module type S = sig
(** [get_unknown_reason t] returns the reason Alt-Ergo raised
[I_dont_know] if it did. If it did not, returns None. *)
val get_unknown_reason : t -> unknown_reason option

(** [get_value t e] returns the value of [e] as a constant expression
in the current model generated. Returns [None] if can't decide. *)
val get_value : t -> Expr.t -> Expr.t option
end


Expand Down
4 changes: 4 additions & 0 deletions src/lib/reasoners/sat_solver_sig.mli
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,10 @@ module type S = sig
(** [get_unknown_reason t] returns the reason Alt-Ergo raised
[I_dont_know] if it did. If it did not, returns None. *)
val get_unknown_reason : t -> unknown_reason option

(** [get_value t e] returns the value of [e] as a constant expression
in the current model generated. Returns [None] if can't decide. *)
val get_value : t -> Expr.t -> Expr.t option
end


Expand Down
10 changes: 10 additions & 0 deletions src/lib/reasoners/satml_frontend.ml
Original file line number Diff line number Diff line change
Expand Up @@ -1249,6 +1249,16 @@ module Make (Th : Theory.S) : Sat_solver_sig.S = struct

let get_unknown_reason env = env.unknown_reason

let get_value env t =
match E.type_info t with
| Ty.Tbool ->
begin
match ME.find_opt t env.gamma with
| None -> Some E.faux
| Some _ -> Some E.vrai
end
| _ -> None

let reinit_ctx () =
Steps.reinit_steps ();
Th.reinit_cpt ();
Expand Down
2 changes: 2 additions & 0 deletions src/lib/util/util.ml
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ module MI = Map.Make(struct type t = int
module SI = Set.Make(struct type t = int
let compare (x: int) y = Stdlib.compare x y end)

module MS = Map.Make(String)

module SS = Set.Make(String)


Expand Down
1 change: 1 addition & 0 deletions src/lib/util/util.mli
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ exception Not_implemented of string

module MI : Map.S with type key = int
module SI : Set.S with type elt = int
module MS : Map.S with type key = string
module SS : Set.S with type elt = string

(** Different values for -case-split-policy option:
Expand Down
4 changes: 4 additions & 0 deletions tests/models/bool/bool1.models.expected
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,13 @@ unknown
(
(define-fun q () Bool false)
)
()


unknown
(
(define-fun p () Bool true)
(define-fun q () Bool true)
)
()

1 change: 1 addition & 0 deletions tests/models/bool/bool1.models.smt2
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
(set-logic QF_UF)
(set-option :produce-models true) ; enable model generation
(set-option :produce-assignments true) ; enable get assignment
(declare-const p Bool)
(declare-const q Bool)
; (declare-const t Int)
Expand Down
2 changes: 2 additions & 0 deletions tests/models/bool/bool2.models.expected
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,5 @@
unknown
(
)
()

1 change: 1 addition & 0 deletions tests/models/bool/bool2.models.smt2
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
(set-logic QF_UF)
(set-option :produce-models true)
(set-option :produce-assignments true)
(declare-const x Bool)
(declare-const y Bool)
(assert (or x (not x)))
Expand Down
Loading