Skip to content

Commit bc2f8a4

Browse files
committed
added pytests for contract_symmetric_tree, contract_with_grad, and light_cone_size, for both cpp and jax backend. also added p=16 data for k5D8 and k6D7
1 parent 12bab55 commit bc2f8a4

3 files changed

Lines changed: 312 additions & 0 deletions

File tree

qokit/max_k_xor_sat/assets/results_k5_D8.json

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -407,6 +407,49 @@
407407
"num_evals": 1,
408408
"converged": true,
409409
"seed_source": "output_file:results_k5_D8.json(p=15)"
410+
},
411+
"16": {
412+
"gammas": [
413+
-0.09056819891993806,
414+
-0.14172691865214565,
415+
-0.14658176042945625,
416+
-0.1465607141757614,
417+
-0.1445141696186937,
418+
-0.1428999835284535,
419+
-0.1414685959129644,
420+
-0.1407944579823877,
421+
-0.14066289088756392,
422+
-0.14122289740251667,
423+
-0.1431272752955805,
424+
-0.14708477930408226,
425+
-0.15453395725093766,
426+
-0.16648260632709164,
427+
-0.189725577175512,
428+
-0.21638680434914986
429+
],
430+
"betas": [
431+
0.2034406390402451,
432+
0.16485106880917832,
433+
0.15847468224412037,
434+
0.15872876655574017,
435+
0.160949977723424,
436+
0.16331024424572105,
437+
0.16509458089420115,
438+
0.1659071598901084,
439+
0.1659342157665853,
440+
0.16496951027796408,
441+
0.16253532283148903,
442+
0.15680548407106587,
443+
0.14758377418462945,
444+
0.1293033644714071,
445+
0.10137051856474051,
446+
0.04570476680188127
447+
],
448+
"expectation": -0.6450751148644556,
449+
"objective": 0.8225375574322278,
450+
"num_evals": 1,
451+
"converged": true,
452+
"seed_source": "output_file:results_k5_D8.json(p=14,15->scaled)"
410453
}
411454
}
412455
}

qokit/max_k_xor_sat/assets/results_k6_D7.json

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -407,6 +407,49 @@
407407
"num_evals": 1,
408408
"converged": true,
409409
"seed_source": "output_file:results_k6_D7.json(p=15)"
410+
},
411+
"16": {
412+
"gammas": [
413+
-0.10601684916178886,
414+
-0.15894252361983813,
415+
-0.1619866123630878,
416+
-0.1600476491546099,
417+
-0.15651070115779628,
418+
-0.15388542688473567,
419+
-0.15195652838539336,
420+
-0.1509291608055398,
421+
-0.15047995708398604,
422+
-0.15078927664516345,
423+
-0.15230032285663794,
424+
-0.15606627172787887,
425+
-0.1628587662519251,
426+
-0.17578421129069632,
427+
-0.19878284735909824,
428+
-0.2305797657086402
429+
],
430+
"betas": [
431+
0.16603176345021828,
432+
0.13629616579306514,
433+
0.1336887731908258,
434+
0.13573851015792854,
435+
0.1389322893163718,
436+
0.14190691282747625,
437+
0.14404270092108787,
438+
0.14540039787562328,
439+
0.1461798054461892,
440+
0.14608977200770568,
441+
0.14500395890980752,
442+
0.14115347654124905,
443+
0.13488599163534243,
444+
0.11999049852838882,
445+
0.09668273705456024,
446+
0.042534398218459765
447+
],
448+
"expectation": -0.7091301891001267,
449+
"objective": 0.8545650945500634,
450+
"num_evals": 1,
451+
"converged": true,
452+
"seed_source": "output_file:results_k6_D7.json(p=14,15->scaled)"
410453
}
411454
}
412455
}

tests/test_max_k_xor_sat.py

