How to get result from z3py calculation? - z3py

I want to use z3py to illustrate the following genealogy exercise (pa is “parent” and grpa is “grand-parent)
pa(Rob,Kev) ∧ pa(Rob,Sama) ∧ pa(Sama,Tho) ∧ pa(Dor,Jim) ∧ pa(Bor,Jim) ∧ pa(Bor,Eli) ∧ pa(Jim,Tho) ∧ pa(Sama,Samu) ∧ pa(Jim,Samu) ∧ pa(Zel,Max) ∧ pa(Samu,Max)
∀X,Y,Z pa(X,Z) ∧ pa(Z,Y) → grpa(X,Y)
The exercise consists in finding for which value of X one has the following:
∃X grpa(Rob,X) ∧ pa(X,Max)
(The answer being: for X == Samu.) I would like to rewrite this problem in z3py, so I introduce a new sort Hum (for “humans”) and write the following:
import z3
Hum = z3.DeclareSort('Hum')
pa = z3.Function('pa',Hum,Hum,z3.BoolSort())
grpa = z3.Function('grpa',Hum,Hum,z3.BoolSort())
Rob,Kev,Sama,Tho,Dor,Jim,Bor,Eli,Samu,Zel,Max = z3.Consts('Rob Kev Sama Tho Dor Jim Bor Eli Samu Zel Max', Hum)
s=z3.Solver()
for i,j in ((Rob,Kev),(Rob,Sama),(Sama,Tho),(Dor,Jim),(Bor,Jim),(Bor,Eli),(Jim,Tho),(Sama,Samu),(Jim,Samu),(Zel,Max),(Samu,Max)):
s.add(pa(i,j))
x,y,z=z3.Consts('x y z',Hum)
whi=z3.Const('whi',Hum)
s.add(z3.ForAll([x,y,z],z3.Implies(z3.And(pa(x,z),pa(z,y)),grpa(x,y))))
s.add(z3.Exists(whi,z3.And(grpa(Rob,whi),pa(whi,Max))))
The code is accepted by Python and for
print(s.check())
I get
sat
Now I know there is a solution. The problem is: how do I get the value of whi?
When I ask for print(s.model()[whi]) I get None. When I ask for s.model().evaluate(whi) I get whi, which is not very helpful.
How can I get the information that whi must be Samu for the last formula to be true?
(Auxiliary question: why is there no difference between constants and variables? I'm a bit puzzled when I define x,y,z as constants although they are variable.
Why can I not write x=Hum('x') to show that x is a variable of sort Hum?)

