Skip to content

Commit 36978b0

Browse files
authored
Merge pull request #108 from IBM/ulvi/XOR_XNOR_half-adder_full-adder
provide XOR, XNOR, HalfAdder gates and tests
2 parents 9f97c0a + 24444b6 commit 36978b0

5 files changed

Lines changed: 252 additions & 1 deletion

File tree

examples/half_adder.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
"""Half Adder example."""
2+
3+
from p_kit.psl.gates import HalfAdder
4+
from p_kit.solver.csd_solver import CaSuDaSolver
5+
from p_kit.visualization import histplot
6+
import numpy as np
7+
8+
adder = HalfAdder()
9+
# Clamp inputs to 1+1 to demonstrate carry output
10+
adder.h[0] = 10 # input1 = 1
11+
adder.h[1] = 10 # input2 = 1
12+
13+
solver = CaSuDaSolver(Nt=10000, dt=0.1667, i0=0.9)
14+
_, output, _ = solver.solve(adder)
15+
16+
print(f"Sum output (bit 2): {np.mean(output[:, 2]):.2f}")
17+
print(f"Carry output (bit 3): {np.mean(output[:, 3]):.2f}")
18+
19+
histplot(output)

examples/xor_gate.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
"""XOR gate example."""
2+
3+
from p_kit.psl.gates import XORGate
4+
from p_kit.solver.csd_solver import CaSuDaSolver
5+
from p_kit.visualization import histplot
6+
7+
gate = XORGate()
8+
9+
solver = CaSuDaSolver(Nt=10000, dt=0.1667, i0=0.8)
10+
input_data, output, energy = solver.solve(gate)
11+
12+
histplot(output)

p_kit/psl/gates.py

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,3 +69,91 @@ class FullAdder:
6969
)
7070

7171
h = np.array([[-1], [-1], [-1], [1], [2]])
72+
73+
74+
@pcircuit(n_pbits=4)
75+
class XORGate:
76+
"""
77+
Probabilistic implementation of an XOR gate using auxiliary bit
78+
79+
Order: [input1, input2, output, aux]
80+
where aux = input1 AND input2
81+
82+
Attributes:
83+
input1 (Port): First input port
84+
input2 (Port): Second input port
85+
output (Port): XOR output port
86+
aux (Port): Auxiliary bit (internal AND)
87+
"""
88+
89+
input1 = Port("input1")
90+
input2 = Port("input2")
91+
output = Port("output")
92+
aux = Port("aux")
93+
94+
J = np.array([
95+
[0, -3, 2, 6],
96+
[-3, 0, 2, 6],
97+
[2, 2, 0, -4],
98+
[6, 6, -4, 0],
99+
])
100+
h = np.array([[3], [3], [-2], [-6]])
101+
102+
103+
@pcircuit(n_pbits=4)
104+
class XNORGate:
105+
"""
106+
Probabilistic implementation of an XNOR gate using auxiliary bit
107+
XNOR = NOT(XOR), outputs 1 when inputs match
108+
109+
Order: [input1, input2, output, aux]
110+
where aux = input1 AND input2
111+
112+
Attributes:
113+
input1 (Port): First input port
114+
input2 (Port): Second input port
115+
output (Port): XNOR output port
116+
aux (Port): Auxiliary bit (internal AND)
117+
"""
118+
119+
input1 = Port("input1")
120+
input2 = Port("input2")
121+
output = Port("output")
122+
aux = Port("aux")
123+
124+
J = np.array([
125+
[0, -3, -2, 6],
126+
[-3, 0, -2, 6],
127+
[-2, -2, 0, 4],
128+
[6, 6, 4, 0],
129+
])
130+
h = np.array([[3], [3], [2], [-6]])
131+
132+
133+
@pcircuit(n_pbits=4)
134+
class HalfAdder:
135+
"""
136+
Probabilistic implementation of a Half Adder
137+
Sum = A XOR B, Carry = A AND B
138+
139+
Order: [input1, input2, sumout, carryout]
140+
141+
Attributes:
142+
input1 (Port): First input port
143+
input2 (Port): Second input port
144+
sumout (Port): Sum output port (XOR)
145+
carryout (Port): Carry output port (AND)
146+
"""
147+
148+
input1 = Port("input1")
149+
input2 = Port("input2")
150+
sumout = Port("sumout")
151+
carryout = Port("carryout")
152+
153+
J = np.array([
154+
[0, -3, 2, 6],
155+
[-3, 0, 2, 6],
156+
[2, 2, 0, -4],
157+
[6, 6, -4, 0],
158+
])
159+
h = np.array([[3], [3], [-2], [-6]])

p_kit/solver/csd_solver.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ def solve(self, c: PCircuit, annealing_func=constant, n_shots=1):
1414
n_pbits = c.n_pbits
1515

1616
J = xp.asarray(c.J)
17-
h = xp.asarray(c.h)
17+
h = xp.asarray(c.h).flatten() # Ensure h is 1D for proper broadcasting
1818
threshold = float(np.arctanh(self.expected_mean))
1919

2020
# m is (n_shots, n_pbits) — works for n_shots=1 too

tests/test_psl.py

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -378,3 +378,135 @@ def __init__(self):
378378
J_from_sparse[i, j] = w
379379

