#![expect(
unused_results,
clippy::expect_used,
reason = "test helpers - builder results and panics on failure are intentional"
)]
use xqvm::bytecode::{InstructionBuilder, Register};
use xqvm::{Domain, Error, RegVal, Vm};
fn run(build: impl FnOnce(&mut InstructionBuilder)) -> Vm {
let mut b = InstructionBuilder::new();
build(&mut b);
let bytecode = b.build().expect("builder build");
let mut vm = Vm::new();
vm.run(&bytecode).expect("vm run");
vm
}
fn run_err(build: impl FnOnce(&mut InstructionBuilder)) -> Error {
let mut b = InstructionBuilder::new();
build(&mut b);
let bytecode = b.build().expect("builder build");
let mut vm = Vm::new();
vm.run(&bytecode).expect_err("expected error")
}
#[test]
fn add_two_numbers() {
let vm = run(|b| {
b.emit_push(3).emit_push(4).emit_add().emit_halt();
});
assert_eq!(vm.stack(), &[7]);
}
#[test]
fn sub_two_numbers() {
let vm = run(|b| {
b.emit_push(10).emit_push(3).emit_sub().emit_halt();
});
assert_eq!(vm.stack(), &[7]);
}
#[test]
fn mul_two_numbers() {
let vm = run(|b| {
b.emit_push(6).emit_push(7).emit_mul().emit_halt();
});
assert_eq!(vm.stack(), &[42]);
}
#[test]
fn div_two_numbers() {
let vm = run(|b| {
b.emit_push(21).emit_push(3).emit_div().emit_halt();
});
assert_eq!(vm.stack(), &[7]);
}
#[test]
fn div_positive_operands_uses_floor() {
let vm = run(|b| {
b.emit_push(7).emit_push(2).emit_div().emit_halt();
});
assert_eq!(vm.stack(), &[3]);
}
#[test]
fn modulo_basic() {
let vm = run(|b| {
b.emit_push(17).emit_push(5).emit_modulo().emit_halt();
});
assert_eq!(vm.stack(), &[2]);
}
#[test]
fn neg_positive() {
let vm = run(|b| {
b.emit_push(42).emit_neg().emit_halt();
});
assert_eq!(vm.stack(), &[-42]);
}
#[test]
fn neg_negative() {
let vm = run(|b| {
b.emit_push(-7).emit_neg().emit_halt();
});
assert_eq!(vm.stack(), &[7]);
}
#[test]
fn wrapping_add_overflow() {
let vm = run(|b| {
b.emit_push(i64::MAX).emit_push(1).emit_add().emit_halt();
});
assert_eq!(vm.stack(), &[i64::MIN]);
}
#[test]
fn eq_equal() {
let vm = run(|b| {
b.emit_push(5).emit_push(5).emit_eq().emit_halt();
});
assert_eq!(vm.stack(), &[1]);
}
#[test]
fn eq_not_equal() {
let vm = run(|b| {
b.emit_push(5).emit_push(6).emit_eq().emit_halt();
});
assert_eq!(vm.stack(), &[0]);
}
#[test]
fn lt_true() {
let vm = run(|b| {
b.emit_push(3).emit_push(5).emit_lt().emit_halt();
});
assert_eq!(vm.stack(), &[1]);
}
#[test]
fn lt_false() {
let vm = run(|b| {
b.emit_push(5).emit_push(3).emit_lt().emit_halt();
});
assert_eq!(vm.stack(), &[0]);
}
#[test]
fn gt_true() {
let vm = run(|b| {
b.emit_push(5).emit_push(3).emit_gt().emit_halt();
});
assert_eq!(vm.stack(), &[1]);
}
#[test]
fn lte_equal() {
let vm = run(|b| {
b.emit_push(5).emit_push(5).emit_lte().emit_halt();
});
assert_eq!(vm.stack(), &[1]);
}
#[test]
fn gte_greater() {
let vm = run(|b| {
b.emit_push(6).emit_push(5).emit_gte().emit_halt();
});
assert_eq!(vm.stack(), &[1]);
}
#[test]
fn not_zero() {
let vm = run(|b| {
b.emit_push(0).emit_not().emit_halt();
});
assert_eq!(vm.stack(), &[1]);
}
#[test]
fn not_nonzero() {
let vm = run(|b| {
b.emit_push(99).emit_not().emit_halt();
});
assert_eq!(vm.stack(), &[0]);
}
#[test]
fn and_both_nonzero() {
let vm = run(|b| {
b.emit_push(1).emit_push(1).emit_and().emit_halt();
});
assert_eq!(vm.stack(), &[1]);
}
#[test]
fn and_one_zero() {
let vm = run(|b| {
b.emit_push(1).emit_push(0).emit_and().emit_halt();
});
assert_eq!(vm.stack(), &[0]);
}
#[test]
fn or_one_nonzero() {
let vm = run(|b| {
b.emit_push(0).emit_push(1).emit_or().emit_halt();
});
assert_eq!(vm.stack(), &[1]);
}
#[test]
fn xor_different() {
let vm = run(|b| {
b.emit_push(1).emit_push(0).emit_xor().emit_halt();
});
assert_eq!(vm.stack(), &[1]);
}
#[test]
fn xor_same() {
let vm = run(|b| {
b.emit_push(1).emit_push(1).emit_xor().emit_halt();
});
assert_eq!(vm.stack(), &[0]);
}
#[test]
fn band_basic() {
let vm = run(|b| {
b.emit_push(0b1100)
.emit_push(0b1010)
.emit_b_and()
.emit_halt();
});
assert_eq!(vm.stack(), &[0b1000]);
}
#[test]
fn bor_basic() {
let vm = run(|b| {
b.emit_push(0b1100)
.emit_push(0b1010)
.emit_b_or()
.emit_halt();
});
assert_eq!(vm.stack(), &[0b1110]);
}
#[test]
fn bxor_basic() {
let vm = run(|b| {
b.emit_push(0b1100)
.emit_push(0b1010)
.emit_b_xor()
.emit_halt();
});
assert_eq!(vm.stack(), &[0b0110]);
}
#[test]
fn bnot_basic() {
let vm = run(|b| {
b.emit_push(0i64).emit_b_not().emit_halt();
});
assert_eq!(vm.stack(), &[-1i64]);
}
#[test]
fn shl_basic() {
let vm = run(|b| {
b.emit_push(1).emit_push(4).emit_shl().emit_halt();
});
assert_eq!(vm.stack(), &[16]);
}
#[test]
fn shr_basic() {
let vm = run(|b| {
b.emit_push(16i64).emit_push(2).emit_shr().emit_halt();
});
assert_eq!(vm.stack(), &[4]);
}
#[test]
fn shr_preserves_sign_on_negative() {
let vm = run(|b| {
b.emit_push(-1i64).emit_push(1).emit_shr().emit_halt();
});
assert_eq!(vm.stack(), &[-1]);
}
#[test]
fn shr_arithmetic_negative_value() {
let vm = run(|b| {
b.emit_push(-8i64).emit_push(1).emit_shr().emit_halt();
});
assert_eq!(vm.stack(), &[-4]);
}
#[test]
fn shr_i64_min_does_not_overflow() {
let vm = run(|b| {
b.emit_push(i64::MIN).emit_push(1).emit_shr().emit_halt();
});
assert_eq!(vm.stack(), &[i64::MIN / 2]);
}
#[test]
fn push_pop() {
let vm = run(|b| {
b.emit_push(42).emit_pop().emit_halt();
});
assert!(vm.stack().is_empty());
}
#[test]
fn copy_duplicates_top() {
let vm = run(|b| {
b.emit_push(7).emit_copy().emit_halt();
});
assert_eq!(vm.stack(), &[7, 7]);
}
#[test]
fn swap_swaps_top_two() {
let vm = run(|b| {
b.emit_push(1).emit_push(2).emit_swap().emit_halt();
});
assert_eq!(vm.stack(), &[2, 1]);
}
#[test]
fn stow_and_load() {
let vm = run(|b| {
b.emit_push(99)
.emit_stow(Register(0))
.emit_load(Register(0))
.emit_halt();
});
assert_eq!(vm.stack(), &[99]);
}
#[test]
fn load_on_never_written_register_faults() {
let err = run_err(|b| {
b.emit_load(Register(5)).emit_halt();
});
assert!(
matches!(err, Error::UnsetRegister { reg: 5, .. }),
"expected UnsetRegister for reg 5, got {err:?}",
);
}
#[test]
fn unconditional_jump_skips_code() {
let mut b = InstructionBuilder::new();
let skip = b.label();
b.emit_push(1)
.emit_jump(skip)
.emit_push(99)
.place(skip)
.unwrap()
.emit_push(2)
.emit_halt();
let bytecode = b.build().unwrap();
let mut vm = Vm::new();
vm.run(&bytecode).unwrap();
assert_eq!(vm.stack(), &[1, 2]);
}
#[test]
fn conditional_jump_taken() {
let mut b = InstructionBuilder::new();
let done = b.label();
b.emit_push(1)
.emit_jump_if(done)
.emit_push(99)
.place(done)
.unwrap()
.emit_push(2)
.emit_halt();
let bytecode = b.build().unwrap();
let mut vm = Vm::new();
vm.run(&bytecode).unwrap();
assert_eq!(vm.stack(), &[2]);
}
#[test]
fn conditional_jump_not_taken() {
let mut b = InstructionBuilder::new();
let done = b.label();
b.emit_push(0)
.emit_jump_if(done)
.emit_push(99)
.place(done)
.unwrap()
.emit_push(2)
.emit_halt();
let bytecode = b.build().unwrap();
let mut vm = Vm::new();
vm.run(&bytecode).unwrap();
assert_eq!(vm.stack(), &[99, 2]);
}
#[test]
fn range_loop_sum_0_to_5() {
let mut b = InstructionBuilder::new();
b.emit_push(0).emit_stow(Register(0)); b.emit_push(0).emit_push(5).emit_range(); b.emit_l_val(Register(1)); b.emit_load(Register(1))
.emit_load(Register(0))
.emit_add()
.emit_stow(Register(0)); b.emit_next();
b.emit_load(Register(0)).emit_halt();
let bytecode = b.build().unwrap();
let mut vm = Vm::new();
vm.run(&bytecode).unwrap();
assert_eq!(vm.stack(), &[10]);
}
#[test]
fn range_loop_zero_count_skips_body() {
let mut b = InstructionBuilder::new();
b.emit_push(0).emit_push(0).emit_range();
b.emit_push(99); b.emit_next();
b.emit_halt();
let bytecode = b.build().unwrap();
let mut vm = Vm::new();
vm.run(&bytecode).unwrap();
assert_eq!(vm.stack(), &[]);
}
#[test]
fn iter_loop_over_vec_int() {
let mut b = InstructionBuilder::new();
b.emit_vec_i(Register(0)); b.emit_push(10).emit_vec_push(Register(0));
b.emit_push(20).emit_vec_push(Register(0));
b.emit_push(30).emit_vec_push(Register(0));
b.emit_push(0).emit_stow(Register(1)); b.emit_push(0).emit_push(3).emit_iter(Register(0)); b.emit_l_val(Register(2)); b.emit_load(Register(2))
.emit_load(Register(1))
.emit_add()
.emit_stow(Register(1));
b.emit_next();
b.emit_load(Register(1)).emit_halt();
let bytecode = b.build().unwrap();
let mut vm = Vm::new();
vm.run(&bytecode).unwrap();
assert_eq!(vm.stack(), &[60]);
}
#[test]
fn iter_loop_over_slice_skips_outside_range() {
let vm = run(|b| {
b.emit_vec_i(Register(0));
for v in [10, 20, 30, 40, 50] {
b.emit_push(v).emit_vec_push(Register(0));
}
b.emit_push(0).emit_stow(Register(1));
b.emit_push(1).emit_push(4).emit_iter(Register(0));
b.emit_l_val(Register(2));
b.emit_load(Register(2))
.emit_load(Register(1))
.emit_add()
.emit_stow(Register(1));
b.emit_next();
b.emit_load(Register(1)).emit_halt();
});
assert_eq!(vm.stack(), &[20 + 30 + 40]);
}
#[test]
fn iter_lval_uses_slice_copy_not_source_vec() {
let vm = run(|b| {
b.emit_vec_i(Register(0));
b.emit_push(10).emit_vec_push(Register(0));
b.emit_push(20).emit_vec_push(Register(0));
b.emit_push(0).emit_push(2).emit_iter(Register(0));
b.emit_l_val(Register(1));
b.emit_load(Register(1));
b.emit_push(0).emit_push(999).emit_vec_set(Register(0));
b.emit_next();
b.emit_halt();
});
assert_eq!(vm.stack(), &[10, 20]);
}
#[test]
fn iter_rejects_negative_start() {
let err = run_err(|b| {
b.emit_vec_i(Register(0));
b.emit_push(1).emit_vec_push(Register(0));
b.emit_push(-1).emit_push(1).emit_iter(Register(0));
b.emit_next().emit_halt();
});
assert!(
matches!(err, Error::IndexOutOfBounds { index: -1, .. }),
"expected IndexOutOfBounds with index=-1, got {err:?}"
);
}
#[test]
fn iter_rejects_end_past_len() {
let err = run_err(|b| {
b.emit_vec_i(Register(0));
b.emit_push(1).emit_vec_push(Register(0));
b.emit_push(0).emit_push(5).emit_iter(Register(0));
b.emit_next().emit_halt();
});
assert!(
matches!(err, Error::IndexOutOfBounds { index: 5, .. }),
"expected IndexOutOfBounds with index=5, got {err:?}"
);
}
#[test]
fn iter_rejects_inverted_range() {
let err = run_err(|b| {
b.emit_vec_i(Register(0));
b.emit_push(1).emit_vec_push(Register(0));
b.emit_push(2).emit_vec_push(Register(0));
b.emit_push(2).emit_push(1).emit_iter(Register(0));
b.emit_next().emit_halt();
});
assert!(
matches!(err, Error::IndexOutOfBounds { index: 1, .. }),
"expected IndexOutOfBounds rejecting inverted range, got {err:?}"
);
}
#[test]
fn lidx_in_range_loop_returns_current_value() {
let vm = run(|b| {
b.emit_push(5).emit_push(3).emit_range();
b.emit_lidx(Register(0));
b.emit_load(Register(0));
b.emit_next();
b.emit_halt();
});
assert_eq!(vm.stack(), &[5, 6, 7]);
}
#[test]
fn lidx_matches_lval_in_range_loop() {
let vm = run(|b| {
b.emit_push(10).emit_push(3).emit_range();
b.emit_lidx(Register(0));
b.emit_l_val(Register(1));
b.emit_load(Register(0));
b.emit_load(Register(1));
b.emit_next();
b.emit_halt();
});
assert_eq!(vm.stack(), &[10, 10, 11, 11, 12, 12]);
}
#[test]
fn lidx_in_iter_loop_returns_position() {
let vm = run(|b| {
b.emit_vec_i(Register(0));
b.emit_push(100).emit_vec_push(Register(0));
b.emit_push(200).emit_vec_push(Register(0));
b.emit_push(300).emit_vec_push(Register(0));
b.emit_push(0).emit_push(3).emit_iter(Register(0));
b.emit_lidx(Register(1));
b.emit_load(Register(1));
b.emit_next();
b.emit_halt();
});
assert_eq!(vm.stack(), &[0, 1, 2]);
}
#[test]
fn lidx_in_iter_slice_reports_absolute_position() {
let vm = run(|b| {
b.emit_vec_i(Register(0));
for v in [10, 20, 30, 40, 50] {
b.emit_push(v).emit_vec_push(Register(0));
}
b.emit_push(2).emit_push(5).emit_iter(Register(0));
b.emit_lidx(Register(1));
b.emit_load(Register(1));
b.emit_next();
b.emit_halt();
});
assert_eq!(vm.stack(), &[2, 3, 4]);
}
#[test]
fn lidx_outside_loop_errors() {
let err = run_err(|b| {
b.emit_lidx(Register(0));
b.emit_halt();
});
assert!(
matches!(err, Error::NoActiveLoop { .. }),
"expected NoActiveLoop, got {err:?}"
);
}
#[test]
fn vec_push_get_set_len() {
let vm = run(|b| {
b.emit_vec_i(Register(0));
b.emit_push(100).emit_vec_push(Register(0));
b.emit_push(200).emit_vec_push(Register(0));
b.emit_push(300).emit_vec_push(Register(0));
b.emit_vec_len(Register(0));
b.emit_push(1).emit_vec_get(Register(0));
b.emit_push(0).emit_push(999).emit_vec_set(Register(0));
b.emit_push(0).emit_vec_get(Register(0));
b.emit_halt();
});
assert_eq!(vm.stack(), &[3, 200, 999]);
}
#[test]
fn bqmx_setline_getline() {
let vm = run(|b| {
b.emit_push(4).emit_bqmx(Register(0)); b.emit_push(2).emit_push(7).emit_set_line(Register(0));
b.emit_push(2).emit_get_line(Register(0));
b.emit_halt();
});
assert_eq!(vm.stack(), &[7]);
}
#[test]
fn bqmx_addline() {
let vm = run(|b| {
b.emit_push(4).emit_bqmx(Register(0));
b.emit_push(0).emit_push(5).emit_set_line(Register(0));
b.emit_push(0).emit_push(3).emit_add_line(Register(0));
b.emit_push(0).emit_get_line(Register(0));
b.emit_halt();
});
assert_eq!(vm.stack(), &[8]);
}
#[test]
fn bqmx_setquad_getquad() {
let vm = run(|b| {
b.emit_push(4).emit_bqmx(Register(0));
b.emit_push(1)
.emit_push(2)
.emit_push(-1)
.emit_set_quad(Register(0));
b.emit_push(2).emit_push(1).emit_get_quad(Register(0));
b.emit_halt();
});
assert_eq!(vm.stack(), &[-1]);
}
#[test]
fn bqmx_addquad() {
let vm = run(|b| {
b.emit_push(4).emit_bqmx(Register(0));
b.emit_push(0)
.emit_push(1)
.emit_push(3)
.emit_set_quad(Register(0));
b.emit_push(0)
.emit_push(1)
.emit_push(2)
.emit_add_quad(Register(0));
b.emit_push(1).emit_push(0).emit_get_quad(Register(0));
b.emit_halt();
});
assert_eq!(vm.stack(), &[5]);
}
#[test]
fn getline_absent_returns_zero() {
let vm = run(|b| {
b.emit_push(4).emit_bqmx(Register(0));
b.emit_push(3).emit_get_line(Register(0));
b.emit_halt();
});
assert_eq!(vm.stack(), &[0]);
}
#[test]
fn energy_simple_qubo_all_zero_sample() {
let vm = run(|b| {
b.emit_push(2).emit_bqmx(Register(0));
b.emit_push(0).emit_push(-1).emit_set_line(Register(0));
b.emit_push(0)
.emit_push(1)
.emit_push(2)
.emit_set_quad(Register(0));
b.emit_push(2).emit_bsmx(Register(1));
b.emit_energy(Register(0), Register(1));
b.emit_halt();
});
assert_eq!(vm.stack(), &[0]);
}
#[test]
fn energy_spin_ising_all_minus_one() {
let vm = run(|b| {
b.emit_push(2).emit_sqmx(Register(0));
b.emit_push(0)
.emit_push(1)
.emit_push(1)
.emit_set_quad(Register(0));
b.emit_push(2).emit_ssmx(Register(1));
b.emit_energy(Register(0), Register(1));
b.emit_halt();
});
assert_eq!(vm.stack(), &[1]);
}
#[test]
fn energy_rejects_model_in_sample_slot() {
let err = run_err(|b| {
b.emit_push(2).emit_bqmx(Register(0));
b.emit_push(2).emit_bqmx(Register(1));
b.emit_energy(Register(0), Register(1));
b.emit_halt();
});
match err {
Error::RegisterType {
reg: 1,
expected: "sample",
got: "model",
} => {}
other => panic!(
"expected RegisterType {{ expected: \"sample\", got: \"model\" }}, got {other:?}"
),
}
}
#[test]
fn energy_rejects_int_in_sample_slot() {
let err = run_err(|b| {
b.emit_push(2).emit_bqmx(Register(0));
b.emit_push(0).emit_stow(Register(1));
b.emit_energy(Register(0), Register(1));
b.emit_halt();
});
assert!(
matches!(
err,
Error::RegisterType {
reg: 1,
expected: "sample",
..
}
),
"expected RegisterType expected=sample, got {err:?}"
);
}
#[test]
fn input_output_roundtrip() {
let mut b = InstructionBuilder::new();
b.emit_push(0).emit_input(Register(0));
b.emit_push(0).emit_output(Register(0));
b.emit_halt();
let bytecode = b.build().unwrap();
let mut vm = Vm::new();
vm.set_calldata(vec![RegVal::Int(42)]).set_output_slots(4);
vm.run(&bytecode).unwrap();
assert_eq!(vm.outputs()[0], RegVal::Int(42));
}
#[test]
fn idx_grid_basic() {
let vm = run(|b| {
b.emit_push(1)
.emit_push(2)
.emit_push(4)
.emit_idx_grid()
.emit_halt();
});
assert_eq!(vm.stack(), &[6]);
}
#[test]
fn idx_triu_basic() {
let vm = run(|b| {
b.emit_push(1).emit_push(3).emit_idx_triu().emit_halt();
});
assert_eq!(vm.stack(), &[4]);
}
#[test]
fn stack_underflow_on_pop() {
let err = run_err(|b| {
b.emit_pop().emit_halt();
});
assert!(matches!(err, Error::StackUnderflow { .. }));
}
#[test]
fn stack_underflow_on_add() {
let err = run_err(|b| {
b.emit_push(1).emit_add().emit_halt();
});
assert!(matches!(err, Error::StackUnderflow { .. }));
}
#[test]
fn division_by_zero() {
let err = run_err(|b| {
b.emit_push(5).emit_push(0).emit_div().emit_halt();
});
assert!(matches!(err, Error::DivisionByZero { .. }));
}
#[test]
fn modulo_by_zero() {
let err = run_err(|b| {
b.emit_push(5).emit_push(0).emit_modulo().emit_halt();
});
assert!(matches!(err, Error::DivisionByZero { .. }));
}
#[test]
fn step_limit_exceeded() {
let mut b = InstructionBuilder::new();
let top = b.label();
b.place(top)
.unwrap()
.emit_push(1)
.emit_jump_if(top)
.emit_halt();
let bytecode = b.build().unwrap();
let mut vm = Vm::new();
vm.set_step_limit(100);
let err = vm.run(&bytecode).expect_err("expected step limit error");
assert!(matches!(err, Error::StepLimitExceeded { .. }));
}
#[test]
fn invalid_shift_negative() {
let err = run_err(|b| {
b.emit_push(1).emit_push(-1).emit_shl().emit_halt();
});
assert!(matches!(err, Error::InvalidShift { .. }));
}
#[test]
fn invalid_shift_too_large() {
let err = run_err(|b| {
b.emit_push(1).emit_push(64).emit_shl().emit_halt();
});
assert!(matches!(err, Error::InvalidShift { .. }));
}
#[test]
fn register_type_error_load_on_model() {
let err = run_err(|b| {
b.emit_push(4).emit_bqmx(Register(0));
b.emit_load(Register(0)); b.emit_halt();
});
assert!(matches!(err, Error::RegisterType { .. }));
}
#[test]
fn no_active_loop_next() {
let err = run_err(|b| {
b.emit_next(); b.emit_halt();
});
assert!(matches!(err, Error::NoActiveLoop { .. }));
}
#[test]
fn index_out_of_bounds_vec_get() {
let err = run_err(|b| {
b.emit_vec_i(Register(0));
b.emit_push(10).emit_vec_push(Register(0));
b.emit_push(5).emit_vec_get(Register(0)); b.emit_halt();
});
assert!(matches!(err, Error::IndexOutOfBounds { .. }));
}
#[test]
fn one_hot_constraint_adds_coefficients() {
let vm = run(|b| {
b.emit_push(3).emit_bqmx(Register(0));
b.emit_push(1).emit_push(3).emit_resize(Register(0));
b.emit_push(0).emit_push(1).emit_one_hot_r(Register(0));
b.emit_halt();
});
let reg = vm.register(0);
if let RegVal::Model(m) = reg {
assert_eq!(m.get_linear(0), -1);
assert_eq!(m.get_linear(1), -1);
assert_eq!(m.get_linear(2), -1);
assert_eq!(m.get_quad(0, 1), 2);
assert_eq!(m.get_quad(0, 2), 2);
assert_eq!(m.get_quad(1, 2), 2);
} else {
panic!("expected model register");
}
}
#[test]
fn exclude_constraint_adds_coupling() {
let vm = run(|b| {
b.emit_push(4).emit_bqmx(Register(0));
b.emit_push(1)
.emit_push(2)
.emit_push(5)
.emit_exclude(Register(0));
b.emit_halt();
});
let reg = vm.register(0);
if let RegVal::Model(m) = reg {
assert_eq!(m.get_quad(1, 2), 5);
} else {
panic!("expected model register");
}
}
#[test]
fn implies_constraint_adds_linear_and_coupling() {
let vm = run(|b| {
b.emit_push(4).emit_bqmx(Register(0));
b.emit_push(0)
.emit_push(1)
.emit_push(3)
.emit_implies(Register(0));
b.emit_halt();
});
let reg = vm.register(0);
if let RegVal::Model(m) = reg {
assert_eq!(m.get_linear(0), 3);
assert_eq!(m.get_quad(0, 1), -3);
} else {
panic!("expected model register");
}
}
#[test]
fn nop_does_nothing() {
let vm = run(|b| {
b.emit_push(42).emit_nop().emit_nop().emit_halt();
});
assert_eq!(vm.stack(), &[42]);
}
#[test]
fn halt_at_end_of_bytecode() {
let vm = run(|b| {
b.emit_push(1).emit_push(2).emit_add();
});
assert_eq!(vm.stack(), &[3]);
}
#[test]
fn reset_clears_state() {
let mut b = InstructionBuilder::new();
b.emit_push(99).emit_halt();
let bytecode = b.build().unwrap();
let mut vm = Vm::new();
vm.run(&bytecode).unwrap();
assert_eq!(vm.stack(), &[99]);
vm.reset();
assert!(vm.stack().is_empty());
}
#[test]
fn xqmx_discrete_model() {
let vm = run(|b| {
b.emit_push(2).emit_push(3).emit_xqmx(Register(0)); b.emit_halt();
});
let reg = vm.register(0);
if let RegVal::Model(m) = reg {
assert_eq!(m.domain, Domain::Discrete(3));
assert_eq!(m.size, 2);
} else {
panic!("expected model register");
}
}
#[test]
fn xqmx_minimum_k_is_two() {
let vm = run(|b| {
b.emit_push(4).emit_push(2).emit_xqmx(Register(0));
b.emit_halt();
});
if let RegVal::Model(m) = vm.register(0) {
assert_eq!(m.domain, Domain::Discrete(2));
assert_eq!(m.size, 4);
} else {
panic!("expected model register");
}
}
#[test]
fn xqmx_rejects_k_one() {
let err = run_err(|b| {
b.emit_push(2).emit_push(1).emit_xqmx(Register(0));
b.emit_halt();
});
assert!(
matches!(err, Error::InvalidDiscreteK { k: 1, .. }),
"expected InvalidDiscreteK, got {err:?}"
);
}
#[test]
fn xqmx_rejects_zero_k() {
let err = run_err(|b| {
b.emit_push(2).emit_push(0).emit_xqmx(Register(0));
b.emit_halt();
});
assert!(
matches!(err, Error::InvalidDiscreteK { k: 0, .. }),
"expected InvalidDiscreteK, got {err:?}"
);
}
#[test]
fn xqmx_rejects_negative_k() {
let err = run_err(|b| {
b.emit_push(2).emit_push(-3).emit_xqmx(Register(0));
b.emit_halt();
});
assert!(
matches!(err, Error::InvalidDiscreteK { k: -3, .. }),
"expected InvalidDiscreteK, got {err:?}"
);
}
#[test]
fn xsmx_allocates_discrete_sample() {
let vm = run(|b| {
b.emit_push(3).emit_push(4).emit_xsmx(Register(0));
b.emit_halt();
});
if let RegVal::Sample(s) = vm.register(0) {
assert_eq!(s.domain, Domain::Discrete(4));
assert_eq!(s.values, vec![0, 0, 0]);
} else {
panic!("expected sample register");
}
}
#[test]
fn xsmx_rejects_k_below_two() {
let err = run_err(|b| {
b.emit_push(3).emit_push(1).emit_xsmx(Register(0));
b.emit_halt();
});
assert!(
matches!(err, Error::InvalidDiscreteK { k: 1, .. }),
"expected InvalidDiscreteK, got {err:?}"
);
}
#[test]
fn resize_sets_grid_dims() {
let vm = run(|b| {
b.emit_push(9).emit_bqmx(Register(0));
b.emit_push(3).emit_push(3).emit_resize(Register(0));
b.emit_halt();
});
let reg = vm.register(0);
if let RegVal::Model(m) = reg {
assert_eq!(m.rows, 3);
assert_eq!(m.cols, 3);
} else {
panic!("expected model register");
}
}
#[test]
fn row_sum_and_col_sum() {
let vm = run(|b| {
b.emit_push(4).emit_bqmx(Register(0));
b.emit_push(2).emit_push(2).emit_resize(Register(0));
b.emit_push(0).emit_push(1).emit_set_line(Register(0));
b.emit_push(1).emit_push(2).emit_set_line(Register(0));
b.emit_push(2).emit_push(3).emit_set_line(Register(0));
b.emit_push(3).emit_push(4).emit_set_line(Register(0));
b.emit_push(0).emit_row_sum(Register(0));
b.emit_push(1).emit_col_sum(Register(0));
b.emit_halt();
});
assert_eq!(vm.stack(), &[3, 6]);
}
#[test]
fn row_find_and_col_find() {
let vm = run(|b| {
b.emit_push(4).emit_bqmx(Register(0));
b.emit_push(2).emit_push(2).emit_resize(Register(0));
b.emit_push(0).emit_push(10).emit_set_line(Register(0));
b.emit_push(1).emit_push(20).emit_set_line(Register(0));
b.emit_push(2).emit_push(30).emit_set_line(Register(0));
b.emit_push(3).emit_push(10).emit_set_line(Register(0));
b.emit_push(0).emit_push(20).emit_row_find(Register(0));
b.emit_push(1).emit_push(10).emit_col_find(Register(0));
b.emit_halt();
});
assert_eq!(vm.stack(), &[1, 1]);
}
#[test]
fn drop_marks_register_unset() {
let vm = run(|b| {
b.emit_push(42)
.emit_stow(Register(3))
.emit_drop(Register(3))
.emit_halt();
});
assert_eq!(vm.register(3), &RegVal::Unset);
}
#[test]
fn sclr_clears_entire_stack() {
let vm = run(|b| {
b.emit_push(1)
.emit_push(2)
.emit_push(3)
.emit_sclr()
.emit_halt();
});
assert!(vm.stack().is_empty());
}
#[test]
fn sqr_squares_top() {
let vm = run(|b| {
b.emit_push(7).emit_sqr().emit_halt();
});
assert_eq!(vm.stack(), &[49]);
}
#[test]
fn abs_positive_unchanged() {
let vm = run(|b| {
b.emit_push(5).emit_abs().emit_halt();
});
assert_eq!(vm.stack(), &[5]);
}
#[test]
fn abs_negative_becomes_positive() {
let vm = run(|b| {
b.emit_push(-9).emit_abs().emit_halt();
});
assert_eq!(vm.stack(), &[9]);
}
#[test]
fn min_returns_smaller() {
let vm = run(|b| {
b.emit_push(10).emit_push(3).emit_min().emit_halt();
});
assert_eq!(vm.stack(), &[3]);
}
#[test]
fn max_returns_larger() {
let vm = run(|b| {
b.emit_push(10).emit_push(3).emit_max().emit_halt();
});
assert_eq!(vm.stack(), &[10]);
}
#[test]
fn inc_adds_one() {
let vm = run(|b| {
b.emit_push(41).emit_inc().emit_halt();
});
assert_eq!(vm.stack(), &[42]);
}
#[test]
fn dec_subtracts_one() {
let vm = run(|b| {
b.emit_push(43).emit_dec().emit_halt();
});
assert_eq!(vm.stack(), &[42]);
}
#[test]
fn one_hot_c_applies_column_constraint() {
let vm = run(|b| {
b.emit_push(4).emit_bqmx(Register(0));
b.emit_push(2).emit_push(2).emit_resize(Register(0));
b.emit_push(0).emit_push(1).emit_one_hot_c(Register(0));
b.emit_halt();
});
if let RegVal::Model(m) = vm.register(0) {
assert_eq!(m.get_linear(0), -1);
assert_eq!(m.get_linear(2), -1);
assert_eq!(m.get_quad(0, 2), 2);
} else {
panic!("r0 should be a model");
}
}
#[test]
fn stack_overflow_at_8192() {
let err = run_err(|b| {
b.emit_push(0).emit_push(8193).emit_range();
b.emit_push(0); b.emit_next().emit_halt();
});
assert!(matches!(err, Error::StackOverflow { .. }));
}