Skip to main content

qualia_core_db/solvers/qpu/
pre_solver.rs

1//! QPU Pre-Solver — converts high-level problem descriptions into provider-ready
2//! QUBO matrices or gate-model quantum circuits.
3//!
4//! Ported from `qpu/src/pre_solver.rs`.
5
6use super::{JobParameters, ProblemType, QpuError};
7use serde::{Deserialize, Serialize};
8use std::collections::HashMap;
9
10// ── Problem description ───────────────────────────────────────────────────────
11
12#[derive(Debug, Clone, Serialize, Deserialize)]
13pub struct ProblemDescription {
14    pub problem_type: ProblemType,
15    pub variables: Vec<Variable>,
16    pub constraints: Vec<Constraint>,
17    pub objective: Objective,
18}
19
20#[derive(Debug, Clone, Serialize, Deserialize)]
21pub struct Variable {
22    pub name: String,
23    pub domain: VariableDomain,
24    pub index: u32,
25}
26
27#[derive(Debug, Clone, Serialize, Deserialize)]
28pub enum VariableDomain {
29    Binary,
30    Spin,
31    Integer { min: i32, max: i32 },
32    Continuous { min: f64, max: f64 },
33}
34
35#[derive(Debug, Clone, Serialize, Deserialize)]
36pub struct Constraint {
37    pub constraint_type: ConstraintType,
38    pub variables: Vec<String>,
39    pub parameters: serde_json::Value,
40}
41
42#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
43pub enum ConstraintType {
44    Linear,
45    Quadratic,
46    Equality,
47    Inequality,
48    Logical,
49}
50
51#[derive(Debug, Clone, Serialize, Deserialize)]
52pub struct Objective {
53    pub objective_type: ObjectiveType,
54    pub expression: String,
55    pub minimize: bool,
56}
57
58#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
59pub enum ObjectiveType {
60    Linear,
61    Quadratic,
62    Polynomial,
63    Custom,
64}
65
66// ── QUBO formulation ──────────────────────────────────────────────────────────
67
68#[derive(Debug, Clone, Serialize, Deserialize)]
69pub struct QuboFormulation {
70    pub num_variables: u32,
71    /// (variable_index, coefficient)
72    pub linear_terms: Vec<(u32, f64)>,
73    /// (var_a, var_b, coefficient)
74    pub quadratic_terms: Vec<(u32, u32, f64)>,
75    pub offset: f64,
76}
77
78impl QuboFormulation {
79    pub fn new(num_variables: u32) -> Self {
80        Self {
81            num_variables,
82            linear_terms: Vec::new(),
83            quadratic_terms: Vec::new(),
84            offset: 0.0,
85        }
86    }
87
88    pub fn add_linear_term(&mut self, variable: u32, coefficient: f64) {
89        self.linear_terms.push((variable, coefficient));
90    }
91
92    pub fn add_quadratic_term(&mut self, var_a: u32, var_b: u32, coefficient: f64) {
93        self.quadratic_terms.push((var_a, var_b, coefficient));
94    }
95
96    pub fn to_job_parameters(&self) -> JobParameters {
97        JobParameters {
98            num_qubits: self.num_variables,
99            hamiltonian: serde_json::to_string(self).ok(),
100            circuit: None,
101            circuit_depth: 1,
102            shots: 1000,
103            extra: serde_json::json!({"formulation": "qubo"}),
104        }
105    }
106}
107
108// ── Circuit formulation ───────────────────────────────────────────────────────
109
110#[derive(Debug, Clone, Serialize, Deserialize)]
111pub struct CircuitFormulation {
112    pub num_qubits: u32,
113    pub gates: Vec<Gate>,
114    pub measurements: Vec<Measurement>,
115}
116
117#[derive(Debug, Clone, Serialize, Deserialize)]
118pub struct Gate {
119    pub gate_type: GateType,
120    pub qubits: Vec<u32>,
121    pub parameters: Vec<f64>,
122}
123
124#[derive(Debug, Clone, Serialize, Deserialize)]
125pub enum GateType {
126    H,
127    X,
128    Y,
129    Z,
130    Rx,
131    Ry,
132    Rz,
133    CNOT,
134    CZ,
135    SWAP,
136    ZZ,
137    Custom(String),
138}
139
140#[derive(Debug, Clone, Serialize, Deserialize)]
141pub struct Measurement {
142    pub qubit: u32,
143    pub basis: MeasurementBasis,
144}
145
146#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
147pub enum MeasurementBasis {
148    Computational,
149    X,
150    Y,
151    Z,
152}
153
154impl CircuitFormulation {
155    pub fn new(num_qubits: u32) -> Self {
156        Self {
157            num_qubits,
158            gates: Vec::new(),
159            measurements: Vec::new(),
160        }
161    }
162
163    pub fn add_gate(&mut self, gate: Gate) {
164        self.gates.push(gate);
165    }
166
167    pub fn add_measurement(&mut self, m: Measurement) {
168        self.measurements.push(m);
169    }
170
171    pub fn to_job_parameters(&self) -> JobParameters {
172        JobParameters {
173            num_qubits: self.num_qubits,
174            hamiltonian: None,
175            circuit: serde_json::to_string(self).ok(),
176            circuit_depth: self.gates.len() as u32,
177            shots: 1000,
178            extra: serde_json::json!({"formulation": "circuit"}),
179        }
180    }
181}
182
183// ── Pre-solver ────────────────────────────────────────────────────────────────
184
185pub struct PreSolver {
186    variable_map: HashMap<String, u32>,
187}
188
189impl PreSolver {
190    pub fn new() -> Self {
191        Self {
192            variable_map: HashMap::new(),
193        }
194    }
195
196    pub fn formulate(&mut self, problem: &ProblemDescription) -> Result<JobParameters, QpuError> {
197        match problem.problem_type {
198            ProblemType::Annealing => self.formulate_qubo(problem),
199            ProblemType::GateModel => self.formulate_circuit(problem),
200            ProblemType::Vqe | ProblemType::Qaoa => self.formulate_circuit(problem),
201        }
202    }
203
204    fn formulate_qubo(&mut self, problem: &ProblemDescription) -> Result<JobParameters, QpuError> {
205        let mut qubo = QuboFormulation::new(problem.variables.len() as u32);
206        self.variable_map.clear();
207        for var in &problem.variables {
208            self.variable_map.insert(var.name.clone(), var.index);
209        }
210        for c in &problem.constraints {
211            self.apply_constraint_qubo(&mut qubo, c)?;
212        }
213        self.apply_objective_qubo(&mut qubo, &problem.objective)?;
214        Ok(qubo.to_job_parameters())
215    }
216
217    fn formulate_circuit(
218        &mut self,
219        problem: &ProblemDescription,
220    ) -> Result<JobParameters, QpuError> {
221        let mut circuit = CircuitFormulation::new(problem.variables.len() as u32);
222        self.variable_map.clear();
223        for var in &problem.variables {
224            self.variable_map.insert(var.name.clone(), var.index);
225            circuit.add_gate(Gate {
226                gate_type: GateType::H,
227                qubits: vec![var.index],
228                parameters: vec![],
229            });
230        }
231        for c in &problem.constraints {
232            self.apply_constraint_circuit(&mut circuit, c)?;
233        }
234        for var in &problem.variables {
235            circuit.add_measurement(Measurement {
236                qubit: var.index,
237                basis: MeasurementBasis::Computational,
238            });
239        }
240        Ok(circuit.to_job_parameters())
241    }
242
243    fn apply_constraint_qubo(
244        &self,
245        qubo: &mut QuboFormulation,
246        c: &Constraint,
247    ) -> Result<(), QpuError> {
248        match c.constraint_type {
249            ConstraintType::Linear => {
250                if let Some(coeffs) = c.parameters.as_array() {
251                    for (i, coeff) in coeffs.iter().enumerate() {
252                        if let (Some(val), Some(name)) = (coeff.as_f64(), c.variables.get(i)) {
253                            if let Some(&idx) = self.variable_map.get(name.as_str()) {
254                                qubo.add_linear_term(idx, val);
255                            }
256                        }
257                    }
258                }
259                Ok(())
260            }
261            ConstraintType::Quadratic => {
262                if let Some(params) = c.parameters.as_object() {
263                    for (key, value) in params {
264                        if let Some(coeff) = value.as_f64() {
265                            let parts: Vec<&str> = key.splitn(2, ',').collect();
266                            if parts.len() == 2 {
267                                if let (Some(&a), Some(&b)) = (
268                                    self.variable_map.get(parts[0]),
269                                    self.variable_map.get(parts[1]),
270                                ) {
271                                    qubo.add_quadratic_term(a, b, coeff);
272                                }
273                            }
274                        }
275                    }
276                }
277                Ok(())
278            }
279            ref t => Err(QpuError::Api(format!(
280                "Constraint type {:?} not supported for QUBO",
281                t
282            ))),
283        }
284    }
285
286    fn apply_constraint_circuit(
287        &self,
288        circuit: &mut CircuitFormulation,
289        c: &Constraint,
290    ) -> Result<(), QpuError> {
291        if c.constraint_type == ConstraintType::Logical && c.variables.len() == 2 {
292            let a = self
293                .variable_map
294                .get(c.variables[0].as_str())
295                .copied()
296                .unwrap_or(0);
297            let b = self
298                .variable_map
299                .get(c.variables[1].as_str())
300                .copied()
301                .unwrap_or(0);
302            circuit.add_gate(Gate {
303                gate_type: GateType::CNOT,
304                qubits: vec![a, b],
305                parameters: vec![],
306            });
307            Ok(())
308        } else {
309            Err(QpuError::Api(format!(
310                "Constraint type {:?} not supported for circuit",
311                c.constraint_type
312            )))
313        }
314    }
315
316    fn apply_objective_qubo(
317        &self,
318        qubo: &mut QuboFormulation,
319        obj: &Objective,
320    ) -> Result<(), QpuError> {
321        let sign = if obj.minimize { 1.0 } else { -1.0 };
322        match obj.objective_type {
323            ObjectiveType::Linear => {
324                for &idx in self.variable_map.values() {
325                    qubo.add_linear_term(idx, sign);
326                }
327                Ok(())
328            }
329            ObjectiveType::Quadratic => {
330                let vars: Vec<u32> = self.variable_map.values().cloned().collect();
331                for i in 0..vars.len() {
332                    for j in i..vars.len() {
333                        qubo.add_quadratic_term(vars[i], vars[j], sign);
334                    }
335                }
336                Ok(())
337            }
338            ref t => Err(QpuError::Api(format!(
339                "Objective type {:?} not supported",
340                t
341            ))),
342        }
343    }
344}
345
346impl Default for PreSolver {
347    fn default() -> Self {
348        Self::new()
349    }
350}
351
352#[cfg(test)]
353mod tests {
354    use super::*;
355
356    fn two_var_problem(pt: ProblemType) -> ProblemDescription {
357        ProblemDescription {
358            problem_type: pt,
359            variables: vec![
360                Variable {
361                    name: "x0".into(),
362                    domain: VariableDomain::Binary,
363                    index: 0,
364                },
365                Variable {
366                    name: "x1".into(),
367                    domain: VariableDomain::Binary,
368                    index: 1,
369                },
370            ],
371            constraints: vec![],
372            objective: Objective {
373                objective_type: ObjectiveType::Linear,
374                expression: "x0 + x1".into(),
375                minimize: true,
376            },
377        }
378    }
379
380    #[test]
381    fn qubo_formulation_roundtrip() {
382        let mut solver = PreSolver::new();
383        let params = solver
384            .formulate(&two_var_problem(ProblemType::Annealing))
385            .unwrap();
386        assert_eq!(params.num_qubits, 2);
387        assert!(params.hamiltonian.is_some());
388    }
389
390    #[test]
391    fn circuit_formulation_roundtrip() {
392        let mut solver = PreSolver::new();
393        let params = solver
394            .formulate(&two_var_problem(ProblemType::GateModel))
395            .unwrap();
396        assert_eq!(params.num_qubits, 2);
397        assert!(params.circuit.is_some());
398    }
399
400    #[test]
401    fn qubo_add_terms() {
402        let mut q = QuboFormulation::new(2);
403        q.add_linear_term(0, 1.0);
404        q.add_linear_term(1, -1.0);
405        q.add_quadratic_term(0, 1, 0.5);
406        assert_eq!(q.linear_terms.len(), 2);
407        assert_eq!(q.quadratic_terms.len(), 1);
408    }
409}