380380
assert np.allclose(J_dense, J_from_sparse)
381+
382+
383+
384+
# ── XOR Gate Tests ────────────────────────────────────────────────────────────
385+
386+
def test_xor_gate_structure():
387+
"""Test XOR gate has correct structure."""
388+
from p_kit.psl.gates import XORGate
389+
390+
gate = XORGate()
391+
assert gate.input1.width == 1
392+
assert gate.input2.width == 1
393+
assert gate.output.width == 1
394+
assert gate.aux.width == 1
395+
assert gate.J.shape == (4, 4)
396+
assert gate.h.shape == (4, 1)
397+
398+
399+
def test_xor_gate_truth_table():
400+
"""Test XOR gate produces correct truth table with high i0."""
401+
from p_kit.psl.gates import XORGate
402+
from p_kit.solver.csd_solver import CaSuDaSolver
403+
404+
gate = XORGate()
405+
solver = CaSuDaSolver(Nt=5000, dt=0.1667, i0=0.95, seed=42)
406+
407+
test_cases = [
408+
([-1, -1], -1), # 0 XOR 0 = 0
409+
([-1, 1], 1), # 0 XOR 1 = 1
410+
([1, -1], 1), # 1 XOR 0 = 1
411+
([1, 1], -1), # 1 XOR 1 = 0
412+
]
413+
414+
for inputs, expected_output in test_cases:
415+
gate.h[0] = inputs[0] * 10
416+
gate.h[1] = inputs[1] * 10
417+
_, output, _ = solver.solve(gate)
418+
419+
# Output is at index 2 (order: input1, input2, output, aux)
420+
output_states = output[:, 2]
421+
most_common = 1 if np.mean(output_states) > 0 else -1
422+
assert most_common == expected_output, \
423+
f"XOR({inputs[0]}, {inputs[1]}) expected {expected_output}, got {most_common}"
424+
425+
426+
# ── XNOR Gate Tests ───────────────────────────────────────────────────────────
427+
428+
def test_xnor_gate_structure():
429+
"""Test XNOR gate has correct structure."""
430+
from p_kit.psl.gates import XNORGate
431+
432+
gate = XNORGate()
433+
assert gate.input1.width == 1
434+
assert gate.input2.width == 1
435+
assert gate.output.width == 1
436+
assert gate.aux.width == 1
437+
assert gate.J.shape == (4, 4)
438+
assert gate.h.shape == (4, 1)
439+
440+
441+
def test_xnor_gate_truth_table():
442+
"""Test XNOR gate produces correct truth table with high i0."""
443+
from p_kit.psl.gates import XNORGate
444+
from p_kit.solver.csd_solver import CaSuDaSolver
445+
446+
gate = XNORGate()
447+
solver = CaSuDaSolver(Nt=5000, dt=0.1667, i0=0.95, seed=42)
448+
449+
test_cases = [
450+
([-1, -1], 1), # 0 XNOR 0 = 1
451+
([-1, 1], -1), # 0 XNOR 1 = 0
452+
([1, -1], -1), # 1 XNOR 0 = 0
453+
([1, 1], 1), # 1 XNOR 1 = 1
454+
]
455+
456+
for inputs, expected_output in test_cases:
457+
gate.h[0] = inputs[0] * 10
458+
gate.h[1] = inputs[1] * 10
459+
_, output, _ = solver.solve(gate)
460+
461+
# Output is at index 2 (order: input1, input2, output, aux)
462+
output_states = output[:, 2]
463+
most_common = 1 if np.mean(output_states) > 0 else -1
464+
assert most_common == expected_output, \
465+
f"XNOR({inputs[0]}, {inputs[1]}) expected {expected_output}, got {most_common}"
466+
467+
468+
# ── Half Adder Tests ──────────────────────────────────────────────────────────
469+
470+
def test_half_adder_structure():
471+
"""Test Half Adder has correct structure."""
472+
from p_kit.psl.gates import HalfAdder
473+
474+
gate = HalfAdder()
475+
assert gate.input1.width == 1
476+
assert gate.input2.width == 1
477+
assert gate.sumout.width == 1
478+
assert gate.carryout.width == 1
479+
assert gate.J.shape == (4, 4)
480+
assert gate.h.shape == (4, 1)
481+
482+
483+
def test_half_adder_truth_table():
484+
"""Test Half Adder produces correct sum and carry outputs."""
485+
from p_kit.psl.gates import HalfAdder
486+
from p_kit.solver.csd_solver import CaSuDaSolver
487+
488+
gate = HalfAdder()
489+
solver = CaSuDaSolver(Nt=5000, dt=0.1667, i0=0.95, seed=42)
490+
491+
test_cases = [
492+
([-1, -1], -1, -1),
493+
([-1, 1], 1, -1),
494+
([1, -1], 1, -1),
495+
([1, 1], -1, 1),
496+
]
497+
498+
for inputs, expected_sum, expected_carry in test_cases:
499+
gate.h[0] = inputs[0] * 10
500+
gate.h[1] = inputs[1] * 10
501+
_, output, _ = solver.solve(gate)
502+
503+
sum_states = output[:, 2]
504+
carry_states = output[:, 3]
505+
506+
sum_result = 1 if np.mean(sum_states) > 0 else -1
507+
carry_result = 1 if np.mean(carry_states) > 0 else -1
508+
509+
assert sum_result == expected_sum
510+
assert carry_result == expected_carry
511+
512+

0 commit comments

Comments
 (0)