From 24444b6d1f6f083ac08009b1e617ace55a7f98e3 Mon Sep 17 00:00:00 2001 From: Ulvi Movsum-zada Date: Tue, 9 Jun 2026 18:30:48 +0200 Subject: [PATCH] provide XOR, XNOR, HalfAdder gates and tests --- examples/half_adder.py | 19 ++++++ examples/xor_gate.py | 12 ++++ p_kit/psl/gates.py | 88 +++++++++++++++++++++++++ p_kit/solver/csd_solver.py | 2 +- tests/test_psl.py | 132 +++++++++++++++++++++++++++++++++++++ 5 files changed, 252 insertions(+), 1 deletion(-) create mode 100644 examples/half_adder.py create mode 100644 examples/xor_gate.py diff --git a/examples/half_adder.py b/examples/half_adder.py new file mode 100644 index 0000000..e0bc6ef --- /dev/null +++ b/examples/half_adder.py @@ -0,0 +1,19 @@ +"""Half Adder example.""" + +from p_kit.psl.gates import HalfAdder +from p_kit.solver.csd_solver import CaSuDaSolver +from p_kit.visualization import histplot +import numpy as np + +adder = HalfAdder() +# Clamp inputs to 1+1 to demonstrate carry output +adder.h[0] = 10 # input1 = 1 +adder.h[1] = 10 # input2 = 1 + +solver = CaSuDaSolver(Nt=10000, dt=0.1667, i0=0.9) +_, output, _ = solver.solve(adder) + +print(f"Sum output (bit 2): {np.mean(output[:, 2]):.2f}") +print(f"Carry output (bit 3): {np.mean(output[:, 3]):.2f}") + +histplot(output) diff --git a/examples/xor_gate.py b/examples/xor_gate.py new file mode 100644 index 0000000..8f1d685 --- /dev/null +++ b/examples/xor_gate.py @@ -0,0 +1,12 @@ +"""XOR gate example.""" + +from p_kit.psl.gates import XORGate +from p_kit.solver.csd_solver import CaSuDaSolver +from p_kit.visualization import histplot + +gate = XORGate() + +solver = CaSuDaSolver(Nt=10000, dt=0.1667, i0=0.8) +input_data, output, energy = solver.solve(gate) + +histplot(output) diff --git a/p_kit/psl/gates.py b/p_kit/psl/gates.py index dddd632..818c4ae 100644 --- a/p_kit/psl/gates.py +++ b/p_kit/psl/gates.py @@ -69,3 +69,91 @@ class FullAdder: ) h = np.array([[-1], [-1], [-1], [1], [2]]) + + +@pcircuit(n_pbits=4) +class XORGate: + """ + Probabilistic implementation of an XOR gate using auxiliary bit + + Order: [input1, input2, output, aux] + where aux = input1 AND input2 + + Attributes: + input1 (Port): First input port + input2 (Port): Second input port + output (Port): XOR output port + aux (Port): Auxiliary bit (internal AND) + """ + + input1 = Port("input1") + input2 = Port("input2") + output = Port("output") + aux = Port("aux") + + J = np.array([ + [0, -3, 2, 6], + [-3, 0, 2, 6], + [2, 2, 0, -4], + [6, 6, -4, 0], + ]) + h = np.array([[3], [3], [-2], [-6]]) + + +@pcircuit(n_pbits=4) +class XNORGate: + """ + Probabilistic implementation of an XNOR gate using auxiliary bit + XNOR = NOT(XOR), outputs 1 when inputs match + + Order: [input1, input2, output, aux] + where aux = input1 AND input2 + + Attributes: + input1 (Port): First input port + input2 (Port): Second input port + output (Port): XNOR output port + aux (Port): Auxiliary bit (internal AND) + """ + + input1 = Port("input1") + input2 = Port("input2") + output = Port("output") + aux = Port("aux") + + J = np.array([ + [0, -3, -2, 6], + [-3, 0, -2, 6], + [-2, -2, 0, 4], + [6, 6, 4, 0], + ]) + h = np.array([[3], [3], [2], [-6]]) + + +@pcircuit(n_pbits=4) +class HalfAdder: + """ + Probabilistic implementation of a Half Adder + Sum = A XOR B, Carry = A AND B + + Order: [input1, input2, sumout, carryout] + + Attributes: + input1 (Port): First input port + input2 (Port): Second input port + sumout (Port): Sum output port (XOR) + carryout (Port): Carry output port (AND) + """ + + input1 = Port("input1") + input2 = Port("input2") + sumout = Port("sumout") + carryout = Port("carryout") + + J = np.array([ + [0, -3, 2, 6], + [-3, 0, 2, 6], + [2, 2, 0, -4], + [6, 6, -4, 0], + ]) + h = np.array([[3], [3], [-2], [-6]]) diff --git a/p_kit/solver/csd_solver.py b/p_kit/solver/csd_solver.py index 5caa8cf..1bb46a6 100644 --- a/p_kit/solver/csd_solver.py +++ b/p_kit/solver/csd_solver.py @@ -14,7 +14,7 @@ def solve(self, c: PCircuit, annealing_func=constant, n_shots=1): n_pbits = c.n_pbits J = xp.asarray(c.J) - h = xp.asarray(c.h) + h = xp.asarray(c.h).flatten() # Ensure h is 1D for proper broadcasting threshold = float(np.arctanh(self.expected_mean)) # m is (n_shots, n_pbits) — works for n_shots=1 too diff --git a/tests/test_psl.py b/tests/test_psl.py index 3f346b9..d714142 100644 --- a/tests/test_psl.py +++ b/tests/test_psl.py @@ -378,3 +378,135 @@ def __init__(self): J_from_sparse[i, j] = w assert np.allclose(J_dense, J_from_sparse) + + + +# ── XOR Gate Tests ──────────────────────────────────────────────────────────── + +def test_xor_gate_structure(): + """Test XOR gate has correct structure.""" + from p_kit.psl.gates import XORGate + + gate = XORGate() + assert gate.input1.width == 1 + assert gate.input2.width == 1 + assert gate.output.width == 1 + assert gate.aux.width == 1 + assert gate.J.shape == (4, 4) + assert gate.h.shape == (4, 1) + + +def test_xor_gate_truth_table(): + """Test XOR gate produces correct truth table with high i0.""" + from p_kit.psl.gates import XORGate + from p_kit.solver.csd_solver import CaSuDaSolver + + gate = XORGate() + solver = CaSuDaSolver(Nt=5000, dt=0.1667, i0=0.95, seed=42) + + test_cases = [ + ([-1, -1], -1), # 0 XOR 0 = 0 + ([-1, 1], 1), # 0 XOR 1 = 1 + ([1, -1], 1), # 1 XOR 0 = 1 + ([1, 1], -1), # 1 XOR 1 = 0 + ] + + for inputs, expected_output in test_cases: + gate.h[0] = inputs[0] * 10 + gate.h[1] = inputs[1] * 10 + _, output, _ = solver.solve(gate) + + # Output is at index 2 (order: input1, input2, output, aux) + output_states = output[:, 2] + most_common = 1 if np.mean(output_states) > 0 else -1 + assert most_common == expected_output, \ + f"XOR({inputs[0]}, {inputs[1]}) expected {expected_output}, got {most_common}" + + +# ── XNOR Gate Tests ─────────────────────────────────────────────────────────── + +def test_xnor_gate_structure(): + """Test XNOR gate has correct structure.""" + from p_kit.psl.gates import XNORGate + + gate = XNORGate() + assert gate.input1.width == 1 + assert gate.input2.width == 1 + assert gate.output.width == 1 + assert gate.aux.width == 1 + assert gate.J.shape == (4, 4) + assert gate.h.shape == (4, 1) + + +def test_xnor_gate_truth_table(): + """Test XNOR gate produces correct truth table with high i0.""" + from p_kit.psl.gates import XNORGate + from p_kit.solver.csd_solver import CaSuDaSolver + + gate = XNORGate() + solver = CaSuDaSolver(Nt=5000, dt=0.1667, i0=0.95, seed=42) + + test_cases = [ + ([-1, -1], 1), # 0 XNOR 0 = 1 + ([-1, 1], -1), # 0 XNOR 1 = 0 + ([1, -1], -1), # 1 XNOR 0 = 0 + ([1, 1], 1), # 1 XNOR 1 = 1 + ] + + for inputs, expected_output in test_cases: + gate.h[0] = inputs[0] * 10 + gate.h[1] = inputs[1] * 10 + _, output, _ = solver.solve(gate) + + # Output is at index 2 (order: input1, input2, output, aux) + output_states = output[:, 2] + most_common = 1 if np.mean(output_states) > 0 else -1 + assert most_common == expected_output, \ + f"XNOR({inputs[0]}, {inputs[1]}) expected {expected_output}, got {most_common}" + + +# ── Half Adder Tests ────────────────────────────────────────────────────────── + +def test_half_adder_structure(): + """Test Half Adder has correct structure.""" + from p_kit.psl.gates import HalfAdder + + gate = HalfAdder() + assert gate.input1.width == 1 + assert gate.input2.width == 1 + assert gate.sumout.width == 1 + assert gate.carryout.width == 1 + assert gate.J.shape == (4, 4) + assert gate.h.shape == (4, 1) + + +def test_half_adder_truth_table(): + """Test Half Adder produces correct sum and carry outputs.""" + from p_kit.psl.gates import HalfAdder + from p_kit.solver.csd_solver import CaSuDaSolver + + gate = HalfAdder() + solver = CaSuDaSolver(Nt=5000, dt=0.1667, i0=0.95, seed=42) + + test_cases = [ + ([-1, -1], -1, -1), + ([-1, 1], 1, -1), + ([1, -1], 1, -1), + ([1, 1], -1, 1), + ] + + for inputs, expected_sum, expected_carry in test_cases: + gate.h[0] = inputs[0] * 10 + gate.h[1] = inputs[1] * 10 + _, output, _ = solver.solve(gate) + + sum_states = output[:, 2] + carry_states = output[:, 3] + + sum_result = 1 if np.mean(sum_states) > 0 else -1 + carry_result = 1 if np.mean(carry_states) > 0 else -1 + + assert sum_result == expected_sum + assert carry_result == expected_carry + +