I would write {1, 2} + {3} + {4} = {1, 2, 3, 4} in maths to say that the sets on LHS partition that of RHS. Is there something similar in isabelle so I don't need to go through all the permutations {1, 2} intersect {3} = {} etc.
[EDIT]
I've found this disjoint definition in the probability sigma algebra package but is there anything that wouldn't introduce that dependency?
disjoint is probably the best choice. You can just copy the definition and the few lemmas after it to your own theory.
I will talk to Johannes Hölzl (who made the Probability Theory library) and ask him what he thinks about moving disjoint into HOL so that it is available with no extra imports.
In the development version of Isabelle (http://isabelle.in.tum.de/repos/isabelle/rev/53697011b03a) it is now in its own theory file:
~~/src/HOL/Library/Disjoint_Sets
Related
The theorem 1+2+...+n = n(n+1)/2 seems to be provable by first translating it to CNFs and repeatedly applying resolution rules to them, as discussed in the introduction section of [1]. I believe this means the theorem is solvable by SMT solver like Z3.
However, I thought first-order logic cannot deal with these kind of problems, because "No first-order theory, however, has the strength to uniquely describe a structure with an infinite domain, such as the natural numbers or the real line." [2]
So, my questions are:
Can we use Z3 to prove the theorem?
If so, how to program the equation 1+2+...+n = n(n+1)/2 in Z3?
[1] Learning to Prove Theorems via Interacting with Proof Assistants
[2] https://en.wikipedia.org/wiki/First-order_logic#:~:text=First%2Dorder%20logic%20is%20the,%2C%20into%20first%2Dorder%20logic.
I tried using Z3 to solve it, but I couldn't figure out how to specify the formula "1+2+..+n" in Z3 in the first place.
You can express such theorems, but existing SMT solvers aren't strong enough to prove them out-of-the box, at least not for the time being. This is because SMT solvers do not do induction, and such claims require induction for their proofs.
To illustrate, this is how you'd write the theorem you want in z3's Python API:
from z3 import *
dummy = Int('dummy')
Sum = RecFunction("Sum", IntSort(), IntSort())
RecAddDefinition(Sum, [dummy], If(dummy == 0, 0, dummy + Sum(dummy-1)))
def P(n):
return Sum(n) == n * (n+1) / 2
n = Int('n')
prove(P(n))
Alas, if you run this you'll see that z3 loops forever.
In certain cases, you can get away by posing the inductive argument yourself:
from z3 import *
dummy = Int('dummy')
Sum = RecFunction("Sum", IntSort(), IntSort())
RecAddDefinition(Sum, [dummy], If(dummy == 0, 0, dummy + Sum(dummy-1)))
def P(n):
return Sum(n) == n * (n+1) / 2
s = Solver()
# Induction. Base case:
s.add(P(0))
# Inductive-step. Assert for `n`. Assert the negation for `n+1`, hoping for unsat answer.
n = Int('n')
s.add(P(n))
s.add(Not(P(n+1)))
print(s.check())
Alas, this loops forever as well. The tool is just not capable of handling the required step.
You can also try adding Sum as an uninterpreted function, add axioms about it, and see if you can use some patterns to push the proof through. Alas, none of these attempts are very satisfactory. Even if you get a proof, it'll be brittle.
Bottom line, SMT solvers don't do induction, at least not for the time being. But the SMTLib spec allows for recursive function definitions, which naturally require inductive reasoning capabilities. Perhaps things will get better in the future, with possible user annotations, helping the solver find the inductive invariant. But, as it stands in early 2023, if you want to prove such theorems, you need to use actual theorem-provers, such as Isabelle, Coq, Lean, ACL2, etc.; or systems like Dafny, why3, etc., which automate inductive proofs much better than SMT solvers themselves.
I am trying to write a technical report that explains my algorithm for my work and instead of writing long pseudocode, I want to write it in neat mathematical notations. However, because of my limited knowledge of math, I cannot come up with a clean and straightforward notation for the below pseudocode:
elements = [a, b, ..., z] // a sequence of some elements with size K.
interested_poses = {1, 4, ... t} // a set of indexes of elements that we are interested in.
results = ∅ // an empty set to hold result elements.
FOR I = 1 to K DO
IF I ∈ interested_poses THEN
ADD elements[I] to results
If someone can guide me how I can convert the above to math notations, that would be very much appreciated as well as some references that I can use for further study for myself.
Thanks in advance.
I am lost in the sea of different kinds of constraint solvers available. I am writing a database query program.
Given an equation where terms are sets of non-consecutive integers, my goal is to simplify the equation as the influence of some terms might completely overlap or be completely disjoint in/from the resulting set, therefore making them discardable.
Example:
({1, 2} ∪ {3, 4}) - {4, 6}
as a first step can be simplified to:
({1, 2} ∪ {3, 4}) - {4}
since {6} ⊄ ({1, 2} ∪ {3, 4}), etc.
What kind of solver can one use for such a problem? I found library(fd_sets) which seems appropriate. However, ECLiPSE is not an easy platform to work with. Are there other solutions?
({1, 2} ∪ {3, 4}) - {4, 6}
is not an equation (or constraint), so there is nothing to solve. So you don't need a solver. It is a (constant) expression. We can easily evaluate this expression and print the result. E.g. in straightforward Python:
print({1,2}.union({3,4}).difference({4,6}))
will give
{1, 2, 3}
Python knows about sets, so that makes it easy. To automatically go from ({1, 2} ∪ {3, 4}) - {4, 6} to {1,2}.union({3,4}).difference({4,6}), you would need to build a parser. This is not very difficult to do (for a skilled programmer). If your favorite programming language does not know about sets, it is not too difficult to create data structures and functions to handle sets. Many languages have some support for sets (e.g. the standard C++ library has sets).
PS. To be complete: Python also allows infix set operators. E.g.:
print({1,2} | {3,4} - {4,6})
After a long search on google I couldn't find a clear answer of this:
In Prolog doing recursion by itself its easy. My main problem is understanding where to place accumulators and counters. Here is an example:
nXlist(N,X,[X|T]):-
N \=0,
N1 is N-1,
nXList(N1,X,T).
nXList(0,_,[]).
media([X|L], N, Soma):-
media(L, N1, Soma1),
N is N1 + 1,
Soma is Soma1 + X.
media([], 0, 0).
On the first example i used a counter BEFORE the recursion but in the second example I use it AFTER. The reason I have done that is the called try and see cause i really can't understand why sometimes is before and sometimes is after...
Maybe the central point of your question is in the preamble:
In Prolog doing recursion by itself its easy
It's not easy, it's mandatory. We don't have loops, because we don't have a way to control them. Variables are assign once.
So, I think the practical answer is rather simple: if the 'predicate' (like is/2) needs a variable value, you ground the variable before calling it.
To me, it helps to consider a Prolog program (a set of clauses) as grammar productions, and clause arguments as attributes, either inherited (values computed before the 'instruction pointer') or synthesized (values computed 'here', to be returned).
update: Most importantly, if the recursive call is not last, the predicate is not tail recursive. So, having anything after the recursive call should be avoided if possible. Notice that both definitions in the answer by user false are tail recursive, and that's precisely due to the fact that the arithmetic conditions there are placed before the recursive call, in both of them. That's so basic, that we have to make an effort to notice it explicitly.
Sometimes we count down, sometimes we count up. I discuss this in another answer at length. It talks of accumulators, befores and afters. :)
There's also this thing called "associativity" of an operation (say, +), where
a+(b+(c+....)) == (a+b)+(c+...)
that lets us regroup and (partially) calculate sooner rather than later. As soon as possible, but not sooner.
Short answer: you can place such arithmetical relations both before and thereafter. At least, if you are using constraints in place of (is)/2. The only difference may be in termination and errors.
So let's see how your predicates can be defined with constraints:
:- use_module(library(clpfd)).
nXList(0,_,[]).
nXList(N,X,[X|T]):-
N #> 0,
N1 #= N-1,
nXList(N1,X,T).
media([], 0, 0).
media([X|L], N, Soma):-
N #> 0,
N #= N1 + 1,
Soma #= Soma1 + X,
media(L, N1, Soma1).
You can now use these definitions in a much more general way, say:
?- nXList(3, X, T).
T = [X, X, X]
; false.
?- media(Xs, 3, S).
Xs = [_A, _B, _C], _D+_A#=S, _C+_B#=_D
; false.
... nXList/3 can be more compactly expressed by:
..., length(T, N), maplist(=(X), T), ...
I am very confused in how CLP works in Prolog. Not only do I find it hard to see the benefits (I do see it in specific cases but find it hard to generalise those) but more importantly, I can hardly make up how to correctly write a recursive predicate. Which of the following would be the correct form in a CLP(R) way?
factorial(0, 1).
factorial(N, F):- {
N > 0,
PrevN = N - 1,
factorial(PrevN, NewF),
F = N * NewF}.
or
factorial(0, 1).
factorial(N, F):- {
N > 0,
PrevN = N - 1,
F = N * NewF},
factorial(PrevN, NewF).
In other words, I am not sure when I should write code outside the constraints. To me, the first case would seem more logical, because PrevN and NewF belong to the constraints. But if that's true, I am curious to see in which cases it is useful to use predicates outside the constraints in a recursive function.
There are several overlapping questions and issues in your post, probably too many to coherently address to your complete satisfaction in a single post.
Therefore, I would like to state a few general principles first, and then—based on that—make a few specific comments about the code you posted.
First, I would like to address what I think is most important in your case:
LP ⊆ CLP
This means simply that CLP can be regarded as a superset of logic programming (LP). Whether it is to be considered a proper superset or if, in fact, it makes even more sense to regard them as denoting the same concept is somewhat debatable. In my personal view, logic programming without constraints is much harder to understand and much less usable than with constraints. Given that also even the very first Prolog systems had a constraint like dif/2 and also that essential built-in predicates like (=)/2 perfectly fit the notion of "constraint", the boundaries, if they exist at all, seem at least somewhat artificial to me, suggesting that:
LP ≈ CLP
Be that as it may, the key concept when working with CLP (of any kind) is that the constraints are available as predicates, and used in Prolog programs like all other predicates.
Therefore, whether you have the goal factorial(N, F) or { N > 0 } is, at least in principle, the same concept: Both mean that something holds.
Note the syntax: The CLP(ℛ) constraints have the form { C }, which is {}(C) in prefix notation.
Note that the goal factorial(N, F) is not a CLP(ℛ) constraint! Neither is the following:
?- { factorial(N, F) }.
ERROR: Unhandled exception: type_error({factorial(_3958,_3960)},...)
Thus, { factorial(N, F) } is not a CLP(ℛ) constraint either!
Your first example therefore cannot work for this reason alone already. (In addition, you have a syntax error in the clause head: factorial (, so it also does not compile at all.)
When you learn working with a constraint solver, check out the predicates it provides. For example, CLP(ℛ) provides {}/1 and a few other predicates, and has a dedicated syntax for stating relations that hold about floating point numbers (in this case).
Other constraint solver provide their own predicates for describing the entities of their respective domains. For example, CLP(FD) provides (#=)/2 and a few other predicates to reason about integers. dif/2 lets you reason about any Prolog term. And so on.
From the programmer's perspective, this is exactly the same as using any other predicate of your Prolog system, whether it is built-in or stems from a library. In principle, it's all the same:
A goal like list_length(Ls, L) can be read as: "The length of the list Ls is L."
A goal like { X = A + B } can be read as: The number X is equal to the sum of A and B. For example, if you are using CLP(Q), it is clear that we are talking about rational numbers in this case.
In your second example, the body of the clause is a conjunction of the form (A, B), where A is a CLP(ℛ) constraint, and B is a goal of the form factorial(PrevN, NewF).
The point is: The CLP(ℛ) constraint is also a goal! Check it out:
?- write_canonical({a,b,c}).
{','(a,','(b,c))}
true.
So, you are simply using {}/1 from library(clpr), which is one of the predicates it exports.
You are right that PrevN and NewF belong to the constraints. However, factorial(PrevN, NewF) is not part of the mini-language that CLP(ℛ) implements for reasoning over floating point numbers. Therefore, you cannot pull this goal into the CLP(ℛ)-specific part.
From a programmer's perspective, a major attraction of CLP is that it blends in completely seamlessly into "normal" logic programming, to the point that it can in fact hardly be distinguished at all from it: The constraints are simply predicates, and written down like all other goals.
Whether you label a library predicate a "constraint" or not hardly makes any difference: All predicates can be regarded as constraints, since they can only constrain answers, never relax them.
Note that both examples you post are recursive! That's perfectly OK. In fact, recursive predicates will likely be the majority of situations in which you use constraints in the future.
However, for the concrete case of factorial, your Prolog system's CLP(FD) constraints are likely a better fit, since they are completely dedicated to reasoning about integers.