1use super::{JobParameters, ProblemType, QpuError};
7use serde::{Deserialize, Serialize};
8use std::collections::HashMap;
9
10#[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#[derive(Debug, Clone, Serialize, Deserialize)]
69pub struct QuboFormulation {
70 pub num_variables: u32,
71 pub linear_terms: Vec<(u32, f64)>,
73 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#[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
183pub 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}