use crate::error::{QuantumError, Result};
use moonlab_sys::{
graph_add_edge, graph_create, graph_free, graph_t,
ising_encode_maxcut, ising_model_create, ising_model_evaluate,
ising_model_free, ising_model_set_coupling, ising_model_set_field,
ising_model_t, qaoa_compute_expectation, qaoa_result_t, qaoa_solve,
qaoa_solver_create, qaoa_solver_free, qaoa_solver_t,
quantum_entropy_ctx_create_hw, quantum_entropy_ctx_destroy,
quantum_entropy_ctx_t,
};
use std::ptr;
struct EntropyGuard {
ctx: *mut quantum_entropy_ctx_t,
}
impl EntropyGuard {
fn new() -> Result<Self> {
let ctx = unsafe { quantum_entropy_ctx_create_hw() };
if ctx.is_null() {
return Err(QuantumError::Ffi(
"quantum_entropy_ctx_create_hw returned NULL".to_string(),
));
}
Ok(Self { ctx })
}
}
impl Drop for EntropyGuard {
fn drop(&mut self) {
if !self.ctx.is_null() {
unsafe { quantum_entropy_ctx_destroy(self.ctx) };
self.ctx = ptr::null_mut();
}
}
}
pub struct Graph {
ptr: *mut graph_t,
}
impl Graph {
pub fn new(num_vertices: usize, edges: &[(usize, usize, f64)]) -> Result<Self> {
let ptr = unsafe { graph_create(num_vertices, edges.len()) };
if ptr.is_null() {
return Err(QuantumError::Ffi(
"graph_create returned NULL".to_string(),
));
}
for (idx, &(u, v, w)) in edges.iter().enumerate() {
let rc = unsafe { graph_add_edge(ptr, idx, u as i32, v as i32, w) };
if rc != 0 {
unsafe { graph_free(ptr) };
return Err(QuantumError::Ffi(format!(
"graph_add_edge(idx={idx}, u={u}, v={v}) rc={rc}"
)));
}
}
Ok(Self { ptr })
}
pub(crate) fn as_ptr(&self) -> *const graph_t {
self.ptr
}
}
impl Drop for Graph {
fn drop(&mut self) {
if !self.ptr.is_null() {
unsafe { graph_free(self.ptr) };
self.ptr = ptr::null_mut();
}
}
}
pub struct IsingModel {
ptr: *mut ising_model_t,
}
impl IsingModel {
pub fn new(num_qubits: usize) -> Result<Self> {
let ptr = unsafe { ising_model_create(num_qubits) };
if ptr.is_null() {
return Err(QuantumError::Ffi(
"ising_model_create returned NULL".to_string(),
));
}
Ok(Self { ptr })
}
pub fn from_maxcut(graph: &Graph) -> Result<Self> {
let ptr = unsafe { ising_encode_maxcut(graph.as_ptr()) };
if ptr.is_null() {
return Err(QuantumError::Ffi(
"ising_encode_maxcut returned NULL".to_string(),
));
}
Ok(Self { ptr })
}
pub fn set_coupling(&mut self, i: usize, j: usize, value: f64) -> Result<()> {
let rc = unsafe { ising_model_set_coupling(self.ptr, i, j, value) };
if rc != 0 {
return Err(QuantumError::Ffi(format!(
"ising_model_set_coupling({i},{j}) rc={rc}"
)));
}
Ok(())
}
pub fn set_field(&mut self, i: usize, value: f64) -> Result<()> {
let rc = unsafe { ising_model_set_field(self.ptr, i, value) };
if rc != 0 {
return Err(QuantumError::Ffi(format!(
"ising_model_set_field({i}) rc={rc}"
)));
}
Ok(())
}
pub fn evaluate(&self, bitstring: u64) -> f64 {
unsafe { ising_model_evaluate(self.ptr, bitstring) }
}
}
impl Drop for IsingModel {
fn drop(&mut self) {
if !self.ptr.is_null() {
unsafe { ising_model_free(self.ptr) };
self.ptr = ptr::null_mut();
}
}
}
#[derive(Debug, Clone)]
pub struct QaoaResult {
pub best_bitstring: u64,
pub best_energy: f64,
pub num_iterations: usize,
pub converged: bool,
pub approximation_ratio: f64,
pub optimal_gamma: Vec<f64>,
pub optimal_beta: Vec<f64>,
pub num_layers: usize,
pub total_measurements: usize,
}
pub struct QaoaSolver {
solver: *mut qaoa_solver_t,
ising: IsingModel,
_entropy: EntropyGuard,
}
impl QaoaSolver {
pub fn new(ising: IsingModel, num_layers: usize) -> Result<Self> {
if num_layers == 0 {
return Err(QuantumError::Ffi(
"num_layers must be >= 1".to_string(),
));
}
let entropy = EntropyGuard::new()?;
let solver = unsafe {
qaoa_solver_create(ising.ptr, num_layers, entropy.ctx)
};
if solver.is_null() {
return Err(QuantumError::Ffi(
"qaoa_solver_create returned NULL".to_string(),
));
}
Ok(Self {
solver,
ising,
_entropy: entropy,
})
}
pub fn num_qubits(&self) -> usize {
unsafe { (*self.ising.ptr).num_qubits }
}
pub fn solve(&mut self) -> Result<QaoaResult> {
let r: qaoa_result_t = unsafe { qaoa_solve(self.solver) };
let n = r.num_layers;
let gamma = if r.optimal_gamma.is_null() || n == 0 {
Vec::new()
} else {
unsafe { std::slice::from_raw_parts(r.optimal_gamma, n).to_vec() }
};
let beta = if r.optimal_beta.is_null() || n == 0 {
Vec::new()
} else {
unsafe { std::slice::from_raw_parts(r.optimal_beta, n).to_vec() }
};
Ok(QaoaResult {
best_bitstring: r.best_bitstring,
best_energy: r.best_energy,
num_iterations: r.num_iterations,
converged: r.converged != 0,
approximation_ratio: r.approximation_ratio,
optimal_gamma: gamma,
optimal_beta: beta,
num_layers: n,
total_measurements: r.total_measurements,
})
}
pub fn compute_expectation(&self, gamma: &[f64], beta: &[f64]) -> f64 {
unsafe {
qaoa_compute_expectation(
self.solver,
gamma.as_ptr(),
beta.as_ptr(),
)
}
}
}
impl Drop for QaoaSolver {
fn drop(&mut self) {
if !self.solver.is_null() {
unsafe { qaoa_solver_free(self.solver) };
self.solver = ptr::null_mut();
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn graph_rejects_invalid_edge_index() {
let bad = Graph::new(3, &[(0, 5, 1.0)]); assert!(bad.is_err());
}
#[test]
fn ising_evaluate_returns_finite_energy_on_triangle() {
let g = Graph::new(3, &[(0, 1, 1.0), (1, 2, 1.0), (2, 0, 1.0)]).unwrap();
let ising = IsingModel::from_maxcut(&g).unwrap();
let e_000 = ising.evaluate(0b000);
let e_101 = ising.evaluate(0b101);
assert!(
e_000 > e_101,
"all-zeros (no cut, E={e_000}) should have higher energy than one-flipped (cut, E={e_101})"
);
assert!(e_101.is_finite());
}
#[test]
fn qaoa_solver_runs_on_triangle_maxcut() {
let g = Graph::new(
3,
&[(0, 1, 1.0), (1, 2, 1.0), (2, 0, 1.0)],
)
.unwrap();
let ising = IsingModel::from_maxcut(&g).unwrap();
let mut solver = QaoaSolver::new(ising, 1).unwrap();
let result = solver.solve().unwrap();
assert_eq!(result.num_layers, 1);
assert_eq!(result.optimal_gamma.len(), 1);
assert_eq!(result.optimal_beta.len(), 1);
assert!(result.best_energy.is_finite());
}
#[test]
fn compute_expectation_returns_finite() {
let g = Graph::new(2, &[(0, 1, 1.0)]).unwrap();
let ising = IsingModel::from_maxcut(&g).unwrap();
let solver = QaoaSolver::new(ising, 1).unwrap();
let e = solver.compute_expectation(&[0.3], &[0.5]);
assert!(e.is_finite());
}
}