% The 2-inverter puzzle.
%
% The problem is to build a combinational circuit with 3 inputs
% and 3 outputs, such that the outputs negate the inputs; we can
% use as many AND and OR gates as we wish, but at most 2 NOT gates.
%
% The clause P(function, inversion_list) represents a wire, whose
% state is a function of the inputs, and which depends on negated
% wires listed in inversion_list.
%
% The initial clauses are
%
%   P([0,0,0,0,1,1,1,1], v).  % input 1
%   P([0,0,1,1,0,0,1,1], v).  % input 2
%   P([0,1,0,1,0,1,0,1], v).  % input 3
%
% which represent the three input wires. The goal formula is
%
%   exists v (P([1,1,1,1,0,0,0,0], v) & 
%             P([1,1,0,0,1,1,0,0], v) & 
%             P([1,0,1,0,1,0,1,0], v)).
%
% That is, the three output functions with unifiable (consistent) inversion lists.
%
% The inversion lists are tricky: each has a variable as its tail, which
% means that two lists unify iff on is an initial sublist of the other.
% Two wires can be input to an OR or AND gate if their inversion lists
% are unifiable.  (Note this means that the second NOT gate must depend
% on the first.)
%
% For example, P(01000110,[11111000,11001110|x]) means that we
% can acheive the function 01000110 with a circuit in which 11111000 and
% 11001110 are inverted.
%
% If a proof is found, the corresponding circuit can be constructed by
% going through the proof; each step represents a gate, and the parent
% lists show how to connect the wires.
%
% The inversion lists are due to Steve Winker and is documented in
% "Automated Reasoning: Introduction and Applications" by Wos et al.
% This formulation does not use the "reversion" heuristic of Winker's
% formulation.
%

set(production).
assign(max_weight, 55).  % to eliminate functions with more than 2 inversions

formulas(usable).

% Rules for building circuits.
-P(x, v) | -P(y, v) | P(bit_and(x,y), v).
-P(x, v) | -P(y, v) | P(bit_or(x,y), v).
-P(x, v) | P(bit_not(x), append_inversion(v,x)).
end_of_list.

formulas(assumptions).
P([0,0,0,0,1,1,1,1], v).  % input 1
P([0,0,1,1,0,0,1,1], v).  % input 2
P([0,1,0,1,0,1,0,1], v).  % input 3
end_of_list.

formulas(goals).
exists v (P([1,1,1,1,0,0,0,0], v) & 
          P([1,1,0,0,1,1,0,0], v) & 
          P([1,0,1,0,1,0,1,0], v)).
end_of_list.

formulas(demodulators).

bit_and([],[]) = [].
bit_and([1:y1],[x:y2]) = [x:bit_and(y1,y2)].
bit_and([x:y1],[1:y2]) = [x:bit_and(y1,y2)].
bit_and([0:y1],[x:y2]) = [0:bit_and(y1,y2)].
bit_and([x:y1],[0:y2]) = [0:bit_and(y1,y2)].

bit_or([],[]) = [].
bit_or([1:y1],[x:y2]) = [1:bit_or(y1,y2)].
bit_or([x:y1],[1:y2]) = [1:bit_or(y1,y2)].
bit_or([0:y1],[x:y2]) = [x:bit_or(y1,y2)].
bit_or([x:y1],[0:y2]) = [x:bit_or(y1,y2)].

bit_not([]) = [].
bit_not([0:y]) = [1:bit_not(y)].
bit_not([1:y]) = [0:bit_not(y)].

append_inversion([x1:x2],y) = [x1:append_inversion(x2,y)].
variable(x) -> append_inversion(x,y) = [y:x].

end_of_list.

% The following causes components of potential solutions to be used immediately.

list(weights).
weight(P([1,1,1,1,0,0,0,0], v)) = 0.
weight(P([1,1,0,0,1,1,0,0], v)) = 0.
weight(P([1,0,1,0,1,0,1,0], v)) = 0.
end_of_list.

