use wasm_bindgen::prelude::*;
use serde::{Serialize, Deserialize};
const WASM_MAX_QUBITS: u32 = 25;
#[wasm_bindgen]
pub struct WasmQuantumCircuit {
inner: ruqu_core::circuit::QuantumCircuit,
}
#[wasm_bindgen]
impl WasmQuantumCircuit {
#[wasm_bindgen(constructor)]
pub fn new(num_qubits: u32) -> Result<WasmQuantumCircuit, JsValue> {
if num_qubits > WASM_MAX_QUBITS {
return Err(JsValue::from_str(&format!(
"Qubit limit exceeded: {} requested, max {} in WASM",
num_qubits, WASM_MAX_QUBITS
)));
}
Ok(Self {
inner: ruqu_core::circuit::QuantumCircuit::new(num_qubits),
})
}
pub fn h(&mut self, qubit: u32) {
self.inner.h(qubit);
}
pub fn x(&mut self, qubit: u32) {
self.inner.x(qubit);
}
pub fn y(&mut self, qubit: u32) {
self.inner.y(qubit);
}
pub fn z(&mut self, qubit: u32) {
self.inner.z(qubit);
}
pub fn s(&mut self, qubit: u32) {
self.inner.s(qubit);
}
pub fn t(&mut self, qubit: u32) {
self.inner.t(qubit);
}
pub fn rx(&mut self, qubit: u32, angle: f64) {
self.inner.rx(qubit, angle);
}
pub fn ry(&mut self, qubit: u32, angle: f64) {
self.inner.ry(qubit, angle);
}
pub fn rz(&mut self, qubit: u32, angle: f64) {
self.inner.rz(qubit, angle);
}
pub fn cnot(&mut self, control: u32, target: u32) {
self.inner.cnot(control, target);
}
pub fn cz(&mut self, q1: u32, q2: u32) {
self.inner.cz(q1, q2);
}
pub fn swap(&mut self, q1: u32, q2: u32) {
self.inner.swap(q1, q2);
}
pub fn rzz(&mut self, q1: u32, q2: u32, angle: f64) {
self.inner.rzz(q1, q2, angle);
}
pub fn measure(&mut self, qubit: u32) {
self.inner.measure(qubit);
}
pub fn measure_all(&mut self) {
self.inner.measure_all();
}
pub fn reset(&mut self, qubit: u32) {
self.inner.reset(qubit);
}
pub fn barrier(&mut self) {
self.inner.barrier();
}
#[wasm_bindgen(getter)]
pub fn num_qubits(&self) -> u32 {
self.inner.num_qubits()
}
#[wasm_bindgen(getter)]
pub fn gate_count(&self) -> usize {
self.inner.gate_count()
}
#[wasm_bindgen(getter)]
pub fn depth(&self) -> u32 {
self.inner.depth()
}
}
#[derive(Serialize, Deserialize)]
pub struct WasmSimResult {
pub probabilities: Vec<f64>,
pub measurements: Vec<WasmMeasurement>,
pub num_qubits: u32,
pub gate_count: usize,
pub execution_time_ms: f64,
}
#[derive(Serialize, Deserialize)]
pub struct WasmMeasurement {
pub qubit: u32,
pub result: bool,
pub probability: f64,
}
#[wasm_bindgen]
pub fn simulate(circuit: &WasmQuantumCircuit) -> Result<JsValue, JsValue> {
let result = ruqu_core::simulator::Simulator::run(&circuit.inner)
.map_err(|e| JsValue::from_str(&e.to_string()))?;
let wasm_result = WasmSimResult {
probabilities: result.state.probabilities(),
measurements: result
.measurements
.iter()
.map(|m| WasmMeasurement {
qubit: m.qubit,
result: m.result,
probability: m.probability,
})
.collect(),
num_qubits: result.metrics.num_qubits,
gate_count: result.metrics.gate_count,
execution_time_ms: result.metrics.execution_time_ns as f64 / 1_000_000.0,
};
serde_wasm_bindgen::to_value(&wasm_result)
.map_err(|e| JsValue::from_str(&e.to_string()))
}
#[wasm_bindgen]
pub fn estimate_memory(num_qubits: u32) -> usize {
ruqu_core::state::QuantumState::estimate_memory(num_qubits)
}
#[wasm_bindgen]
pub fn max_qubits() -> u32 {
WASM_MAX_QUBITS
}
#[wasm_bindgen]
pub fn grover_search(
num_qubits: u32,
target_states: Vec<u32>,
seed: JsValue,
) -> Result<JsValue, JsValue> {
if num_qubits > WASM_MAX_QUBITS {
return Err(JsValue::from_str(&format!(
"Qubit limit exceeded: {} requested, max {} in WASM",
num_qubits, WASM_MAX_QUBITS
)));
}
let seed_opt: Option<u64> = if seed.is_undefined() || seed.is_null() {
None
} else {
Some(
seed.as_f64()
.ok_or_else(|| JsValue::from_str("seed must be a number, null, or undefined"))?
as u64,
)
};
let target_states_usize: Vec<usize> = target_states
.into_iter()
.map(|s| s as usize)
.collect();
let config = ruqu_algorithms::grover::GroverConfig {
num_qubits,
target_states: target_states_usize,
num_iterations: None,
seed: seed_opt,
};
let result = ruqu_algorithms::grover::run_grover(&config)
.map_err(|e| JsValue::from_str(&e.to_string()))?;
#[derive(Serialize)]
struct GroverJs {
measured_state: usize,
target_found: bool,
success_probability: f64,
num_iterations: u32,
}
serde_wasm_bindgen::to_value(&GroverJs {
measured_state: result.measured_state,
target_found: result.target_found,
success_probability: result.success_probability,
num_iterations: result.num_iterations,
})
.map_err(|e| JsValue::from_str(&e.to_string()))
}
#[wasm_bindgen]
pub fn qaoa_maxcut(
num_nodes: u32,
edges_flat: Vec<u32>,
p: u32,
gammas: Vec<f64>,
betas: Vec<f64>,
seed: JsValue,
) -> Result<JsValue, JsValue> {
if num_nodes > WASM_MAX_QUBITS {
return Err(JsValue::from_str(&format!(
"Qubit limit exceeded: {} requested, max {} in WASM",
num_nodes, WASM_MAX_QUBITS
)));
}
if gammas.len() != p as usize {
return Err(JsValue::from_str(&format!(
"gammas length mismatch: expected {} (p), got {}",
p,
gammas.len()
)));
}
if betas.len() != p as usize {
return Err(JsValue::from_str(&format!(
"betas length mismatch: expected {} (p), got {}",
p,
betas.len()
)));
}
if edges_flat.len() % 2 != 0 {
return Err(JsValue::from_str(
"edges_flat must contain an even number of elements (pairs of node indices)",
));
}
let seed_opt: Option<u64> = if seed.is_undefined() || seed.is_null() {
None
} else {
Some(
seed.as_f64()
.ok_or_else(|| JsValue::from_str("seed must be a number, null, or undefined"))?
as u64,
)
};
let mut graph = ruqu_algorithms::qaoa::Graph::new(num_nodes);
for chunk in edges_flat.chunks(2) {
if chunk.len() == 2 {
graph.add_edge(chunk[0], chunk[1], 1.0);
}
}
let circuit = ruqu_algorithms::qaoa::build_qaoa_circuit(&graph, &gammas, &betas);
let result = ruqu_core::simulator::Simulator::run_with_config(
&circuit,
&ruqu_core::simulator::SimConfig {
seed: seed_opt,
noise: None,
shots: None,
},
)
.map_err(|e| JsValue::from_str(&e.to_string()))?;
let probs = result.state.probabilities();
let mut expected_cut = 0.0;
for chunk in edges_flat.chunks(2) {
if chunk.len() == 2 {
let zz = result.state.expectation_value(&ruqu_core::types::PauliString {
ops: vec![
(chunk[0], ruqu_core::types::PauliOp::Z),
(chunk[1], ruqu_core::types::PauliOp::Z),
],
});
expected_cut += 0.5 * (1.0 - zz);
}
}
#[derive(Serialize)]
struct QaoaJs {
probabilities: Vec<f64>,
expected_cut: f64,
}
serde_wasm_bindgen::to_value(&QaoaJs {
probabilities: probs,
expected_cut,
})
.map_err(|e| JsValue::from_str(&e.to_string()))
}
#[wasm_bindgen(start)]
pub fn init() {
#[cfg(feature = "console_error_panic_hook")]
console_error_panic_hook::set_once();
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_max_qubits_constant() {
assert_eq!(max_qubits(), 25);
}
#[test]
fn test_circuit_rejects_too_many_qubits() {
let result = WasmQuantumCircuit::new(WASM_MAX_QUBITS + 1);
assert!(result.is_err());
}
#[test]
fn test_circuit_accepts_max_qubits() {
let result = WasmQuantumCircuit::new(WASM_MAX_QUBITS);
assert!(result.is_ok());
}
#[test]
fn test_circuit_accepts_small_count() {
let circuit = WasmQuantumCircuit::new(2).expect("2 qubits should succeed");
assert_eq!(circuit.num_qubits(), 2);
assert_eq!(circuit.gate_count(), 0);
}
}