:- op(700, xfy, and).
:- op(700, xfy, ==>).
:- op(750, xfy, or).
:- op(700, fx, not).
fact('I go to school' and 'I study' and 'I exercise').
fact('I exercise' ==> 'I\'m healthy').
fact('I study' or 'I\'m sick').
fact(not 'it rains today').
fact('I study' and ('I go to gym' or 'I drink a coffee')).
fact('it rains today' or ('I exercise' and 'I study')).
fact('I\'m healthy' ==> ('I\'m happy' or 'I\'m fit')).
true(C) :- fact(F), conjunction_conjunct(F, C).
true(B) :- fact(A ==> B), true(A).
true(A or _) :- true(A).
true(_ or B) :- true(B).
conjunction_conjunct(A and B, D) :-
( conjunction_conjunct(A, D)
; conjunction_conjunct(B, D)
?- true('I study' and 'I exercise').
?- true('I study' or 'I\'m sick').
?- true('I go to gym').
?- true('I\'m healthy').
?- true('it rains today' or 'I\'m healthy').
T = ('I go to school'and'I study'and'I exercise') ;
T = 'I go to school' ;
T = ('I study'and'I exercise') ;
T = 'I study' ;
T = 'I exercise' ;
T = ('I exercise'==>'I\'m healthy') ;
T = ('I study'or'I\'m sick') ;
T = (not'it rains today') ;
T = ('I study'and('I go to gym'or'I drink a coffee')) .
This of course requires a slightly different way to encode the task, using logical variables:
sat(Exercise =< Healthy),
sat(Study + Sick),
sat(Study * ( Gym + Coffee)),
sat(Rain + (Exercise * Study)),
sat(Healthy =< (Happy + Fit)).
School = Study, Study = Exercise, Exercise = Healthy, Healthy = 1,
Rain = 0,
To obtain ground solutions, you can use labeling/1 on the remaining free variables, showing all possible ways in which truth values can be assigned to them.
One way is to simply extend the Prolog engine to handle such constraints natively. Many basic constraints are implemented in this way. In fact, you can regard even (=)/2, i.e., unification, as one such constraint, and all Prolog systems implement it, typically as one of their virtual machine instructions. The same method naturally extends to other constraints like freeze/2 and (in)/2, which are often implemented natively. See the system descriptions of GNU Prolog, as one example for a system that handles the CLP(FD) constraint (in)/2 natively.
For application programmers, Prolog systems typically provide an interface in the form of attributed variables are metastructures, so that any programmer can implement a custom constraint solver on top of Prolog by attaching attributes to variables. For example, in SICStus Prolog, there is a hook called verify_attributes/3, and you can provide a custom user-defined specification that handles your own constraints.
In practice, the implementation of constraint solvers on top of Prolog typically involves a combination of both approaches, with some low-level constraints implemented natively (i.e., as part of the engine), and some higher-level constraints implemented via the general interface on the Prolog level.
There are of course also other ways to do it, such as writing a Prolog meta-interpreter that interprets your code as if the Prolog engine supported such extensions. This is likely among the slower ways to provide such extensions, while a native implementation is among the fastest, and using general interface predicates will likely be somewhere between these extremes.
Further reading, you seem to do quite a bit more.
> I came across this paragraph while studying "Discrete Mathematics and It's Applications"; therefore, I got inspired to automate this task using computers.
Congrats, going from reading to an idea to a project to a public repo!