Lines changed: 226 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -308,3 +308,229 @@ def test_seed_loading():
308308
assert len(gammas) == 5
309309
assert len(betas) == 5
310310
assert "results" in source
311+
312+
313+
# ============================================================
314+
# FD helper for gradient accuracy tests
315+
# ============================================================
316+
317+
_GRAD_CASES = [(2, 3, 1), (2, 3, 2), (3, 2, 1), (3, 3, 1)]
318+
319+
320+
def _fd_grad(contract_fn, gammas, betas, p, D, k=2, h=1e-5):
321+
"""Numerical central finite-difference gradient of contract_fn."""
322+
gammas = np.asarray(gammas, dtype=float)
323+
betas = np.asarray(betas, dtype=float)
324+
grad_g = np.empty(p)
325+
grad_b = np.empty(p)
326+
for i in range(p):
327+
g_p = gammas.copy()
328+
g_p[i] += h
329+
g_m = gammas.copy()
330+
g_m[i] -= h
331+
grad_g[i] = (contract_fn(g_p, betas, p, D, k) - contract_fn(g_m, betas, p, D, k)) / (2 * h)
332+
for i in range(p):
333+
b_p = betas.copy()
334+
b_p[i] += h
335+
b_m = betas.copy()
336+
b_m[i] -= h
337+
grad_b[i] = (contract_fn(gammas, b_p, p, D, k) - contract_fn(gammas, b_m, p, D, k)) / (2 * h)
338+
return grad_g, grad_b
339+
340+
341+
# ============================================================
342+
# contract.py dispatcher tests
343+
# ============================================================
344+
345+
346+
@pytest.mark.skipif(not (HAS_CPP or HAS_JAX), reason="No backend available")
347+
@pytest.mark.parametrize("k,D,p", CASES[:6])
348+
def test_dispatcher_contract_symmetric_tree(k, D, p):
349+
"""Dispatcher contract_symmetric_tree auto-selects a backend and returns a valid float."""
350+
from qokit.max_k_xor_sat.contract import contract_symmetric_tree as disp_contract
351+
352+
gammas, betas = _params(p)
353+
val = disp_contract(gammas, betas, p, D, k)
354+
assert isinstance(val, float)
355+
assert -1.0 <= val <= 1.0
356+
357+
358+
@pytest.mark.skipif(not HAS_CPP, reason="C++ backend not built")
359+
def test_dispatcher_contract_symmetric_tree_cpp():
360+
"""Dispatcher backend='cpp' matches cpp_backend directly."""
361+
from qokit.max_k_xor_sat.contract import contract_symmetric_tree as disp_contract
362+
363+
gammas, betas = _params(2)
364+
val_disp = disp_contract(gammas, betas, 2, 3, k=2, backend="cpp")
365+
val_cpp = cpp_contract(gammas, betas, 2, 3, 2)
366+
assert abs(val_disp - val_cpp) < 1e-12
367+
368+
369+
@pytest.mark.skipif(not HAS_JAX, reason="JAX not installed")
370+
def test_dispatcher_contract_symmetric_tree_jax():
371+
"""Dispatcher backend='jax' matches jax backend directly."""
372+
from qokit.max_k_xor_sat.contract import contract_symmetric_tree as disp_contract
373+
374+
gammas, betas = _params(2)
375+
val_disp = disp_contract(gammas, betas, 2, 3, k=2, backend="jax")
376+
val_jax = jax_contract(gammas, betas, 2, 3, 2)
377+
assert abs(val_disp - val_jax) < 1e-12
378+
379+
380+
def test_dispatcher_jax_dd_raises():
381+
"""Dispatcher raises ValueError when backend='jax' + precision='dd' requested."""
382+
from qokit.max_k_xor_sat.contract import contract_symmetric_tree as disp_contract
383+
384+
gammas, betas = _params(2)
385+
with pytest.raises(ValueError, match="double-double"):
386+
disp_contract(gammas, betas, 2, 3, k=2, precision="dd", backend="jax")
387+
388+
389+
@pytest.mark.skipif(not (HAS_CPP or HAS_JAX), reason="No backend available")
390+
@pytest.mark.parametrize("k,D,p", _GRAD_CASES)
391+
def test_dispatcher_contract_with_grad_shape(k, D, p):
392+
"""Dispatcher contract_with_grad returns (float, array(p,), array(p,))."""
393+
from qokit.max_k_xor_sat.contract import contract_with_grad as disp_grad
394+
395+
gammas, betas = _params(p)
396+
val, dg, db = disp_grad(gammas, betas, p, D, k)
397+
assert isinstance(val, float)
398+
assert np.asarray(dg).shape == (p,)
399+
assert np.asarray(db).shape == (p,)
400+
401+
402+
@pytest.mark.skipif(not (HAS_CPP or HAS_JAX), reason="No backend available")
403+
@pytest.mark.parametrize("k,D,p", _GRAD_CASES)
404+
def test_dispatcher_contract_with_grad_value(k, D, p):
405+
"""Dispatcher contract_with_grad value matches contract_symmetric_tree."""
406+
from qokit.max_k_xor_sat.contract import (
407+
contract_symmetric_tree as disp_contract,
408+
contract_with_grad as disp_grad,
409+
)
410+
411+
gammas, betas = _params(p)
412+
val_tree = disp_contract(gammas, betas, p, D, k)
413+
val_grad, _, _ = disp_grad(gammas, betas, p, D, k)
414+
assert abs(val_tree - val_grad) < 1e-8, f"k={k} D={D} p={p}: tree={val_tree} grad={val_grad}"
415+
416+
417+
@pytest.mark.skipif(not (HAS_CPP or HAS_JAX), reason="No backend available")
418+
@pytest.mark.parametrize("k,D,p", _GRAD_CASES)
419+
def test_dispatcher_contract_with_grad_accuracy(k, D, p):
420+
"""Dispatcher contract_with_grad gradient matches finite differences."""
421+
from qokit.max_k_xor_sat.contract import (
422+
contract_symmetric_tree as disp_contract,
423+
contract_with_grad as disp_grad,
424+
)
425+
426+
gammas, betas = _params(p)
427+
_, dg, db = disp_grad(gammas, betas, p, D, k)
428+
ref_dg, ref_db = _fd_grad(disp_contract, gammas, betas, p, D, k)
429+
np.testing.assert_allclose(dg, ref_dg, atol=1e-4, rtol=1e-4, err_msg=f"gamma grad k={k} D={D} p={p}")
430+
np.testing.assert_allclose(db, ref_db, atol=1e-4, rtol=1e-4, err_msg=f"beta grad k={k} D={D} p={p}")
431+
432+
433+
# ============================================================
434+
# cpp_backend.py additional tests
435+
# ============================================================
436+
437+
438+
@pytest.mark.skipif(not HAS_CPP, reason="C++ backend not built")
439+
@pytest.mark.parametrize("k,D,p", [(2, 3, 1), (2, 3, 2), (3, 2, 1), (2, 2, 3)])
440+
def test_cpp_light_cone_size(k, D, p):
441+
"""cpp_backend light_cone_size matches reference formula."""
442+
from qokit.max_k_xor_sat.cpp_backend import light_cone_size as cpp_lcs
443+
from qokit.max_k_xor_sat.contract import light_cone_size as ref_lcs
444+
445+
assert cpp_lcs(p, D, k) == ref_lcs(p, D, k), f"k={k} D={D} p={p}"
446+
447+
448+
@pytest.mark.skipif(not HAS_CPP, reason="C++ backend not built")
449+
@pytest.mark.parametrize("k,D,p", _GRAD_CASES)
450+
def test_cpp_contract_with_grad_shape(k, D, p):
451+
"""cpp_backend contract_with_grad returns (float, array(p,), array(p,))."""
452+
from qokit.max_k_xor_sat.cpp_backend import contract_with_grad as cpp_grad
453+
454+
gammas, betas = _params(p)
455+
val, dg, db = cpp_grad(gammas, betas, p, D, k)
456+
assert isinstance(val, float)
457+
assert np.asarray(dg).shape == (p,)
458+
assert np.asarray(db).shape == (p,)
459+
460+
461+
@pytest.mark.skipif(not HAS_CPP, reason="C++ backend not built")
462+
@pytest.mark.parametrize("k,D,p", _GRAD_CASES)
463+
def test_cpp_contract_with_grad_value(k, D, p):
464+
"""cpp_backend contract_with_grad value matches contract_symmetric_tree."""
465+
from qokit.max_k_xor_sat.cpp_backend import contract_with_grad as cpp_grad
466+
467+
gammas, betas = _params(p)
468+
val_tree = cpp_contract(gammas, betas, p, D, k)
469+
val_grad, _, _ = cpp_grad(gammas, betas, p, D, k)
470+
assert abs(val_tree - val_grad) < 1e-8, f"k={k} D={D} p={p}: tree={val_tree} grad={val_grad}"
471+
472+
473+
@pytest.mark.skipif(not HAS_CPP, reason="C++ backend not built")
474+
@pytest.mark.parametrize("k,D,p", _GRAD_CASES)
475+
def test_cpp_grad_accuracy(k, D, p):
476+
"""cpp_backend gradient matches finite differences."""
477+
from qokit.max_k_xor_sat.cpp_backend import contract_with_grad as cpp_grad
478+
479+
gammas, betas = _params(p)
480+
_, dg, db = cpp_grad(gammas, betas, p, D, k)
481+
ref_dg, ref_db = _fd_grad(cpp_contract, gammas, betas, p, D, k)
482+
np.testing.assert_allclose(dg, ref_dg, atol=1e-4, rtol=1e-4, err_msg=f"gamma grad k={k} D={D} p={p}")
483+
np.testing.assert_allclose(db, ref_db, atol=1e-4, rtol=1e-4, err_msg=f"beta grad k={k} D={D} p={p}")
484+
485+
486+
# ============================================================
487+
# JAX backend additional tests
488+
# ============================================================
489+
490+
491+
@pytest.mark.skipif(not HAS_JAX, reason="JAX not installed")
492+
@pytest.mark.parametrize("k,D,p", [(2, 3, 1), (2, 3, 2), (3, 2, 1), (2, 2, 3)])
493+
def test_jax_light_cone_size(k, D, p):
494+
"""JAX backend light_cone_size matches reference formula."""
495+
from qokit.max_k_xor_sat.jax.contract import light_cone_size as jax_lcs
496+
from qokit.max_k_xor_sat.contract import light_cone_size as ref_lcs
497+
498+
assert jax_lcs(p, D, k) == ref_lcs(p, D, k), f"k={k} D={D} p={p}"
499+
500+
501+
@pytest.mark.skipif(not HAS_JAX, reason="JAX not installed")
502+
@pytest.mark.parametrize("k,D,p", _GRAD_CASES)
503+
def test_jax_contract_with_grad_shape(k, D, p):
504+
"""JAX backend contract_with_grad returns (float, array(p,), array(p,))."""
505+
from qokit.max_k_xor_sat.jax import contract_with_grad as jax_grad
506+
507+
gammas, betas = _params(p)
508+
val, dg, db = jax_grad(gammas, betas, p, D, k)
509+
assert isinstance(val, float)
510+
assert np.asarray(dg).shape == (p,)
511+
assert np.asarray(db).shape == (p,)
512+
513+
514+
@pytest.mark.skipif(not HAS_JAX, reason="JAX not installed")
515+
@pytest.mark.parametrize("k,D,p", _GRAD_CASES)
516+
def test_jax_contract_with_grad_value(k, D, p):
517+
"""JAX backend contract_with_grad value matches contract_symmetric_tree."""
518+
from qokit.max_k_xor_sat.jax import contract_with_grad as jax_grad
519+
520+
gammas, betas = _params(p)
521+
val_tree = jax_contract(gammas, betas, p, D, k)
522+
val_grad, _, _ = jax_grad(gammas, betas, p, D, k)
523+
assert abs(val_tree - val_grad) < 1e-8, f"k={k} D={D} p={p}: tree={val_tree} grad={val_grad}"
524+
525+
526+
@pytest.mark.skipif(not HAS_JAX, reason="JAX not installed")
527+
@pytest.mark.parametrize("k,D,p", _GRAD_CASES)
528+
def test_jax_grad_accuracy(k, D, p):
529+
"""JAX backend gradient matches finite differences."""
530+
from qokit.max_k_xor_sat.jax import contract_with_grad as jax_grad
531+
532+
gammas, betas = _params(p)
533+
_, dg, db = jax_grad(gammas, betas, p, D, k)
534+
ref_dg, ref_db = _fd_grad(jax_contract, gammas, betas, p, D, k)
535+
np.testing.assert_allclose(dg, ref_dg, atol=1e-4, rtol=1e-4, err_msg=f"gamma grad k={k} D={D} p={p}")
536+
np.testing.assert_allclose(db, ref_db, atol=1e-4, rtol=1e-4, err_msg=f"beta grad k={k} D={D} p={p}")

0 commit comments

Comments
 (0)