diff --git a/src/rasqal/src/analysis/projections.rs b/src/rasqal/src/analysis/projections.rs index 6504cb2..9d5256e 100644 --- a/src/rasqal/src/analysis/projections.rs +++ b/src/rasqal/src/analysis/projections.rs @@ -248,6 +248,13 @@ impl QuantumProjection { return AnalysisResult::empty(); } + if self.trace_module.has(ActiveTracers::Solver) { + log!(Level::Info, "Solving circuit:"); + for inst in self.instructions.iter() { + log!(Level::Info, "{}", inst.to_string()); + } + } + let start = Instant::now(); let qsolver = QuantumSolver::with_trace(self.trace_module.clone()); for inst in self.instructions.iter() { diff --git a/src/rasqal/src/analysis/solver.rs b/src/rasqal/src/analysis/solver.rs index 95f037a..e5fd979 100644 --- a/src/rasqal/src/analysis/solver.rs +++ b/src/rasqal/src/analysis/solver.rs @@ -183,14 +183,16 @@ impl AnalysisQubit { pub fn is_entangled(&self) -> bool { !self.tangles.is_empty() } - pub fn entangle(&self, other: &Ptr) { + // TODO: Remove self_pointer and decide how to deal with this. Don't want to steal the + // borrow really. + pub fn entangle(&self, self_pointer: &Ptr, other: &Ptr) { if self.is_entangled_with(&other.index) { return; } - let tangle = Ptr::from(Tangle::from_qubits(&Ptr::from(self), other)); - with_mutable_self!(self.tangles.insert(other.index, tangle.clone())); - with_mutable_self!(other.tangles.insert(self.index, tangle)); + let tangle = Ptr::from(Tangle::from_qubits(self_pointer, other)); + with_mutable!(self_pointer.tangles.insert(other.index, tangle.clone())); + with_mutable!(other.tangles.insert(self.index, tangle)); } /// Returns 0...x depending upon what this qubit would be measured as. @@ -268,19 +270,33 @@ impl AnalysisQubit { pub fn apply(&self, gate: &GateFragment) { with_mutable_self!(self.state.apply(gate)); for tangle in self.tangles.values() { - // Depending which side of the matrix we're on, we need to expand it different ways. + // If we're a single-qubit gate, expand. let expanded_gate = if gate.affected_qubits == 2 { gate - } else if tangle.right.index == self.index { - &MatrixFragment::id().tensor(gate) } else { &gate.tensor(&MatrixFragment::id()) }; - with_mutable!(tangle.state.apply(expanded_gate)); + log!(Level::Info, "Applying gate: {}", gate); + log!(Level::Info, "Pre-apply: {}", self.to_string()); + + // If our actual target is inverted, invert the matrix too. + let application_result = if tangle.right.index == self.index { + log!(Level::Info, "Inverting."); + with_mutable!(tangle.state.apply(&gate.invert())) + } else { + log!(Level::Info, "No invert."); + with_mutable!(tangle.state.apply(expanded_gate)) + }; + + if let Some(error) = application_result { + panic!("{}", error); + } + + log!(Level::Info, "Post-apply: {}", self.to_string()); // If our rotation has removed entanglement, drop the tangle entirely. - if tangle.is_entangled() { + if !tangle.is_entangled() { with_mutable!(tangle.left.tangles.remove(&tangle.right.index)); with_mutable!(tangle.right.tangles.remove(&tangle.left.index)); } @@ -296,11 +312,17 @@ impl AnalysisQubit { // TODO: Pretty sure these require target qubit alignment. // (Aka now that left/right qubits are not garanteed, need to just re-align). - pub fn CX(&self, control: &i64, radians: &f64) { self.apply(&GateFragment::CX(radians)) } + pub fn CX(&self, control: &i64, radians: &f64) { + self.apply(&GateFragment::CX(radians)) + } - pub fn CZ(&self, control: &i64, radians: &f64) { self.apply(&GateFragment::CZ(radians)) } + pub fn CZ(&self, control: &i64, radians: &f64) { + self.apply(&GateFragment::CZ(radians)) + } - pub fn CY(&self, control: &i64, radians: &f64) { self.apply(&GateFragment::CY(radians)) } + pub fn CY(&self, control: &i64, radians: &f64) { + self.apply(&GateFragment::CY(radians)) + } pub fn stringify(&self, indent_level: i32) -> Vec { let mut result = Vec::new(); @@ -311,7 +333,7 @@ impl AnalysisQubit { let indent = format!("{} ", base_indent); result.push(format!("{}{{\n", base_indent)); - result.push(format!("{}Q{}: {}\n", indent, self.index, self.measure())); + result.push(format!("{}Q{}: {}\n", indent, self.index, self.state.get((1, 1)).re)); for matrix_fragment in stringified_matrix_lines(&self.state.matrix_fragment.matrix) { result.push(format!("{}{}\n", indent, matrix_fragment)); } @@ -321,7 +343,7 @@ impl AnalysisQubit { tangles.sort_by_key(|val| val.0); for (index, state) in tangles { result.push(format!("{}\n", indent)); - result.push(format!("{}<{}~{}>:\n", indent, state.left, state.right)); + result.push(format!("{}<{}~{}>:\n", indent, state.left.index, state.right.index)); for matrix_fragment in stringified_matrix_lines(&state.state.matrix_fragment.matrix) { result.push(format!("{}{}\n", indent, matrix_fragment)); } @@ -395,9 +417,9 @@ impl EntanglementCluster { /// Entangles these two qubits if they exist. Does not entangle if not. pub fn entangle(&self, left: &i64, right: &i64) { - if let Some(rtangle) = self.qubits.get(right) { - if let Some(ltangle) = self.qubits.get(left) { - rtangle.entangle(ltangle); + if let Some(rqubit) = self.qubits.get(right) { + if let Some(lqubit) = self.qubits.get(left) { + rqubit.entangle(rqubit, lqubit); } } } @@ -450,27 +472,43 @@ impl EntanglementCluster { } pub fn CX(&self, control: &i64, target: &i64, radians: &f64) { - self + let qubit = self .qubits .get(target) - .expect("Attempted CX on qubit which doesn't exist in cluster.") - .CX(control, radians); + .expect("Attempted CX on qubit which doesn't exist in cluster."); + log!(Level::Info, "Pre-CX qubit: {}", qubit.to_string()); + + qubit.CX(control, radians); + + log!(Level::Info, "Post-CX qubit: {}", qubit.to_string()); + + if !qubit.is_entangled() { + self.remove(target); + } } pub fn CZ(&self, control: &i64, target: &i64, radians: &f64) { - self + let qubit = self .qubits .get(target) - .expect("Attempted CX on qubit which doesn't exist in cluster.") - .CZ(control, radians); + .expect("Attempted CX on qubit which doesn't exist in cluster."); + qubit.CZ(control, radians); + + if !qubit.is_entangled() { + self.remove(target); + } } pub fn CY(&self, control: &i64, target: &i64, radians: &f64) { - self + let qubit = self .qubits .get(target) - .expect("Attempted CX on qubit which doesn't exist in cluster.") - .CY(control, radians); + .expect("Attempted CX on qubit which doesn't exist in cluster."); + qubit.CY(control, radians); + + if !qubit.is_entangled() { + self.remove(target); + } } pub fn SWAP(&mut self, left: &i64, right: &i64) { @@ -623,26 +661,26 @@ impl MatrixFragment { } } - // /// Flips the matrix reversing the value or which qubit the operation gets applied too. - // /// TODO: Check the latter. - // pub fn invert(&self) -> MatrixFragment { - // // TODO: Cache and re-use familiar transformations. - // if self.affected_qubits == 1 { - // Self::new(array![ - // [*self.get((1, 1)), *self.get((1, 0))], - // [*self.get((0, 1)), *self.get((0, 0))], - // ]) - // } else if self.affected_qubits == 2 { - // Self::new(array![ - // [*self.get((3, 3)), *self.get((2, 3)), *self.get((1, 3)), *self.get((0, 3))], - // [*self.get((3, 2)), *self.get((2, 2)), *self.get((1, 2)), *self.get((0, 2))], - // [*self.get((3, 1)), *self.get((2, 1)), *self.get((1, 1)), *self.get((0, 1))], - // [*self.get((3, 0)), *self.get((2, 0)), *self.get((1, 0)), *self.get((0, 0))], - // ]) - // } else { - // panic!("Can't transpose a matrix covering {} qubits.", self.affected_qubits); - // } - // } + /// Flips the matrix reversing the value or which qubit the operation gets applied too. + /// TODO: Check the latter. + pub fn invert(&self) -> MatrixFragment { + // TODO: Cache and re-use familiar transformations. + if self.affected_qubits == 1 { + Self::new(array![ + [*self.get((1, 1)), *self.get((1, 0))], + [*self.get((0, 1)), *self.get((0, 0))], + ]) + } else if self.affected_qubits == 2 { + Self::new(array![ + [*self.get((3, 3)), *self.get((2, 3)), *self.get((1, 3)), *self.get((0, 3))], + [*self.get((3, 2)), *self.get((2, 2)), *self.get((1, 2)), *self.get((0, 2))], + [*self.get((3, 1)), *self.get((2, 1)), *self.get((1, 1)), *self.get((0, 1))], + [*self.get((3, 0)), *self.get((2, 0)), *self.get((1, 0)), *self.get((0, 0))], + ]) + } else { + panic!("Can't transpose a matrix covering {} qubits.", self.affected_qubits); + } + } #[rustfmt::skip] pub fn X(radians: &f64) -> MatrixFragment { @@ -1010,6 +1048,7 @@ impl SolverResult { } } } + #[derive(Clone)] pub struct ResultFragment { /// Rolling probability of this whole fragment being applicable. Used for filtering. @@ -1260,6 +1299,7 @@ impl QuantumSolver { fn merge_clusters(&self, merger: Vec<&i64>, mergee: &i64) -> &Ptr { let target_cluster = self.cluster_for(&mergee); + log!(Level::Info, "Mergee before: {}", target_cluster.to_string()); for index in merger { if let Some(cluster) = self.clusters.get(index) { // If clusters are different, merge, entangle our two qubits, then replace reference. @@ -1276,6 +1316,8 @@ impl QuantumSolver { target_cluster.add_then_entangle(qubit, mergee); with_mutable_self!(self.clusters.insert(index.clone(), target_cluster.clone())); } + + log!(Level::Info, "Mergee after: {}", target_cluster.to_string()); } target_cluster } @@ -1300,6 +1342,7 @@ impl QuantumSolver { // We only record the last measure on a qubit as the valid one. with_mutable_self!(self.measures.insert(qb.index, result)); + log!(Level::Info, "{}", self.to_string()); } pub fn X(&self, qb: &Qubit, radians: &f64) { @@ -1496,10 +1539,15 @@ mod tests { #[test] fn bell_test() { let solver = QuantumSolver::new(); - let q1 = Qubit::new(0); - solver.Had(&q1); - solver.CX(&vec![Qubit::new(1)], &q1, &PI); + let (q0, q1) = (Qubit::new(0), Qubit::new(1)); + solver.Had(&q0); + solver.CX(&vec![q1.clone()], &q0, &PI); + solver.measure(&q0); + solver.measure(&q1); + let solver_string = solver.to_string(); let result = solver.solve(); + let mut dave = String::new(); + dave = dave.to_string() + "dave"; } #[test]