Skip to main content

qualia_client_core/
qpu_pipeline.rs

1//! End-to-end quantum pipeline: compile → egress → re-hydrate.
2
3use crate::qpu_dispatcher::{self, QpuDispatchResult};
4use crate::qpu_oracle::{self, QpuChatCommandResult};
5use qualia_core_db::qubo_compiler::{compile_quins_to_qubo, rehydrate_solution, QuboMatrix};
6use qualia_core_db::NQuin;
7
8pub const MAX_REHYDRATED: usize = 64;
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11pub enum QuantumTaskKind {
12    QuboRouting,
13    DftGroundState,
14    DefeasibleResolution,
15}
16
17pub struct QuantumPipelineResult {
18    pub task: QuantumTaskKind,
19    pub dispatch: QpuDispatchResult,
20    pub rehydrated: Vec<NQuin>,
21    pub summary: String,
22}
23
24pub fn detect_task_from_prompt(prompt: &str) -> Option<QuantumTaskKind> {
25    let lower = prompt.to_lowercase();
26    if lower.contains("[qpu:qubo]") {
27        return Some(QuantumTaskKind::QuboRouting);
28    }
29    if lower.contains("[qpu:dft]") {
30        return Some(QuantumTaskKind::DftGroundState);
31    }
32    if lower.contains("[qpu:defeasible]") {
33        return Some(QuantumTaskKind::DefeasibleResolution);
34    }
35    if prompt.contains(r"$$") {
36        if prompt.contains(r"\min") || prompt.contains("QUBO") {
37            return Some(QuantumTaskKind::QuboRouting);
38        }
39        if prompt.contains(r"\hat{H}")
40            || prompt.contains(r"\Psi")
41            || prompt.contains(r"\hat{h}")
42            || prompt.contains("ground state")
43        {
44            return Some(QuantumTaskKind::DftGroundState);
45        }
46    }
47    None
48}
49
50pub fn execute_quantum_pipeline(
51    task: QuantumTaskKind,
52    quins: &[NQuin],
53    latex_hint: Option<&str>,
54) -> Result<QuantumPipelineResult, String> {
55    if !qpu_oracle::is_qpu_feature_unlocked() {
56        return Err("QPU Oracle not unlocked. Type [enable_QPU] in Chat first.".into());
57    }
58
59    let settings = qpu_oracle::get_qpu_settings();
60    let shots = settings.max_shots_per_task;
61
62    match task {
63        QuantumTaskKind::QuboRouting => {
64            let mut matrix = QuboMatrix::default();
65            compile_quins_to_qubo(quins, &mut matrix)
66                .map_err(|e| format!("QUBO compile blocked: {e:?}"))?;
67            if matrix.num_vars == 0 {
68                build_demo_qubo(&mut matrix, latex_hint);
69            }
70            let dispatch = qpu_dispatcher::dispatch_qubo(&matrix, shots)?;
71            let mut out = [NQuin {
72                subject: 0,
73                predicate: 0,
74                object: 0,
75                context: 0,
76                metadata: 0,
77                parity: 0,
78            }; MAX_REHYDRATED];
79            let n = rehydrate_solution(&mut matrix, &dispatch.assignment, &mut out);
80            let summary = format!(
81                "⚛️ **QUBO routing complete** ({})\n\n\
82                 - Variables: {}\n\
83                 - Ground energy: {:.4}\n\
84                 - Remote QPU: {}\n\
85                 - Re-hydrated assertions: {}\n\n\
86                 Ephemeral index map wiped. No semantic context left the device.",
87                dispatch.backend, dispatch.num_vars, dispatch.energy, dispatch.used_remote, n
88            );
89            Ok(QuantumPipelineResult {
90                task,
91                dispatch,
92                rehydrated: out[..n].to_vec(),
93                summary,
94            })
95        }
96        QuantumTaskKind::DftGroundState => {
97            let params = extract_vqe_params(latex_hint.unwrap_or(""));
98            let dispatch = qpu_dispatcher::dispatch_vqe(&params, shots)?;
99            let summary = format!(
100                "⚛️ **DFT / VQE ground-state** ({})\n\n\
101                 - Parameter vector dim: {}\n\
102                 - Estimated energy: {:.4} eV\n\
103                 - Remote QPU: {}\n\n\
104                 Local Core 2 prepared the Hamiltonian; only parameter amplitudes egressed.",
105                dispatch.backend,
106                params.len(),
107                dispatch.energy,
108                dispatch.used_remote
109            );
110            Ok(QuantumPipelineResult {
111                task,
112                dispatch,
113                rehydrated: vec![],
114                summary,
115            })
116        }
117        QuantumTaskKind::DefeasibleResolution => {
118            let mut matrix = QuboMatrix::default();
119            compile_quins_to_qubo(quins, &mut matrix)
120                .map_err(|e| format!("Defeasible QUBO blocked: {e:?}"))?;
121            if matrix.num_vars == 0 {
122                build_defeasible_demo(&mut matrix);
123            }
124            let dispatch = qpu_dispatcher::dispatch_qubo(&matrix, shots)?;
125            let summary = format!(
126                "⚛️ **Defeasible resolution** via probabilistic QUBO ({})\n\
127                 Energy: {:.4} | Remote: {}",
128                dispatch.backend, dispatch.energy, dispatch.used_remote
129            );
130            Ok(QuantumPipelineResult {
131                task,
132                dispatch,
133                rehydrated: vec![],
134                summary,
135            })
136        }
137    }
138}
139
140fn build_demo_qubo(matrix: &mut QuboMatrix, hint: Option<&str>) {
141    let _ = hint;
142    matrix.num_vars = 3;
143    matrix.linear[0] = -1.0;
144    matrix.linear[1] = -0.5;
145    matrix.linear[2] = -2.0;
146    let _ = matrix.emit_coupler(0, 1, 1.5);
147    let _ = matrix.emit_coupler(1, 2, 0.8);
148    matrix.index_map[0] = (0xA001, 0);
149    matrix.index_map[1] = (0xA002, 1);
150    matrix.index_map[2] = (0xA003, 2);
151    matrix.index_count = 3;
152}
153
154fn build_defeasible_demo(matrix: &mut QuboMatrix) {
155    matrix.num_vars = 2;
156    matrix.linear[0] = -1.0;
157    matrix.linear[1] = -1.0;
158    let _ = matrix.emit_coupler(0, 1, 3.0);
159    matrix.index_count = 2;
160    matrix.index_map[0] = (0xD001, 0);
161    matrix.index_map[1] = (0xD002, 1);
162}
163
164/// Unified engine command router: QPU unlock, quantum tasks, and LaTeX hints.
165pub fn handle_engine_chat_command(text: &str) -> QpuChatCommandResult {
166    use crate::qpu_oracle::{handle_qpu_chat_command, QpuChatCommandResult};
167
168    let unlock = handle_qpu_chat_command(text);
169    if unlock.handled {
170        return unlock;
171    }
172
173    let task = detect_task_from_prompt(text);
174    if let Some(kind) = task {
175        if !qpu_oracle::is_qpu_feature_unlocked() {
176            return QpuChatCommandResult {
177                handled: true,
178                feature_unlocked: false,
179                response: "⚛️ Quantum task detected but QPU Oracle is locked. \
180                    Type `[enable_QPU]` first, then configure API keys in Settings."
181                    .to_string(),
182            };
183        }
184        let quins: Vec<NQuin> = vec![];
185        let latex = if text.contains(r"$$") {
186            Some(text)
187        } else {
188            None
189        };
190        match execute_quantum_pipeline(kind, &quins, latex) {
191            Ok(result) => QpuChatCommandResult {
192                handled: true,
193                feature_unlocked: true,
194                response: result.summary,
195            },
196            Err(e) => QpuChatCommandResult {
197                handled: true,
198                feature_unlocked: true,
199                response: format!("🔴 Quantum pipeline failed: {e}"),
200            },
201        }
202    } else {
203        QpuChatCommandResult {
204            handled: false,
205            response: String::new(),
206            feature_unlocked: qpu_oracle::is_qpu_feature_unlocked(),
207        }
208    }
209}
210
211fn extract_vqe_params(latex: &str) -> Vec<f64> {
212    let mut params = Vec::new();
213    for token in latex.split(|c: char| !c.is_ascii_digit() && c != '.' && c != '-') {
214        if let Ok(v) = token.parse::<f64>() {
215            if token.contains('.') || v.abs() > 0.0 {
216                params.push(v);
217            }
218        }
219    }
220    if params.is_empty() {
221        params.extend_from_slice(&[0.1, 0.2, -0.15, 0.05]);
222    }
223    params.truncate(16);
224    params
225}