When you write something like:
X, Y = Const('X Y', Hum)
It does not mean that you are declaring two constants named X and Y of sort Hum. (Yes, this is indeed confusing! Especially if you're coming from a Prolog like background!)
Instead, all it means is that you are saying there are two objects X and Y, which belong to the sort Hum. It does not even mean X and Y are different. They might very well be the same, unless you explicitly state it, like this:
s.assert(z3.Distinct([X, Y]))
This might also explain your confusion regarding constants and variables. In your model, everything is a variable; you haven't declared any constants at all.
Your question about how come whi is not Samu is a little trickier to explain, but it stems from the fact that all you have are variables and no constants at all. Furthermore, whi when used as a quantified variable will never have a value in the model: If you want a value for a variable, it has to be a top-level declared variable with its own assertions. This usually trips people who are new to z3py: When you do quantification over a variable, the top-level declaration is a mere trick just to get a name in the scope, it does not actually relate to the quantified variable. If you find this to be confusing, you're not alone: It's a "hack" that perhaps ended up being more confusing than helpful to newcomers. If you're interested, this is explained in detail here: https://theory.stanford.edu/~nikolaj/programmingz3.html#sec-quantifiers-and-lambda-binding But I'd recommend just taking it on faith that the bound variable whi and what you declared at the top level as whi are just two different variables. Once you get more familiar with how z3py works, you can look into the details and reasons behind this hack.
Coming back to your modeling question: You really want these constants to be present in your model. In particular, you want to say these are the humans in my universe and nobody else, and they are all distinct. (Kind of like Prolog's closed world assumption.) This sort of thing is done with a so-called enumeration sort in z3py. Here's how I would go about modeling your problem:
from z3 import *
# Declare an enumerated sort. In this declaration we create 'Human' to be a sort with
# only the elements as we list them below. They are guaranteed to be distinct, and further
# any element of this sort is guaranteed to be equal to one of these.
Human, (Rob, Kev, Sama, Tho, Dor, Jim, Bor, Eli, Samu, Zel, Max) \
= EnumSort('Human', ('Rob', 'Kev', 'Sama', 'Tho', 'Dor', 'Jim', 'Bor', 'Eli', 'Samu', 'Zel', 'Max'))
# Uninterpreted functions for parent/grandParent relationship.
parent = Function('parent', Human, Human, BoolSort())
grandParent = Function('grandParent', Human, Human, BoolSort())
s = Solver()
# An axiom about the parent and grandParent functions. Note that the variables
# x, y, and z are merely for the quantification reasons. They don't "live" in the
# same space when you see them at the top level or within a ForAll/Exists call.
x, y, z = Consts('x y z', Human)
s.add(ForAll([x, y, z], Implies(And(parent(x, z), parent(z, y)), grandParent(x, y))))
# Express known parenting facts. Note that unlike Prolog, we have to tell z3 that
# these are the only pairs of "parent"s available.
parents = [ (Rob, Kev), (Rob, Sama), (Sama, Tho), (Dor, Jim) \
, (Bor, Jim), (Bor, Eli), (Jim, Tho), (Sama, Samu) \
, (Jim, Samu), (Zel, Max), (Samu, Max) \
]
s.add(ForAll([x, y], Implies(parent(x, y), Or([And(x==i, y == j) for (i, j) in parents]))))
# Find what makes Rob-Max belong to the grandParent relationship:
witness = Const('witness', Human)
s.add(grandParent(Rob, Max))
s.add(grandParent(Rob, witness))
s.add(parent(witness, Max))
# Let's see what witness we have:
print s.check()
m = s.model()
print m[witness]
For this, z3 says:
sat
Samu
which I believe is what you were trying to achieve.
Note that the Horn-logic of z3 can express such problems in a nicer way. For that see here: https://rise4fun.com/Z3/tutorialcontent/fixedpoints. It's an extension that z3 supports which isn't available in SMT solvers, making it more suitable for relational programming tasks.
Having said that, while it is indeed possible to express these sorts of relationships using an SMT solver, such problems are really not what SMT solvers are designed for. They are much more suitable for quantifier-free fragments of logics that involve arithmetic, bit-vectors, arrays, uninterpreted-functions, floating-point numbers, etc. It's always fun to try these sorts of problems as a learning exercise, but if this sort of problem is what you really care about, you should really stick to Prolog and its variants which are much more suited for this kind of modeling.

Related

Combine boolean and integer logic in linear arithmetic using the Z3 Solver?

I would like to solve problems combining boolean and integer logic in linear arithmetic with a SAT/SMT solver. At first glance, Z3 seems promising.
First of all, is it at all possible to solve the following problem? This answer makes it seem like it works.
int x,y,z
boolean a,b,c
( (3x + y - 2z >= 10) OR (A AND (NOT B OR C)) OR ((A == C) AND (x + y >= 5)) )
If so, how does Z3 solve this kind of problem in theory and is there any documentation about it?
I could think of two ways to solve this problem. One would be to convert the Boolean operations into a linear integer expression. Another solution I read about is to use the Nelson-Oppen Combination Method described in [Kro 08].
I found a corresponding documentation in chapter 3.2.2. Solving Arithmetical Fragments, Table 1 a listing of the implemented algorithms for a certain logic.
Yes, SMT solvers are quite good at solving problems of this sort. Your problem can be expressed using z3's Python interface like this:
from z3 import *
x, y, z = Ints('x y z')
A, B, C = Bools('A B C')
solve (Or(3*x + y - 2*z >= 10
, And(A, Or(Not(B), C))
, And(A == C, x + y >= 5)))
This prints:
[A = True, z = 3, y = 0, B = True, C = True, x = 5]
giving you a (not necessarily "the") model that satisfies your constraints.
SMT solvers can deal with integers, machine words (i.e., bit-vectors), reals, along with many other data types, and there are efficient procedures for combinations of linear-integer-arithmetic, booleans, uninterpreted-functions, bit-vectors amongst many others.
See http://smtlib.cs.uiowa.edu for many resources on SMT solving, including references to other work. Any given solver (i.e., z3, yices, cvc etc.) will be a collection of various algorithms, heuristics and tactics. It's hard to compare them directly as each shine in their own way for certain sublogics, but for the base set of linear-integer arithmetic, booleans, and bit-vectors, they should all perform fairly well. Looks like you already found some good references, so you can do further reading as necessary; though for most end users it's neither necessary nor that important to know how an SMT solver internally works.

representing large binary vector as problog fact/rule

In ProbLog, how do I represent the following as p-fact/rule :
A binary vector of size N, where P bits are 1 ? i.e. a bit is ON with probability P/N, where N > 1000
i come up with this, but it seem iffy :
0.02::one(X) :- between(1,1000,X).
Want to use it later to make calculations on what happens if i apply two-or-more operations of bin-vec such as : AND,OR,XOR,count,overlap, hamming distance, but do it like Modeling rather than Simulation
F.e. if I ORed random 10 vec's, what is the probable overlap-count of this unionized vector and a new rand vec
... or what is the probability that they will overlap by X bits
.... questions like that
PS> I suspect cplint is the same.
Another try, but dont have idea how to query for 'single' result
1/10::one(X,Y) :- vec(X), between(1,10,Y). %vec: N=10, P=?
vec(X) :- between(1,2,X). %num of vecs
%P=2 ??
two(A,B,C,D) :- one(1,A), one(2,B), A =\= B, one(1,C), one(2,D), C =\= D.
based on #damianodamiono , so far :
P/N::vec(VID,P,N,_Bit).
prob_on([],[],_,_).
prob_on([H1|T1],[H2|T2],P,N):-
vec(1,P,N,H1), vec(2,P,N,H2),
prob_on(T1,T2,P,N).
query(prob_on([1],[1],2,10)).
query(prob_on([1,2,3,5],[1,6,9,2],2,10)).
I'm super happy to see that someone uses Probabilistic Logic Programming! Anyway, usually you do not need to create a list with 1000 elements and then attach 1000 probabilities. For example, if you want to state that each element of the list has a probabilty to be true of P/N (suppose 0.8), you can use (cplint and ProbLog have almost the same syntax, so you can run the programs on both of them):
0.8::on(_).
in the recursion.
For example:
8/10::on(_).
prob_on([]). prob_on([H|T]):-
on(H),
prob_on(T).
and then ask (in cplint)
?- prob(prob_on([1,2,3]),Prob).
Prob = Prob = 0.512
in ProbLog, you need to add query(prob_on([1,2,3])) in the program. Note the usage of the anonymous variable in the probabilistic fact on/1 (is needed, the motivation may be complicated so I omit it). If you want a probability that depends on the lenght of the list and other variables, you can use flexible probabilities:
P/N::on(P,N).
and then call it in your predicate with
...
on(P,N),
...
where both P and N are ground when on/2 is called. In general, you can add also a body in the probabilistic fact (turning it into a clause), and perform whatever operation you want.
With two lists:
8/10::on_1(_).
7/10::on_2(_).
prob_on([],[]).
prob_on([H1|T1],[H2|T2]):-
on_1(H1),
on_2(H2),
prob_on(T1,T2).
?- prob(prob_on([1,2,3,5],[1,6,9,2]),Prob).
Prob = 0.09834496
Hope this helps, let me know if something is still not clear.

Easier way to get "unique" bindings?

Sorry if my terminology is off. Long term (40 years? ouch...) imperative programmer, dabbled in functional, spending some time this morning trying to be a bit more serious about declarative. Going through a learning site, and decided to try the "crossword" in exercise 2.4 here: http://www.learnprolognow.org/lpnpage.php?pagetype=html&pageid=lpn-htmlse7
I've got it, but it feels ridiculously clunky. Here's my newbie solution:
word(astante, a,s,t,a,n,t,e).
word(astoria, a,s,t,o,r,i,a).
word(baratto, b,a,r,a,t,t,o).
word(cobalto, c,o,b,a,l,t,o).
word(pistola, p,i,s,t,o,l,a).
word(statale, s,t,a,t,a,l,e).
crossword(V1,V2,V3,H1,H2,H3):-
word(V1,V1a,V1b,V1c,V1d,V1e,V1f,V1g),
word(V2,V2a,V2b,V2c,V2d,V2e,V2f,V2g),
word(V3,V3a,V3b,V3c,V3d,V3e,V3f,V3g),
word(H1,H1a,H1b,H1c,H1d,H1e,H1f,H1g),
word(H2,H2a,H2b,H2c,H2d,H2e,H2f,H2g),
word(H3,H3a,H3b,H3c,H3d,H3e,H3f,H3g),
V1b = H1b,
V1d = H2b,
V1f = H3b,
V2b = H1d,
V2d = H2d,
V2f = H3d,
V3b = H1f,
V3d = H2f,
V3f = H3f,
not(V1 = V2),
not(V1 = V3),
not(V1 = H1),
not(V1 = H2),
not(V1 = H3),
not(V2 = V3),
not(V2 = H1),
not(V2 = H2),
not(V2 = H3),
not(V3 = H1),
not(V3 = H2),
not(V3 = H3),
not(H1 = H2),
not(H1 = H3),
not(H2 = H3).
It works. crossword will give me the two possible layouts (puzzle is symmetric, after all). But yeesh...
Some of the clunkiness is just because I've only started, so I don't have any feel for how to mark bindings as "don't care" (the 1st, 3rd, 5th, and 7th letters of the words are completely irrelevant, for instance). But what is really chafing me right now is having to put in the triangular matrix of "don't duplicate any bindings" there at the end. This issue keeps coming up (an earlier toy problem involved loves(A,B) and jealous(X,Y) and if you allow X=Y then everybody leading off a loves relationship is claimed to be jealous of themselves (which I see someone else was fighting with a few years ago:Get unique results with Prolog)), but this tutorial doesn't address it. It hasn't even told me about 'not' yet -- I had to dig around elsewhere to get that, which led into completely valid questions of "well, what kind of 'not equal' did you want?", which I am currently unprepared to answer. But I digress...
It is inconceivable to me that this is the way this actually gets done in real code. Combinatorially, it's ridiculous. And it seems like it violates (or at least bends) the principal of least astonishment. Without the uniqueness restrictions, there are a huge number of solutions that just set V1=H1, V2=H2, V3=H3. I guess I could have only disallowed those, but the real solution needs to do the full restriction.
Yes, I completely understand that logically and mathematically there is no reason not to bind the same value into multiple parameters, and also that there are many situations where such a multiple binding is not just helpful but required to handle the issues at hand. I'm not arguing with the default behavior at all, just looking for a better way to express uniqueness constraints when I need them.
I'd love to know a better way to handle this, because honestly I don't think I can dig much deeper into these tutorials if they're all going to require this much fluff to do something so seemingly obvious.
Thanks in advance!
In my opinion, the exercise is setting you up for failure.
First, because it uses a knowledge representation that is unsuitable for processing similar elements in a uniform fashion.
Second, because you do not have the necessary prerequisites at this point in the book to do so, even if the knowledge representation would make it easier.
I can assure you: What you are doing is of course not necessary when programming in Prolog.
So, don't let a single book detract you from the language.
At this point, I would like to show you how you could solve this task if you had more experience with the language, and used more suitable language features. You may enjoy going back to this later, when you have read other material.
The key change I would like to make is to let you reason about the available words more explicitly, as data structures that are available within your program, instead of "only" as facts:
words(Ws) :-
Ws = [[a,s,t,a,n,t,e],
[a,s,t,o,r,i,a],
[b,a,r,a,t,t,o],
[c,o,b,a,l,t,o],
[p,i,s,t,o,l,a],
[s,t,a,t,a,l,e]].
You can of course easily obtain such an explicit (sometimes called spatial) representation automatically, using all-solutions predicates such as findall/3.
The key predicate I now introduce lets us relate a word in this representation to its every second letter:
word_evens([_,A,_,B,_,C,_], [A,B,C]).
Note how easily this relation can be expressed if you can reason explicitly about the list of letters that constitute a word.
Now, the whole solution, using the predicates permutation/2 and transpose/2, which you can either find as library predicates in your Prolog system, or also implement easily yourself:
solution(Ls) :-
Ls = [V1,V2,V3,H1,H2,H3],
words(Ws),
Ws = [First|_],
maplist(same_length(First), Ls),
maplist(word_evens, [H1,H2,H3], Ess),
transpose(Ess, TEss),
maplist(word_evens, [V1,V2,V3], TEss),
permutation(Ws, Ls).
Sample query and the two solutions:
?- solution(Ls).
Ls = [[a, s, t, a, n, t, e], [c, o, b, a, l, t, o], [p, i, s, t, o, l|...], [a, s, t, o, r|...], [b, a, r, a|...], [s, t, a|...]] ;
Ls = [[a, s, t, o, r, i, a], [b, a, r, a, t, t, o], [s, t, a, t, a, l|...], [a, s, t, a, n|...], [c, o, b, a|...], [p, i, s|...]] ;
false.
At least maplist/2 should be available in your Prolog, and same_length/2 is easy to define if your system does not provide it.
If you really want to express disequality of terms, use prolog-dif.
But what is really chafing me right now is having to put in the triangular matrix of "don't duplicate any bindings" there at the end.
We can get a better solution with the all_dif/1 predicate from here: https://stackoverflow.com/a/47294595/4391743
all_dif([]).
all_dif([E|Es]) :-
maplist(dif(E), Es),
all_dif(Es).
This predicate accepts a list of elements that are all different from each other. Don't worry about not understanding it just yet, you can treat it as a black box for now.
You can use this to replace the big block of not goals at the end of your predicate definition:
crossword(V1,V2,V3,H1,H2,H3):-
... % unchanged code here
all_dif([V1, V2, V3, H1, H2, H3]).
With this you get back to only the two solutions in which all the words are used.
I don't have any feel for how to mark bindings as "don't care" (the 1st, 3rd, 5th, and 7th letters of the words are completely irrelevant, for instance).
This is indeed a very important issue! And Prolog should warn you about variables you only use once (called "singletons"), because they are a frequent source of bugs:
Singleton variables: [V1a,V1c,V1e,V1g,V2a,V2c,V2e,V2g,V3a,V3c,V3e,V3g,H1a,H1c,H1e,H1g,H2a,H2c,H2e,H2g,H3a,H3c,H3e,H3g]
You explicitly mark a variable as "don't care" by giving it a name that starts with the underscore character _, or just naming it _ altogether. Different occurrences of _ mark different "don't care" variables. So we get:
crossword(V1,V2,V3,H1,H2,H3):-
word(V1,_,V1b,_,V1d,_,V1f,_),
word(V2,_,V2b,_,V2d,_,V2f,_),
word(V3,_,V3b,_,V3d,_,V3f,_),
word(H1,_,H1b,_,H1d,_,H1f,_),
word(H2,_,H2b,_,H2d,_,H2f,_),
word(H3,_,H3b,_,H3d,_,H3f,_),
V1b = H1b,
V1d = H2b,
V1f = H3b,
V2b = H1d,
V2d = H2d,
V2f = H3d,
V3b = H1f,
V3d = H2f,
V3f = H3f,
all_dif([V1, V2, V3, H1, H2, H3]).
The warnings are gone and we the program is easier to read because the underscores leave "holes" in uninteresting places, and we see more clearly which variables matter.
That leaves us with the task of removing all those equations. General Prolog tip: Except maybe sometimes for reasons of clarity, there is never any need to write an equation of the form Var1 = Var2 where both sides are variables. Just use the same name for both variables in the whole clause, and you get the same result!
So let's replace V1b and H1b by the same variable named A, V1d and H2b by the same variable B, etc.:
crossword(V1,V2,V3,H1,H2,H3):-
word(V1,_,A,_,B,_,C,_),
word(V2,_,D,_,E,_,F,_),
word(V3,_,G,_,H,_,I,_),
word(H1,_,A,_,D,_,G,_),
word(H2,_,B,_,E,_,H,_),
word(H3,_,C,_,F,_,I,_),
all_dif([V1, V2, V3, H1, H2, H3]).
This is equivalent to your initial solution and, I hope, fairly beginner-friendly.
Hopefully this convices you that Prolog programs can be a bit less clunky than your first attempt. Please stick around, we're here to help if you get stuck, and hopefully you will have some less frustrating experiences and see the magic of Prolog.

Prolog - subsitution and evaluation

Hello good people of programming .
Logic programming is always fascinating compare to imperative programming.
As pursuing unknown of logic programming, there is some problems encountering arithmetic expressions.
Here is the code I have done so far.
number_atom(N) :-
(number(N) -> functor(N, _, _); functor(N, _, _), atom(N)).
arithmeticAdd_expression(V,V,Val,Val).
arithmeticAdd_expression(N, _Var, _Val, N) :-
number_atom(N).
arithmeticAdd_expression(X+Y, Var, Val, R) :-
arithmeticAdd_expression(X, Var, Val, RX),
arithmeticAdd_expression(Y, Var, Val, RY),
(number(RX), number(RY) -> R is RX + RY; R = RX + RY).
Taking add operation as example:
arithmeticAdd_expression(Expression, Variable, Value, Result)
?- arithmeticAdd_expression(a+10, a, 1, Result).
?- Result = 11;
?- Result = a + 10.
?- arithmeticAdd_expression(a+10, b, 1, Result).
?- Result = a + 10.
What I would like to achieve is that
if the atom(s) in the Expression can only be substituted by given Variable and value, then Result is the number only like the example shown above(Result = 11). Else, the Result is the Expression itself only. My problem with the code is somewhere there, I just could figure it out. So, Please someone can help me? Thank you.
An important attraction of logic programming over, say, functional programming is that you can often use the same code in multiple directions.
This means that you can ask not only for a particular result if the inputs are given, but also ask how solutions look like in general.
However, for this to work, you have to put some thought into the way you represent your data. For example, in your case, any term in your expression that is still a logical variable may denote either a given number or an atom that should be interpreted differently than a plain number or an addition of two other terms. This is called a defaulty representation because you have to decide what a variable should denote by default, and there is no way to restrict its meaning to only one of the possible cases.
Therefore, I suggest first of all to change the representation so that you can symbolically distinguish the two cases. For example, to represent expressions in your case, let us adopt the convention that:
atoms are denoted by the wrapper a/1
numbers are denoted by the wrapper n/1.
and as is already the case, (+)/2 shall denote addition of two expressions.
So, a defaulty term like b+10 shall now be written as: a(b)+n(10). Note the use of the wrappers a/1 and n/1 to make clear which case we are dealing with. Such a representation is called clean. The wrappers are arbitrarily (though mnemonically) chosen, and we could have used completely different wrappers such as atom/1 and number/1, or atm/1 and nmb/1. The key property is only that we can now symbolically distinguish different cases by virtue of their outermost functor and arity.
Now the key advantage: Using such a convention, we can write for example: a(X)+n(Y). This is a generalization of the earlier term. However, it carries a lot more information than only X+Y, because in the latter case, we have lost track of what these variables stand for, while in the former case, this distinction is still available.
Now, assuming that this convention is used in expressions, it becomes straight-forward to describe the different cases:
expression_result(n(N), _, _, n(N)).
expression_result(a(A), A, N, n(N)).
expression_result(a(A), Var, _, a(A)) :-
dif(A, Var).
expression_result(X+Y, Var, Val, R) :-
expression_result(X, Var, Val, RX),
expression_result(Y, Var, Val, RY),
addition(RX, RY, R).
addition(n(X), n(Y), n(Z)) :- Z #= X + Y.
addition(a(X), Y, a(X)+Y).
addition(X, a(Y), X+a(Y)).
Note that we can now use pattern matching to distinguish the cases. No more if-then-elses, and no more atom/1 or number/1 tests are necessary.
Your test cases work as expected:
?- expression_result(a(a)+n(10), a, 1, Result).
Result = n(11) ;
false.
?- expression_result(a(a)+n(10), b, 1, Result).
Result = a(a)+n(10) ;
false.
And now the key advantage: With such a pure program (please see logical-purity for more information), we can also ask "What do results look like in general?"
?- expression_result(Expr, Var, N, R).
Expr = R, R = n(_1174) ;
Expr = a(Var),
R = n(N) ;
Expr = R, R = a(_1698),
dif(_1698, Var) ;
Expr = n(_1852)+n(_1856),
R = n(_1896),
_1852+_1856#=_1896 ;
Expr = n(_2090)+a(Var),
R = n(_2134),
_2090+N#=_2134 .
Here, I have used logical variables for all arguments, and I get quite general answers from this program. This is why I have used clpfd constraints for declarative integer arithmetic.
Thus, your immediate issue can be readily solved by using a clean representation, and using the code above.
Only one very small challenge remains: Maybe you actually want to use a defaulty representation such as c+10 (instead of a(c)+n(10)). The task you are then facing is to convert the defaulty representation to a clean one, for example via a predicate defaulty_clean/2. I leave this as an easy exercise. Once you have a clean representation, you can use the code above without changes.

Necessary and Sufficient vs Soundness and Completeness

I am trying to learn proof. I came across these 4 terms. I am trying to relate all.
A: X>Y B: Y<X
Necessary Condition
B implies A
Sufficient Condition
A implies B
And
A = { set of statements} Q= a statement
Soundness
if A derives Q then A is a logical consequence of Q
Completeness
if A is a logical consequence of Q then A derives Q.
What is relation between all?
Help is appreciated.
Necessary / sufficient doesn't have much to do with soundness and completeness so I'll explain the two concepts separately.
Necessary / sufficient:
In your example, the two statements are equivalent: X>Y if and only if Y<X. So it is indeed the case that B implies A and A implies B. A better example would perhaps be:
A: X>Y+1
B: X>Y
Here A would imply B, i.e. A would be sufficient for B to hold. The other way would not hold: B does not imply A (since you could have X=10 and Y=9 in which case only B would hold). This means that A is not necessary for B.
Completeness/soundness:
This took a while for me to wrap my head around when I first encountered it. But it's really simple!
Suppose you have the following:
A = { X>Y, Y>Z }
Q = X>Z
Now, soundsess says that we can't reach crazyness by sticking to the statements of A. More formally, if Q does not hold, it can't be derived from A. Or, only valid things can be derived from A.
It's easy to create an unsound set of statements. Take for instance
A = { x<Y, X>Y }
They contradict each other, so we can for instance derive X>X (which is false) by using proof by contradiction.
Completeness says the dual: All valid things can be derived from A. Suppose that X, Y and Z are the only variables in the world, and > is the only relation in the world. Then a set of statements such as
A = { X>Y, Y>Z }
is complete, since for any two given variables, a and b, we can derive a>b if and only if a>b in fact holds.
If we would only have
A = { X>Y } (and no knowledge about Z)
then the set of statements would not be complete since there would be true statements about Z which we could say nothing about.
In a nutshell: Soundness says that you can't come to crazy conclusions, and completeness says that you can reach all sensible conclusions.

Resources