aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--README.org5
-rw-r--r--examples/README.org52
-rw-r--r--examples/arithmetic.sol17
-rw-r--r--examples/constraints.sol27
-rw-r--r--examples/family.sol27
-rw-r--r--examples/integrity.sol35
-rw-r--r--examples/quantifiers.sol40
-rw-r--r--examples/recursion.sol33
-rw-r--r--theories/Interval.v80
-rw-r--r--theories/dune9
10 files changed, 323 insertions, 2 deletions
diff --git a/README.org b/README.org
index cd6149c..832ce9d 100644
--- a/README.org
+++ b/README.org
@@ -191,7 +191,10 @@ algebra.
Build with =nix develop -c dune build=; run the tests with =nix
develop -c dune runtest=; run a program with =dune exec sol --
-file.sol=.
+file.sol=. Worked examples live in =examples/= (see
+=examples/README.org=); machine checked proofs in =theories/=
+(structural and lexicographic decrease, extracted into admission;
+the interval characterizations behind propagation).
Machine checked proofs live in =theories/=: the admission checker's
core claim, that strict containment decreases a well founded
diff --git a/examples/README.org b/examples/README.org
new file mode 100644
index 0000000..20b853a
--- /dev/null
+++ b/examples/README.org
@@ -0,0 +1,52 @@
+#+TITLE: Sol Examples
+
+* Running
+
+From the repository root, with Nix:
+
+#+BEGIN_SRC sh
+nix develop -c dune exec sol -- examples/family.sol
+#+END_SRC
+
+Or enter the shell once and run them all:
+
+#+BEGIN_SRC sh
+nix develop
+dune build
+for f in examples/*.sol; do echo "== $f"; dune exec sol -- "$f"; done
+#+END_SRC
+
+Without a file argument, =dune exec sol= starts a REPL: declarations
+accumulate, =Query expression: ...= runs immediately, one statement
+per line.
+
+* The files
+
+- =family.sol= :: relations, facts, a rule, joins through shared
+ variables, decidable denial with =:not=, and the =World= scoped
+ negation form.
+- =arithmetic.sol= :: =Plus= and =Times= evaluating forward and
+ solving backward through the rationals; minimal shape, where
+ =1/2 + 1/2= is the integer =1=.
+- =recursion.sol= :: structural recursion admitted by the decrease
+ checker extracted from Rocq, and Even/Odd mutual recursion under a
+ lexicographic measure.
+- =constraints.sol= :: membership is not generation: residuation,
+ interval propagation, finite ranges enumerating, contradictions
+ answering no, and explicit bounded generation with =Between=.
+- =quantifiers.sol= :: universals over infinite domains proven or
+ refuted symbolically, including the specification's own true and
+ false examples and the =Plus= identity; finite quantifiers simply
+ run.
+- =integrity.sol= :: schemas as membership: primitive domains, a
+ refinement, and a foreign key as a criterion. The comments at the
+ bottom show facts that make admission reject the whole program;
+ uncomment one and run again to see it happen.
+
+* Reading the answers
+
+Each query prints one line per answer: variable bindings in Sol
+notation, =yes.= for a closed query that holds, =no.= for one that
+does not. An answer conditioned on open membership prints its
+residual constraints after the bindings, as the propositions they
+are.
diff --git a/examples/arithmetic.sol b/examples/arithmetic.sol
new file mode 100644
index 0000000..a56d82e
--- /dev/null
+++ b/examples/arithmetic.sol
@@ -0,0 +1,17 @@
+% Arithmetic solves backward, and the numeric tower keeps its
+% minimal shape: an integer-valued rational IS the integer.
+
+% Forward: 3 + 4.
+Query expression: [Plus left: 3 right: 4 result: R]
+
+% Backward: what plus 5 gives 12?
+Query expression: [Plus left: X right: 5 result: 12]
+
+% Through the rationals: 3 * X = 2.
+Query expression: [Times left: 3 right: X result: 2]
+
+% Minimal shape: 1/2 + 1/2 is the integer 1, not the rational 2/2.
+Query expression: [Plus left: 1/2 right: 1/2 result: R]
+
+% Rationals reduce on sight: 2/4 equals 1/2.
+Query expression: [Equal left: 2/4 right: 1/2]
diff --git a/examples/constraints.sol b/examples/constraints.sol
new file mode 100644
index 0000000..676f4c1
--- /dev/null
+++ b/examples/constraints.sol
@@ -0,0 +1,27 @@
+% Membership is not generation. Open membership constrains; the
+% engine residuates, propagates, and enumerates only finite ranges.
+
+% An open constraint comes back as the answer's condition.
+Query expression: [Integer value: X]
+
+% A later equality grounds it and the residue discharges.
+Query expression: [Natural value: X] :and [Equal left: X right: 5]
+
+% Bounds that pin an integer into a finite range enumerate, the
+% same admissible enumeration Between performs explicitly.
+Query expression: [Natural value: X] :and [LessThan left: X right: 4]
+
+% Strict rational bounds tighten to integers: 1/2 < X <= 5/2.
+Query expression:
+ [Integer value: X] :and
+ [GreaterThan left: X right: 1/2] :and
+ [LessThanEqual left: X right: 5/2]
+
+% An empty interval is no, not a conditioned maybe.
+Query expression: [GreaterThan left: X right: 5] :and [LessThan left: X right: 3]
+
+% Explicit bounded generation.
+Query expression: [Between value: X lower: 1 upper: 4] :and [GreaterThan left: X right: 2]
+
+% Disjunction branches finitely.
+Query expression: [Equal left: X right: 1] :or [Equal left: X right: 2]
diff --git a/examples/family.sol b/examples/family.sol
new file mode 100644
index 0000000..8d1f7f9
--- /dev/null
+++ b/examples/family.sol
@@ -0,0 +1,27 @@
+% Relations, facts, rules, joins and negation.
+
+Relation named: Person schema: #(
+ [name domain: String]
+ [age domain: Integer]
+)
+
+[Person name: "Marcos" age: 30]
+[Person name: "Ana" age: 12]
+[Person name: "Rui" age: 70]
+
+Rule name: "an adult is a person of age at least 18"
+ head: [Adult name: N]
+ body: [Person name: N age: A] :and
+ [GreaterEqual left: A right: 18]
+
+% Everyone stored.
+Query expression: [Person name: N age: A]
+
+% The rule, joined through the shared variable N.
+Query expression: [Adult name: N]
+
+% Decidable denial: people who are not adults.
+Query expression: ([Person name: N age: A]) :not [Adult name: N]
+
+% The specification's negation form, scoped by the universe.
+Query expression: [World] :not [Adult name: "Ana"]
diff --git a/examples/integrity.sol b/examples/integrity.sol
new file mode 100644
index 0000000..2729f4b
--- /dev/null
+++ b/examples/integrity.sol
@@ -0,0 +1,35 @@
+% Schemas constrain by membership: primitive domains, refinements,
+% and foreign keys are one mechanism. A program whose facts violate
+% them gets no world at all; this one is admitted.
+
+Relation named: Department schema: #(
+ [id domain: Integer]
+ [title domain: String]
+)
+
+[Department id: 1 title: "Engineering"]
+[Department id: 2 title: "Design"]
+
+Relation named: Employee schema: #(
+ [name domain: String]
+ % A foreign key is a membership criterion, nothing more special.
+ % Only the subject D is free; other attributes stay existential.
+ [dept domain: ([Department id: D])]
+ % A refinement: a natural at most 100.
+ [score domain: ([Natural value: S] :and [LessThanEqual left: S right: 100])]
+)
+
+[Employee name: "Marcos" dept: 1 score: 90]
+[Employee name: "Ana" dept: 2 score: 75]
+
+Query expression: [Employee name: N dept: D score: S]
+
+% Join through the foreign key.
+Query expression:
+ [Employee name: N dept: D score: S] :and
+ [Department id: D title: T]
+
+% Try it yourself: append one of these lines and watch admission
+% reject the whole program.
+% [Employee name: "Ghost" dept: 9 score: 50]
+% [Employee name: "Over" dept: 1 score: 101]
diff --git a/examples/quantifiers.sol b/examples/quantifiers.sol
new file mode 100644
index 0000000..3a65145
--- /dev/null
+++ b/examples/quantifiers.sol
@@ -0,0 +1,40 @@
+% Universals over infinite domains are proven or refuted
+% symbolically, never enumerated.
+
+% The specification's true example: every natural is at least zero.
+Query expression:
+ [ForAll variable: X
+ quantifier: [Natural value: X]
+ body: [GreaterEqual left: X right: 0]]
+
+% The specification's false example: not every integer is.
+Query expression:
+ [ForAll variable: X
+ quantifier: [Integer value: X]
+ body: [GreaterEqual left: X right: 0]]
+
+% Subtype inclusion, decided symbolically: every integer is a
+% rational, literally.
+Query expression:
+ [ForAll variable: X
+ quantifier: [Integer value: X]
+ body: [Rational value: X]]
+
+% The algebra of Plus: adding zero is the identity, over all
+% integers, stated flat as membership.
+Query expression:
+ [ForAll variable: X
+ quantifier: [Integer value: X]
+ body: [Plus left: X right: 0 result: X]]
+
+% Universals over finite quantifiers just run.
+Query expression:
+ [ForAll variable: X
+ quantifier: [Between value: X lower: 1 upper: 5]
+ body: [GreaterThan left: X right: 0]]
+
+% Existence, over a finite quantifier.
+Query expression:
+ [Exists variable: X
+ quantifier: [Between value: X lower: 1 upper: 10]
+ body: [Times left: X right: X result: 49]]
diff --git a/examples/recursion.sol b/examples/recursion.sol
new file mode 100644
index 0000000..0a1c389
--- /dev/null
+++ b/examples/recursion.sol
@@ -0,0 +1,33 @@
+% Recursion is admitted only with a termination proof. Length
+% decreases structurally: T sits strictly inside #(H | T), and the
+% checker that validates this is extracted from Rocq.
+
+Rule name: "an empty list has length zero"
+ head: [Length list: #() result: 0]
+ body: [True]
+
+Rule name: "a cons adds one"
+ head: [Length list: #(H | T) result: R]
+ body: [Length list: T result: P] :and
+ [Plus left: 1 right: P result: R]
+
+Query expression: [Length list: #(1 2 3) result: R]
+Query expression: [Length list: #("a" "b") result: R]
+
+% Mutual recursion under a shared lexicographic measure: Even and
+% Odd pass a shrinking list back and forth.
+
+Rule name: "the empty list is even"
+ head: [Even list: #()]
+ body: [True]
+
+Rule name: "a cons flips even to odd"
+ head: [Even list: #(H | T)]
+ body: [Odd list: T]
+
+Rule name: "a cons flips odd to even"
+ head: [Odd list: #(H | T)]
+ body: [Even list: T]
+
+Query expression: [Even list: #(1 2 3 4)]
+Query expression: [Even list: #(1 2 3)]
diff --git a/theories/Interval.v b/theories/Interval.v
new file mode 100644
index 0000000..7562170
--- /dev/null
+++ b/theories/Interval.v
@@ -0,0 +1,80 @@
+(* The integer-tightening facts behind the interval engine.
+
+ Propagate.lower_int and Propagate.upper_int in lib/vm.ml convert
+ rational bounds into integer endpoints: a non-strict lower bound
+ q becomes ceiling q, a strict one becomes floor q + 1, and dually
+ for upper bounds. These four characterizations say the
+ conversions are exactly right: an integer satisfies the rational
+ bound iff it satisfies the tightened integer bound. Both
+ propagation (enumeration endpoints, contradiction detection) and
+ symbolic discharge (entailment over integer kinds) lean on
+ them. *)
+
+From Stdlib Require Import QArith Qround ZArith Lia.
+
+Lemma inject_Z_le : forall a b : Z, (a <= b)%Z <-> (inject_Z a <= inject_Z b)%Q.
+Proof. intros a b; unfold Qle, inject_Z; simpl; lia. Qed.
+
+Lemma Qfloor_inject : forall z : Z, Qfloor (inject_Z z) = z.
+Proof. intro z; unfold Qfloor, inject_Z; simpl; apply Z.div_1_r. Qed.
+
+Lemma Qceiling_inject : forall z : Z, Qceiling (inject_Z z) = z.
+Proof.
+ intro z; unfold Qceiling, Qopp, Qfloor, inject_Z; simpl.
+ rewrite Z.div_1_r; lia.
+Qed.
+
+(* Non-strict lower bound: q <= z iff ceiling q <= z. *)
+Lemma lower_nonstrict : forall (q : Q) (z : Z),
+ (q <= inject_Z z)%Q <-> (Qceiling q <= z)%Z.
+Proof.
+ intros q z; split; intro H.
+ - rewrite <- (Qceiling_inject z). apply Qceiling_resp_le; exact H.
+ - apply Qle_trans with (inject_Z (Qceiling q)); [ apply Qle_ceiling | ].
+ exact (proj1 (inject_Z_le _ _) H).
+Qed.
+
+(* Strict lower bound: q < z iff floor q + 1 <= z. *)
+Lemma lower_strict : forall (q : Q) (z : Z),
+ (q < inject_Z z)%Q <-> (Qfloor q + 1 <= z)%Z.
+Proof.
+ intros q z; split; intro H.
+ - assert (Hle : (Qfloor q <= z)%Z).
+ { rewrite <- (Qfloor_inject z). apply Qfloor_resp_le.
+ apply Qlt_le_weak; exact H. }
+ destruct (Z.eq_dec (Qfloor q) z) as [ Heq | ]; [ | lia ].
+ exfalso.
+ apply (Qlt_irrefl (inject_Z z)).
+ eapply Qle_lt_trans; [ | exact H ].
+ rewrite <- Heq. apply Qfloor_le.
+ - apply Qlt_le_trans with (inject_Z (Qfloor q + 1)); [ apply Qlt_floor | ].
+ exact (proj1 (inject_Z_le _ _) H).
+Qed.
+
+(* Non-strict upper bound: z <= q iff z <= floor q. *)
+Lemma upper_nonstrict : forall (q : Q) (z : Z),
+ (inject_Z z <= q)%Q <-> (z <= Qfloor q)%Z.
+Proof.
+ intros q z; split; intro H.
+ - rewrite <- (Qfloor_inject z). apply Qfloor_resp_le; exact H.
+ - apply Qle_trans with (inject_Z (Qfloor q)); [ | apply Qfloor_le ].
+ exact (proj1 (inject_Z_le _ _) H).
+Qed.
+
+(* Strict upper bound: z < q iff z <= ceiling q - 1. *)
+Lemma upper_strict : forall (q : Q) (z : Z),
+ (inject_Z z < q)%Q <-> (z <= Qceiling q - 1)%Z.
+Proof.
+ intros q z; split; intro H.
+ - assert (Hle : (z <= Qceiling q)%Z).
+ { rewrite <- (Qceiling_inject z). apply Qceiling_resp_le.
+ apply Qlt_le_weak; exact H. }
+ destruct (Z.eq_dec z (Qceiling q)) as [ Heq | ]; [ | lia ].
+ exfalso.
+ apply (Qlt_irrefl (inject_Z z)).
+ eapply Qlt_le_trans; [ exact H | ].
+ rewrite Heq. apply Qle_ceiling.
+ - apply Qle_lt_trans with (inject_Z (Qceiling q - 1)).
+ + exact (proj1 (inject_Z_le _ _) H).
+ + apply Qceiling_lt.
+Qed.
diff --git a/theories/dune b/theories/dune
index 57cccb6..bde0f7f 100644
--- a/theories/dune
+++ b/theories/dune
@@ -22,6 +22,13 @@
(setenv OCAMLPATH ""
(run coqc -q Lex.v))))
+(rule
+ (targets Interval.vo Interval.glob)
+ (deps Interval.v)
+ (action
+ (setenv OCAMLPATH ""
+ (run coqc -q Interval.v))))
+
(alias
(name default)
- (deps Decrease.vo DecreaseChecker.vo Lex.vo))
+ (deps Decrease.vo DecreaseChecker.vo Lex.vo Interval.vo))