From 77d3764335d98f34769f9400feffb5afe16f60d6 Mon Sep 17 00:00:00 2001 From: John Dumbell Date: Mon, 19 Aug 2024 15:18:24 +0100 Subject: [PATCH] Forq solver prototype --- src/rasqal/Cargo.toml | 2 + src/rasqal/src/analysis.rs | 1196 +++++++++++++++++++++++------------ src/rasqal/src/config.rs | 33 +- src/rasqal/src/evaluator.rs | 3 +- src/rasqal/src/execution.rs | 53 +- src/rasqal/src/runtime.rs | 30 +- 6 files changed, 855 insertions(+), 462 deletions(-) diff --git a/src/rasqal/Cargo.toml b/src/rasqal/Cargo.toml index b1632e3..7c8d47b 100644 --- a/src/rasqal/Cargo.toml +++ b/src/rasqal/Cargo.toml @@ -32,6 +32,8 @@ env_logger = "0.9.3" ctor = "0.2.2" num = "0.4.0" bitflags = "2.4.0" +ndarray = "0.15.6" +num-complex = "0.4.6" [lib] crate-type = ["cdylib"] diff --git a/src/rasqal/src/analysis.rs b/src/rasqal/src/analysis.rs index 9a6b214..1026e9a 100644 --- a/src/rasqal/src/analysis.rs +++ b/src/rasqal/src/analysis.rs @@ -1,561 +1,866 @@ // SPDX-License-Identifier: BSD-3-Clause // Copyright (c) 2024 Oxford Quantum Circuits Ltd +use crate::config::RasqalConfig; use crate::execution::RuntimeCollection; use crate::features::QuantumFeatures; +use crate::graphs::AnalysisGraph; use crate::hardware::Qubit; use crate::runtime::{ActiveTracers, TracingModule}; use crate::smart_pointers::Ptr; use crate::{with_mutable, with_mutable_self}; use log::{log, Level}; -use num::traits::FloatConst; -use std::borrow::Borrow; +use ndarray::{array, Array2}; +use num::range; +use num_complex::Complex; use std::cmp::Ordering; +use std::collections::hash_map::Keys; use std::collections::{HashMap, HashSet}; -use std::fmt::{Display, Formatter}; +use std::fmt::{Display, Formatter, Write}; use std::iter::zip; -use std::ops::Deref; +use std::ops::{Deref, Mul, MulAssign}; +use std::time::Instant; -#[derive(Clone)] -pub struct StateHistory { - index: i64, - metadata: Ptr, +/// More succinct way to initialize complex numbers in the matrix'. +macro_rules! C { + ($real:expr, $img:expr) => { + Complex::new($real, $img) + }; +} - // TODO: Pointer to avoid mutability. - timeline: Ptr> +/// Construct which holds the entanglement information between two qubits, shared between both. +#[derive(Clone)] +pub struct Tangle { + upper_left: i64, + state: Ptr, + bottom_right: i64 } -macro_rules! cluster_or_state { - ($self:ident, $axis:ident, $arg:ident) => { - match $self.state_of() { - StateElement::Single(qstate) => { - let counter = &with_mutable_self!($self.metadata.next_counter()); - let mut next_state = qstate.clone_with_counter(counter); - next_state.$axis($arg); - with_mutable_self!($self - .timeline - .insert(counter.clone(), StateElement::Single(next_state))); - } - StateElement::Cluster(qcluster) => { - qcluster.$axis($arg, &$self.index); - } +impl Tangle { + pub fn from_qubits( + left: (&i64, &Ptr), right: (&i64, &Ptr) + ) -> Tangle { + let fragment = Ptr::from(EntangledFragment::EntangledFromExisting(left.1, right.1)); + Tangle { + upper_left: *left.0, + state: fragment, + bottom_right: *right.0 } - }; - ($self:ident, $method:ident) => { - match $self.state_of() { - StateElement::Single(qstate) => { - let counter = &with_mutable_self!($self.metadata.next_counter()); - let mut next_state = qstate.clone_with_counter(counter); - next_state.$method(); - with_mutable_self!($self - .timeline - .insert(counter.clone(), StateElement::Single(next_state))); - } - StateElement::Cluster(qcluster) => { - qcluster.$method(&$self.index); - } - } - }; + } +} + +pub struct EntanglementStrength {} + +pub struct MeasureAnalysis { + min: i8, + max: i8, + result: f64, + entanglement_strength: Vec } -impl StateHistory { - pub fn new(meta: &Ptr, index: &i64) -> StateHistory { - StateHistory { - timeline: Ptr::from(HashMap::new()), - metadata: meta.clone(), - index: *index +impl MeasureAnalysis { + pub fn qubit(result: f64) -> MeasureAnalysis { + MeasureAnalysis { + min: 0, + max: 1, + result, + entanglement_strength: Vec::new() } } - /// Direct manipulation to the timeline. Means all existing rotational history will be lost. - pub fn add(&self, counter: i64, element: StateElement) { - with_mutable_self!(self.timeline.insert(counter, element)); + pub fn entangled_qubit( + result: f64, entanglement_strength: Vec + ) -> MeasureAnalysis { + MeasureAnalysis { + min: 0, + max: 1, + result, + entanglement_strength + } } +} - pub fn X(&self, radii: i64) { - cluster_or_state!(self, X, radii); - } +#[derive(Clone)] +pub struct AnalysisQubit { + index: i64, - pub fn Y(&self, radii: i64) { - cluster_or_state!(self, Y, radii); + /// Record of the raw qubit sans entanglement. + state: Ptr, + + /// All the tangles between this qubit and others. Key is the index of the other qubit, along + /// with a 4x4 density matrix. + tangles: Ptr>> +} + +impl AnalysisQubit { + pub fn new( + index: &i64, qubit: &Ptr, tangles: &HashMap> + ) -> AnalysisQubit { + AnalysisQubit { + index: *index, + state: qubit.clone(), + tangles: Ptr::from(tangles.clone()) + } } - pub fn Z(&self, radii: i64) { - cluster_or_state!(self, Z, radii); + pub fn with_index(index: &i64) -> AnalysisQubit { + AnalysisQubit { + index: *index, + state: Ptr::from(QubitFragment::DefaultQubit()), + tangles: Ptr::from(HashMap::default()) + } } - pub fn measure(&self) { - cluster_or_state!(self, measure); + pub fn with_fragment(index: &i64, qubit: &Ptr) -> AnalysisQubit { + AnalysisQubit { + index: *index, + state: qubit.clone(), + tangles: Ptr::from(HashMap::default()) + } } - pub fn reset(&self) { - self.measure(); + pub fn entangled_with(&self) -> Keys<'_, i64, Ptr> { self.tangles.keys() } - // We measure first to collapse any state then just reset our timeline to 0. - let counter = with_mutable_self!(self.metadata.next_counter()); - self.add( - counter, - StateElement::Single(SingleState::new(&counter, SpherePoint::new(), &self.index)) - ); - } + pub fn is_entangled_with(&self, index: &i64) -> bool { self.tangles.contains_key(index) } - fn controlled_rotation(&self, sphere: SpherePoint, conditioned_on: &Vec, result: i8) { - let current_counter = with_mutable_self!(self.metadata.next_counter()); - let cluster = self.form_cluster(current_counter.borrow(), conditioned_on); - with_mutable!(cluster.entangle(ClusterRelationship::new( - sphere, - current_counter, - self.index, - conditioned_on, - result - ))); - } + pub fn entangle(&self, other: &Ptr) { + if self.is_entangled_with(&other.index) { + return; + } - pub fn CX(&self, radii: i64, conditioned_on: &Vec, result: i8) { - let mut sphere = SpherePoint::new(); - sphere.X(radii); - self.controlled_rotation(sphere, conditioned_on, result); + let tangle = Ptr::from(Tangle::from_qubits( + (&self.index, &self.state), + (&other.index, &other.state) + )); + with_mutable_self!(self.tangles.insert(other.index, tangle.clone())); + with_mutable_self!(other.tangles.insert(self.index, tangle)); } - pub fn CY(&self, radii: i64, conditioned_on: &Vec, result: i8) { - let mut sphere = SpherePoint::new(); - sphere.X(radii); - self.controlled_rotation(sphere, conditioned_on, result); + /// Returns 0...x depending upon what this qubit would be measured as. + pub fn measure(&self) -> MeasureAnalysis { + // TODO: Add entanglement information for clusters. + MeasureAnalysis::qubit(self.state.matrix.get((1, 1)).unwrap().re) } - pub fn CZ(&self, radii: i64, conditioned_on: &Vec, result: i8) { - let mut sphere = SpherePoint::new(); - sphere.Z(radii); - self.controlled_rotation(sphere, conditioned_on, result); + /// Applies this gate to this qubit and all tangles. + pub fn apply(&self, gate: &GateFragment) { + with_mutable_self!(self.state.apply(gate)); + for tangle in self.tangles.values() { + with_mutable!(tangle.state.apply(gate)); + } } - /// Adds a cluster to this state, forming an entangled cluster. - fn add_cluster(&self, counter: &i64, cluster: &Ptr) { - with_mutable_self!(self - .timeline - .insert(*counter, StateElement::Cluster(cluster.clone()))); - } + pub fn X(&self, radians: &f64) { self.apply(&GateFragment::X(radians)); } - /// Forms a cluster group with the states at the passed-in index. - fn form_cluster(&self, counter: &i64, targets: &Vec) -> Ptr { - if let StateElement::Cluster(cluster) = self.state_of() { - if cluster.spans() == targets.iter().copied().collect::>() { - return cluster.clone(); - } + pub fn Y(&self, radians: &f64) { self.apply(&GateFragment::Y(radians)); } + + pub fn Z(&self, radians: &f64) { self.apply(&GateFragment::Z(radians)) } + + pub fn CX(&self, control: &i64, radians: &f64) { self.apply(&GateFragment::CX(radians)) } + + pub fn CZ(&mut self, control: &i64, radians: &f64) { self.apply(&GateFragment::CZ(radians)) } + + pub fn CY(&mut self, control: &i64, radians: &f64) { self.apply(&GateFragment::CY(radians)) } + + pub fn stringify(&self, indent_level: i32) -> Vec { + let mut result = Vec::new(); + let mut base_indent = String::new(); + for multiplier in 0..indent_level { + base_indent = format!("{} ", base_indent); } + let indent = format!("{} ", base_indent); - // If any of our targets are already clusters then we expand over those clusters as well. - let mut target_indexes = targets.iter().map(|val| *val).collect::>(); - for target in targets { - let state = with_mutable_self!(self.metadata.root.get_history(target)); - if let StateElement::Cluster(cluster) = state.state_of() { - for id in cluster.spans() { - target_indexes.insert(id); - } - } + result.push(format!("{}{{\n", base_indent)); + result.push(format!( + "{}Q{}: {}%\n", + indent, + self.index, + self.measure().result + )); + for matrix_fragment in stringified_matrix_lines(&self.state.matrix) { + result.push(format!("{}{}\n", indent, matrix_fragment)); } - // Finally build a super-cluster that spans every qubit. - let cluster = Ptr::from(ClusterState::new(&self.metadata)); - for target in target_indexes { - let state = with_mutable_self!(self.metadata.root.get_history(&target)); - state.add_cluster(counter, &cluster); + if !self.tangles.is_empty() { + for (index, state) in self.tangles.iter() { + result.push(format!("{}\n", indent)); + result.push(format!( + "{}<{}~{}>:\n", + indent, state.upper_left, state.bottom_right + )); + for matrix_fragment in stringified_matrix_lines(&state.state.matrix) { + result.push(format!("{}{}\n", indent, matrix_fragment)); + } + } } - self.add_cluster(counter, &cluster); - cluster.clone() + result.push(format!("{}}},\n", base_indent)); + result } +} - pub fn state_of(&self) -> &StateElement { - // To make things simpler, if we attempt to get a state on an empty collection, just - // insert a zero-rotation at the beginning. - // - // This also holds because when you entangle something it because something else, so - // seeing it as a continuation of an existing rotation isn't precisely true. - if self.timeline.is_empty() { - self.X(0); +impl Display for AnalysisQubit { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + for line in self.stringify(0) { + f.write_str(&line); } - - self.timeline.values().last().unwrap() + f.write_str("") } } +/// A cluster of entangled states that should be treated as an individual cohesive state. #[derive(Clone)] -pub enum StateElement { - Single(SingleState), - Cluster(Ptr) +pub struct EntanglementCluster { + qubits: Ptr>> } -#[derive(Clone)] -pub struct SingleState { - counter: i64, - state: SpherePoint, +impl EntanglementCluster { + pub fn new() -> EntanglementCluster { + EntanglementCluster { + qubits: Ptr::from(HashMap::default()) + } + } - /// Has this state been collapsed into a classical value? - collapsed: bool, - index: i64 -} + pub fn spans(&self) -> Keys<'_, i64, Ptr> { self.qubits.keys() } + + /// Disentangles this qubit if it no longer is part of this cluster. Returns whether + /// this operation was needed or not, combining the check of 'disentangle' and 'can_disentangle'. + pub fn disentangle(&self, index: &i64) -> bool { false } -impl SingleState { - pub fn new(counter: &i64, state: SpherePoint, index: &i64) -> SingleState { - SingleState { - counter: *counter, - state, - collapsed: false, - index: *index + fn contains_qubit(&self, index: &i64) -> bool { self.qubits.contains_key(index) } + + /// Gets the qubit at this index crom this cluster. Assumes existence. + pub fn qubit_for(&self, index: &i64) -> &Ptr { self.qubits.get(&index).unwrap() } + + pub fn merge(&self, other: &Ptr) { + // No need to check for existence since if a qubit is related it will already be in a + // cluster together. + // TODO: These lines makes the below not add infinite ints to the hashset. + // Definitely not what it should be doing. + let other_length = other.qubits.len(); + let our_length = self.qubits.len(); + + for (index, qubit) in other.qubits.iter() { + with_mutable_self!(self.qubits.insert(index.clone(), qubit.clone())); } } - /// States are commonly cloned with a different counter to perform further rotations on. - pub fn clone_with_counter(&self, counter: &i64) -> SingleState { - SingleState::new(counter, self.state.clone(), &self.index) + /// Adds these qubits to the cluster then entangles them. + pub fn add_then_entangle(&self, qubit_one: &Ptr, qubit_two: &Ptr) { + self.add(qubit_one); + self.add(qubit_two); + self.entangle(&qubit_one.index, &qubit_two.index); } - pub fn X(&mut self, radii: i64) { self.state.X(radii) } + /// Adds this qubit to the cluster. + pub fn add(&self, qubit: &Ptr) { + if !self.qubits.contains_key(&qubit.index) { + with_mutable_self!(self.qubits.insert(qubit.index, qubit.clone())); + } + } - pub fn Y(&mut self, radii: i64) { self.state.Y(radii) } + /// 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); + } + } + } - pub fn Z(&mut self, radii: i64) { self.state.Z(radii) } + pub fn X(&self, qubit: &Ptr, radians: &f64) { + self.add(qubit); + with_mutable!(qubit.X(radians)) + } - /// Sets that this is a measure point with no modifications. - pub fn measure(&mut self) { self.collapsed = true; } -} + pub fn Y(&self, qubit: &Ptr, radians: &f64) { + self.add(qubit); + with_mutable!(qubit.Y(radians)) + } -#[derive(Clone)] -pub struct ClusterState { - clustered_state: QuantumState, - entanglement: Vec, - - /// History of collapsed states. Key is counter, results are target qubit and its exact history. - // TODO: Pointer to avoid mutability. - collapse_history: Ptr>, - metadata: Ptr -} + pub fn Z(&self, qubit: &Ptr, radians: &f64) { + self.add(qubit); + with_mutable!(qubit.Z(radians)) + } -impl ClusterState { - pub fn new(meta: &Ptr) -> ClusterState { - ClusterState { - clustered_state: QuantumState::new(meta), - entanglement: Vec::new(), - collapse_history: Ptr::from(HashMap::new()), - metadata: meta.clone() - } + pub fn CX(&self, control: &Ptr, target: &Ptr, radians: &f64) { + self.add_then_entangle(control, target); + with_mutable!(target.CX(&control.index, radians)) } - pub fn measure(&self, target: &i64) { - let cstate = &self.clustered_state; - self.clustered_state.measure(target); + pub fn CZ(&self, control: &Ptr, target: &Ptr, radians: &f64) { + self.add_then_entangle(control, target); + with_mutable!(target.CZ(&control.index, radians)) + } - let graph = &cstate.state_graph; - let entry = with_mutable!(graph.remove(target).unwrap()); - with_mutable_self!(self - .collapse_history - .insert(self.metadata.counter, (*target, entry))); + pub fn CY(&self, control: &Ptr, target: &Ptr, radians: &f64) { + self.add_then_entangle(control, target); + with_mutable!(target.CY(&control.index, radians)) } - pub fn X(&self, radii: i64, index: &i64) { self.clustered_state.X(radii, index); } + pub fn SWAP(&mut self, left: &i64, right: &i64) { + if self.qubits.contains_key(left) && self.qubits.contains_key(right) { + let mut qubit_one = self.qubits.remove(left).unwrap(); + let mut qubit_two = self.qubits.remove(left).unwrap(); + let first_index = qubit_one.index; + let second_index = qubit_two.index; + + // Just merge indexes so we can deal with the point where entangled qubits reference both + // our swapped qubits. + let mut entanglements = qubit_one.entangled_with().collect::>(); + for index in qubit_two.entangled_with() { + entanglements.insert(index); + } - pub fn Y(&self, radii: i64, index: &i64) { self.clustered_state.Y(radii, index); } + // Go through each tangle and swap target indexes. + for index in entanglements { + let target_qubit = self.qubits.get(index).unwrap(); + let first_tangle = with_mutable!(target_qubit.tangles.remove(&first_index)); + let second_tangle = with_mutable!(target_qubit.tangles.remove(&second_index)); + + if let Some(mut tangle) = first_tangle { + if tangle.bottom_right == first_index { + tangle.bottom_right = second_index; + } else { + tangle.upper_left = second_index; + } - pub fn Z(&self, radii: i64, index: &i64) { self.clustered_state.Z(radii, index); } + with_mutable!(target_qubit.tangles.insert(second_index, tangle)); + } + + if let Some(mut tangle) = second_tangle { + if tangle.bottom_right == second_index { + tangle.bottom_right = first_index; + } else { + tangle.upper_left = first_index; + } - pub fn entangle(&mut self, rel: ClusterRelationship) { self.entanglement.push(rel); } + with_mutable!(target_qubit.tangles.insert(first_index, tangle)); + } + } + + // Then just swap the designation around. + qubit_one.index = second_index; + qubit_two.index = first_index; + self.qubits.insert(qubit_one.index, qubit_one); + self.qubits.insert(qubit_two.index, qubit_two); + } + } +} - pub fn spans(&self) -> HashSet { - self - .clustered_state - .state_graph - .keys() - .copied() - .collect::>() +impl Display for EntanglementCluster { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.write_str("((\n"); + f.write_str( + &self + .qubits + .values() + .map(|val| val.stringify(1).join("")) + .collect::>() + .join("") + ); + f.write_str(")),\n") } } -/// TODO: Swap to more matrix-y representation now. +type GateFragment = MatrixFragment; + +/// Matrix which can be applied to a state fragment. #[derive(Clone)] -pub struct SpherePoint { - amplitude: i64, - phase: i64 +pub struct MatrixFragment { + matrix: Array2>, + affected_qubits: i32 } -impl SpherePoint { - pub fn new() -> SpherePoint { - SpherePoint { - amplitude: 0, - phase: 0 +impl MatrixFragment { + pub fn new(matrix: Array2>, affected_qubits: i32) -> MatrixFragment { + MatrixFragment { + matrix, + affected_qubits } } - pub fn with_X(radii: i64) -> SpherePoint { - let mut sp = SpherePoint::new(); - sp.X(radii); - sp + #[rustfmt::skip] + pub fn id() -> MatrixFragment { + MatrixFragment { + matrix: array![ + [C!(1.0, 0.), C!(0.0, 0.)], + [C!(0.0, 0.), C!(1.0, 0.)] + ], + affected_qubits: 1 + } } - pub fn with_Y(radii: i64) -> SpherePoint { - let mut sp = SpherePoint::new(); - sp.Y(radii); - sp + /// Multiplies current matrix by ID, expanding it to fit more qubits. + pub fn expand(&self) -> MatrixFragment { self * &MatrixFragment::id() } + + #[rustfmt::skip] + pub fn X(radians: &f64) -> MatrixFragment { + MatrixFragment { + matrix: array![ + [C!(0.0, 0.), C!(1.0, 0.)], + [C!(1.0, 0.), C!(0.0, 0.)] + ], + affected_qubits: 1 + } } - pub fn with_Z(radii: i64) -> SpherePoint { - let mut sp = SpherePoint::new(); - sp.Z(radii); - sp + #[rustfmt::skip] + pub fn Y(radians: &f64) -> MatrixFragment { + MatrixFragment { + matrix: array![ + [C!(0.0, 0.), C!(-1.0_f64.sqrt(), 0.)], + [C!(1.0_f64.sqrt(), 0.), C!(0.0, 0.)] + ], + affected_qubits: 1 + } } - pub fn X(&mut self, radii: i64) { self.amplitude = (self.amplitude + radii) % 360 } - - pub fn Y(&mut self, radii: i64) { self.phase = (self.phase + radii) % 360 } - - // TODO: wrong, fix later. - pub fn Z(&mut self, radii: i64) { - let ratio = radii % 360; + #[rustfmt::skip] + pub fn Z(radians: &f64) -> MatrixFragment { + MatrixFragment { + matrix: array![ + [C!(1.0, 0.), C!(0.0, 0.)], + [C!(0.0, 0.), C!(-1.0, 0.)] + ], + affected_qubits: 1 + } + } + #[rustfmt::skip] + pub fn CX(radians: &f64) -> MatrixFragment { + MatrixFragment { + matrix: array![ + [C!(1.0, 0.), C!(0.0, 0.), C!(0.0, 0.), C!(0.0, 0.)], + [C!(0.0, 0.), C!(1.0, 0.), C!(0.0, 0.), C!(0.0, 0.)], + [C!(0.0, 0.), C!(0.0, 0.), C!(0.0, 0.), C!(*radians, 0.)], + [C!(0.0, 0.), C!(0.0, 0.), C!(*radians, 0.), C!(0.0, 0.)] + ], + affected_qubits: 2 + } + } - if radii == 0 { - return; + #[rustfmt::skip] + pub fn CZ(radians: &f64) -> MatrixFragment { + MatrixFragment { + matrix: array![ + [C!(1.0, 0.), C!(0.0, 0.), C!(0.0, 0.), C!(0.0, 0.)], + [C!(0.0, 0.), C!(1.0, 0.), C!(0.0, 0.), C!(0.0, 0.)], + [C!(0.0, 0.), C!(0.0, 0.), C!(*radians, 0.), C!(0.0, 0.)], + [C!(0.0, 0.), C!(0.0, 0.), C!(0.0, 0.), C!(-*radians, 0.)] + ], + affected_qubits: 2 } + } - // Shortcircuit on rotation poles. - if (self.amplitude == 90 || self.amplitude == 270) && (self.phase == 0 || self.phase == 180) { - return; + #[rustfmt::skip] + pub fn CY(radians: &f64) -> MatrixFragment { + MatrixFragment { + matrix: array![ + [C!(1.0, 0.), C!(0.0, 0.), C!(0.0, 0.), C!(0.0, 0.)], + [C!(0.0, 0.), C!(1.0, 0.), C!(0.0, 0.), C!(0.0, 0.)], + [C!(0.0, 0.), C!(0.0, 0.), C!(0.0, 0.), C!(-*radians, 0.)], + [C!(0.0, 0.), C!(0.0, 0.), C!(*radians, 0.), C!(0.0, 0.)] + ], + affected_qubits: 2 } + } - let phase = self.phase; - let amp = self.amplitude; - - if radii == 90 { - self.phase = amp; - self.amplitude = phase; - } else if radii == 180 { - self.phase = -amp % 360; - self.amplitude = -phase % 360; - } else if radii == 270 { - self.phase = -phase % 360; - self.amplitude = -amp % 360; - } else { - panic!("Irregular Y rotation added to prediction algorithm. Unsupported right now.") + #[rustfmt::skip] + pub fn SWAP() -> MatrixFragment { + MatrixFragment { + matrix: array![ + [C!(1.0, 0.), C!(0.0, 0.), C!(0.0, 0.), C!(0.0, 0.)], + [C!(0.0, 0.), C!(0.0, 0.), C!(1.0, 0.), C!(0.0, 0.)], + [C!(0.0, 0.), C!(1.0, 0.), C!(0.0, 0.), C!(0.0, 0.)], + [C!(0.0, 0.), C!(0.0, 0.), C!(0.0, 0.), C!(1.0, 0.)] + ], + affected_qubits: 2 } } } -impl Default for SpherePoint { - fn default() -> Self { SpherePoint::new() } +fn stringified_matrix_lines(matrix: &Array2>) -> Vec { + let mut result = Vec::new(); + let dimensions = matrix.dim(); + if dimensions == (2, 2) { + result.push(format!( + "[{}, {}]", + matrix.get((0, 0)).unwrap(), + matrix.get((0, 1)).unwrap() + )); + result.push(format!( + "[{}, {}]", + matrix.get((1, 0)).unwrap(), + matrix.get((1, 1)).unwrap() + )); + } else if dimensions == (4, 4) { + result.push(format!( + "[{}, {}, {}, {}]", + matrix.get((0, 0)).unwrap(), + matrix.get((0, 1)).unwrap(), + matrix.get((0, 2)).unwrap(), + matrix.get((0, 3)).unwrap() + )); + result.push(format!( + "[{}, {}, {}, {}]", + matrix.get((1, 0)).unwrap(), + matrix.get((1, 1)).unwrap(), + matrix.get((1, 2)).unwrap(), + matrix.get((1, 3)).unwrap() + )); + result.push(format!( + "[{}, {}, {}, {}]", + matrix.get((2, 0)).unwrap(), + matrix.get((2, 1)).unwrap(), + matrix.get((2, 2)).unwrap(), + matrix.get((2, 3)).unwrap() + )); + result.push(format!( + "[{}, {}, {}, {}]", + matrix.get((3, 0)).unwrap(), + matrix.get((3, 1)).unwrap(), + matrix.get((3, 2)).unwrap(), + matrix.get((3, 3)).unwrap() + )); + } else { + panic!("Attempted to print matrix of unexpected dimensions.") + } + result } -#[derive(Clone)] -pub struct ClusterRelationship { - rotation: SpherePoint, - at_counter: i64, - target: i64, - conditioned_on: Vec, - on_value: i8 +fn stringified_matrix(matrix: &Array2>, f: &mut Formatter<'_>) -> std::fmt::Result { + f.write_str(&stringified_matrix_lines(matrix).join("\n")) } -impl ClusterRelationship { - pub fn new( - rotation: SpherePoint, at_counter: i64, target: i64, conditioned_on: &Vec, on_value: i8 - ) -> ClusterRelationship { - ClusterRelationship { - rotation, - at_counter, - target, - conditioned_on: conditioned_on.clone(), - on_value - } +impl Display for MatrixFragment { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { stringified_matrix(&self.matrix, f) } +} + +impl Mul for MatrixFragment { + type Output = MatrixFragment; + + fn mul(self, rhs: Self) -> Self::Output { + MatrixFragment::new( + self.matrix * rhs.matrix, + self.affected_qubits + rhs.affected_qubits + ) } } -/// Collection representing a quantum state with qubits identified by index. -#[derive(Clone)] -pub struct QuantumState { - metadata: Ptr, +impl Mul for &MatrixFragment { + type Output = MatrixFragment; - /// Key = index, Value = state history. - state_graph: Ptr> + fn mul(self, rhs: Self) -> Self::Output { + MatrixFragment::new( + &self.matrix * &rhs.matrix, + self.affected_qubits + rhs.affected_qubits + ) + } } -impl QuantumState { - pub fn new(meta: &Ptr) -> QuantumState { - let collection = QuantumState { - state_graph: Ptr::from(HashMap::default()), - metadata: meta.clone() - }; +impl Mul for &mut MatrixFragment { + type Output = MatrixFragment; - // If we're the root collection in the hierarchy just mark us as such. - if Ptr::is_null(&meta.root) { - with_mutable!(meta.root = Ptr::from(collection.borrow())); - } - collection + fn mul(self, rhs: Self) -> Self::Output { + MatrixFragment::new( + &self.matrix * &rhs.matrix, + self.affected_qubits + rhs.affected_qubits + ) } +} - pub fn get_history(&self, index: &i64) -> &mut StateHistory { - if let Some(qt) = with_mutable_self!(self.state_graph.get_mut(index)) { - qt - } else { - let timeline = StateHistory::new(&self.metadata, index); - with_mutable_self!(self.state_graph.insert(*index, timeline)); - with_mutable_self!(self.state_graph.get_mut(index).unwrap()) +// While there is no distinction it's better to define what the types mean, even if the generic +// structures are the same. +type QubitFragment = StateFragment; +type EntangledFragment = StateFragment; + +/// Composite enum for matrix operations to be able to automatically expand when used against +/// smaller ones. +#[derive(Clone)] +pub struct StateFragment { + matrix: Array2>, + + /// This can also be interpreted as qubits-affected when the fragment is considered a gate. + represented_qubits: i32 +} + +impl StateFragment { + #[rustfmt::skip] + pub fn DefaultQubit() -> QubitFragment { + StateFragment { + matrix: array![ + [C!(1.0, 0.0), C!(0.0, 0.0)], + [C!(0.0, 0.0), C!(0.0, 0.0)] + ], + represented_qubits: 1 } } - pub fn X(&self, radii: i64, target: &i64) { - let qt = self.get_history(target); - qt.X(radii); + /// Creates a state fragment to represent entanglement between 2 qubits. Needs setup with + /// initial qubit values. + #[rustfmt::skip] + pub fn DefaultEntangled() -> EntangledFragment { + StateFragment { + matrix: array![ + [C!(1.0, 0.0), C!(0.0, 0.0), C!(0.0, 0.0), C!(0.0, 0.0)], + [C!(0.0, 0.0), C!(0.0, 0.0), C!(0.0, 0.0), C!(0.0, 0.0)], + [C!(0.0, 0.0), C!(0.0, 0.0), C!(0.0, 0.0), C!(0.0, 0.0)], + [C!(0.0, 0.0), C!(0.0, 0.0), C!(0.0, 0.0), C!(0.0, 0.0)] + ], + represented_qubits: 2 + } } - pub fn Y(&self, radii: i64, target: &i64) { - let qt = self.get_history(target); - qt.Y(radii); + pub fn EntangledFromExisting( + top_left: &Ptr, bottom_right: &Ptr + ) -> EntangledFragment { + if top_left.represented_qubits != 1 || bottom_right.represented_qubits != 1 { + panic!("To create an entangled state both arguments must be qubits.") + } + + let zero_zero = *top_left.matrix.get((0, 0)).unwrap(); + let zero_one = *top_left.matrix.get((0, 1)).unwrap(); + let one_zero = *top_left.matrix.get((1, 0)).unwrap(); + let one_one = *top_left.matrix.get((1, 1)).unwrap(); + + let two_two = *bottom_right.matrix.get((0, 0)).unwrap(); + let two_three = *bottom_right.matrix.get((0, 1)).unwrap(); + let three_two = *bottom_right.matrix.get((1, 0)).unwrap(); + let three_three = *bottom_right.matrix.get((1, 1)).unwrap(); + StateFragment { + matrix: array![ + [zero_zero, zero_one, C!(0.0, 0.0), C!(0.0, 0.0)], + [one_zero, one_one, C!(0.0, 0.0), C!(0.0, 0.0)], + [C!(0.0, 0.0), C!(0.0, 0.0), two_two, two_three], + [C!(0.0, 0.0), C!(0.0, 0.0), three_two, three_three] + ], + represented_qubits: 2 + } } - pub fn Z(&self, radii: i64, target: &i64) { - let qt = self.get_history(target); - qt.Z(radii); + pub fn apply(&mut self, gate: &MatrixFragment) -> Option { + // If a fragment is larger than us we just ignore it, otherwise try and expand the gate to + // fit the state size. + if gate.affected_qubits < self.represented_qubits { + let gate = &gate.expand(); + } + + if self.represented_qubits != gate.affected_qubits { + return Some(String::from("Can't expand fragment to size of the state.")); + } + + self.matrix.mul_assign(&gate.matrix); + None } +} + +impl Display for StateFragment { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { stringified_matrix(&self.matrix, f) } +} + +#[derive(Clone)] +pub struct SolverConfig { + active: bool +} + +impl SolverConfig { + fn new(active: bool) -> SolverConfig { SolverConfig { active } } - pub fn CX(&self, radii: i64, target: &i64, conditioned_on: &Vec, result: i8) { - let qt = self.get_history(target); - qt.CX(radii, conditioned_on, result); + fn off() -> SolverConfig { SolverConfig::new(false) } + + fn on() -> SolverConfig { SolverConfig::new(true) } + + fn with_config(config: &Ptr) -> SolverConfig { + SolverConfig::new(config.solver_active) } +} + +#[derive(Clone)] +pub struct SolverResult {} + +impl SolverResult { + pub fn new() -> SolverResult { SolverResult {} } +} - pub fn CY(&self, radii: i64, target: &i64, conditioned_on: &Vec, result: i8) { - let qt = self.get_history(target); - qt.CY(radii, conditioned_on, result); +/// Acts as a pseudo-state that allows for partial circuit solving and value introspection. +pub struct QuantumSolver { + qubits: Ptr>>, + clusters: Ptr>>, + trace_module: Ptr +} + +impl QuantumSolver { + pub fn new() -> QuantumSolver { + QuantumSolver { + qubits: Ptr::from(HashMap::default()), + clusters: Ptr::from(HashMap::default()), + trace_module: Ptr::from(TracingModule::default()) + } } - pub fn CZ(&self, radii: i64, target: &i64, conditioned_on: &Vec, result: i8) { - let qt = self.get_history(target); - qt.CZ(radii, conditioned_on, result); + fn is_tracing(&self) -> bool { self.trace_module.has(ActiveTracers::Solver) } + + pub fn with_trace(trace_module: Ptr) -> QuantumSolver { + QuantumSolver { + qubits: Ptr::from(HashMap::default()), + clusters: Ptr::from(HashMap::default()), + trace_module + } } - pub fn swap(&self, first: &i64, second: &i64) { - let left_history = self.get_history(first); - let right_history = self.get_history(second); + /// Gets a qubit, or adds a default one at this index if it doesn't exist. + fn qubit_for(&self, index: &i64) -> &Ptr { + if let Some(qubit) = self.qubits.get(index) { + qubit + } else { + with_mutable_self!(self + .qubits + .insert(index.clone(), Ptr::from(AnalysisQubit::with_index(index)))); + self.qubits.get(&index).unwrap() + } + } - let left_state = left_history.state_of(); - let right_state = right_history.state_of(); + /// Gets the cluster for this index. Inserts the qubit into both solver and cluster, creating a + /// new cluster if required. Don't use this if you only want to fetch a cluster without modifying + /// it. + fn cluster_for(&self, index: &i64) -> &Ptr { + let cluster = if let Some(cluster) = with_mutable_self!(self.clusters.get_mut(index)) { + cluster + } else { + with_mutable_self!(self + .clusters + .insert(index.clone(), Ptr::from(EntanglementCluster::new()))); + self.clusters.get(&index).unwrap() + }; - let op_counter = with_mutable_self!(self.metadata.next_counter()); - match left_state { - StateElement::Single(single) => { - right_history.add( - op_counter, - StateElement::Single(single.clone_with_counter(&op_counter)) - ); - } - StateElement::Cluster(cluster) => { - right_history.add(op_counter, StateElement::Cluster(cluster.clone())); - } + if !cluster.contains_qubit(index) { + cluster.add(self.qubit_for(index)); } - match right_state { - StateElement::Single(single) => { - left_history.add( - op_counter, - StateElement::Single(single.clone_with_counter(&op_counter)) - ); - } - StateElement::Cluster(cluster) => { - left_history.add(op_counter, StateElement::Cluster(cluster.clone())); + cluster + } + + fn merge_clusters(&self, merger: Vec<&i64>, mergee: &i64) -> &Ptr { + let target_cluster = self.cluster_for(&mergee); + for index in merger { + let cluster = self.cluster_for(&index); + if !cluster.contains_qubit(&mergee) { + target_cluster.merge(cluster); + target_cluster.add_then_entangle(self.qubit_for(&index), target_cluster.qubit_for(&mergee)); + + // Remove the previous cluster, it's no longer needed, replace with new merged one. + with_mutable_self!(self.clusters.insert(index.clone(), target_cluster.clone())); } } + target_cluster } - pub fn measure(&self, target: &i64) { - let state = self.get_history(target); - state.measure(); - } + pub fn reset(&self, qb: &Qubit) {} - pub fn reset(&self, target: &i64) { - let state = self.get_history(target); - state.reset(); - } -} + pub fn measure(&self, qbs: &Qubit) {} -pub struct Metadata { - /// Current program-counter we're on. - counter: i64, + pub fn X(&self, qb: &Qubit, radians: &f64) { self.qubit_for(&qb.index).X(radians) } - /// Root collection in our hierarchy. Can be used for top-level searches and queries. - root: Ptr -} + pub fn Y(&self, qb: &Qubit, radians: &f64) { self.qubit_for(&qb.index).Y(radians) } -impl Metadata { - pub fn new() -> Metadata { - Metadata { - counter: 0, - root: Ptr::default() + pub fn Z(&self, qb: &Qubit, radians: &f64) { self.qubit_for(&qb.index).Z(radians) } + + pub fn CX(&self, controls: &Vec, target: &Qubit, radians: &f64) { + let target_cluster = self.merge_clusters( + controls.iter().map(|val| &val.index).collect::>(), + &target.index + ); + + let spans = target_cluster.spans().collect::>(); + for qb in controls { + target_cluster.CX( + target_cluster.qubit_for(&qb.index), + target_cluster.qubit_for(&target.index), + radians + ); + } + + if target_cluster.disentangle(&target.index) { + with_mutable_self!(self.clusters.remove(&target.index)); } } - pub fn next_counter(&mut self) -> i64 { - self.counter += 1; - self.counter + pub fn CY(&self, controls: &Vec, target: &Qubit, radians: &f64) { + let target_cluster = self.merge_clusters( + controls.iter().map(|val| &val.index).collect::>(), + &target.index + ); + for qb in controls { + target_cluster.CY( + target_cluster.qubit_for(&qb.index), + target_cluster.qubit_for(&target.index), + radians + ); + } + + if target_cluster.disentangle(&target.index) { + with_mutable_self!(self.clusters.remove(&target.index)); + } } -} -/// Transform radians into degrees for easy debugging for now. -/// TODO: Likely change form later. -fn conv(radians: &f64) -> i64 { (radians * 180.0 / f64::PI()) as i64 } + pub fn CZ(&self, controls: &Vec, target: &Qubit, radians: &f64) { + let target_cluster = self.cluster_for(&target.index); + for qb in controls { + let target_cluster = self.merge_clusters( + controls.iter().map(|val| &val.index).collect::>(), + &target.index + ); -pub struct QuantumStatePredictor { - state: QuantumState -} + target_cluster.CZ( + target_cluster.qubit_for(&qb.index), + target_cluster.qubit_for(&target.index), + radians + ); + } -impl QuantumStatePredictor { - pub fn new() -> QuantumStatePredictor { - QuantumStatePredictor { - state: QuantumState::new(&Ptr::from(Metadata::new())) + if target_cluster.disentangle(&target.index) { + with_mutable_self!(self.clusters.remove(&target.index)); } } - pub fn add(&self, op: Ptr) { - match op.deref() { - QuantumOperations::Reset(qbs) => { - for qubit in qbs { - self.state.reset(&qubit.index); - } - } - QuantumOperations::U(qb, theta, phi, lambda) => { - self.state.Z(qb.index, &conv(lambda)); - self.state.Y(qb.index, &conv(theta)); - self.state.Z(qb.index, &conv(phi)); - } - QuantumOperations::X(qb, radians) => { - self.state.X(qb.index, &conv(radians)); - } - QuantumOperations::Y(qb, radians) => { - self.state.Y(qb.index, &conv(radians)); + pub fn solve(&self) -> SolverResult { + // We don't worry about printing if we're utterly empty. + if self.is_tracing() { + if self.qubits.is_empty() { + log!(Level::Info, "Nothing to solve."); + } else { + log!(Level::Info, "{}", self.to_string()); } - QuantumOperations::Z(qb, radians) => { - self.state.Z(qb.index, &conv(radians)); + } + SolverResult::new() + } +} + +impl Display for QuantumSolver { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.write_str("\nSolver:\n"); + + let mut covered_qubits = HashSet::new(); + for (key, value) in self.qubits.iter() { + if covered_qubits.contains(key) { + continue; } - QuantumOperations::CX(controls, targets, radians) => self.state.CX( - 180, - &targets.index, - &controls.iter().map(|val| val.index).collect::>(), - 1 - ), - QuantumOperations::CZ(controls, targets, radians) => self.state.CZ( - 180, - &targets.index, - &controls.iter().map(|val| val.index).collect::>(), - 1 - ), - QuantumOperations::CY(controls, targets, radians) => self.state.CY( - 180, - &targets.index, - &controls.iter().map(|val| val.index).collect::>(), - 1 - ), - QuantumOperations::Measure(qbs) => { - for qb in qbs { - self.state.measure(&qb.index); + + if let Some(cluster) = self.clusters.get(key) { + for index in cluster.spans() { + covered_qubits.insert(index); } + + f.write_str("[Cluster]\n"); + f.write_str(&cluster.to_string()); + } else { + covered_qubits.insert(key); + f.write_str("[Qubit]\n"); + f.write_str(&value.to_string()); } - QuantumOperations::Initialize() | QuantumOperations::Id(_) => {} } + + f.write_str("") } } @@ -566,7 +871,8 @@ pub struct QuantumProjection { engines: Ptr, instructions: Vec>, cached_result: Option, - cached_filtered: HashMap + cached_filtered: HashMap, + solver_config: SolverConfig } /// A for-now list of linear gates and hardware operations that we can store and send to our @@ -680,19 +986,22 @@ impl QuantumProjection { instructions: Vec::new(), trace_module: Ptr::from(TracingModule::new()), cached_result: None, - cached_filtered: HashMap::new() + cached_filtered: HashMap::new(), + solver_config: SolverConfig::off() } } - pub fn with_tracer( - engines: &Ptr, module: &Ptr + pub fn with_tracer_and_config( + engines: &Ptr, tracing_module: &Ptr, + config: &Ptr ) -> QuantumProjection { QuantumProjection { engines: engines.clone(), instructions: Vec::new(), - trace_module: module.clone(), + trace_module: tracing_module.clone(), cached_result: None, - cached_filtered: HashMap::new() + cached_filtered: HashMap::new(), + solver_config: SolverConfig::with_config(config) } } @@ -751,11 +1060,59 @@ impl QuantumProjection { true } - /// Is our projection simple enough to use algorithmic prediction? - pub fn can_predict(&self) -> bool { false } - /// Perform algorithmic state value prediction. - fn predict(&mut self) -> AnalysisResult { AnalysisResult::one() } + fn solve(&mut self) -> AnalysisResult { + if !self.solver_config.active { + return AnalysisResult::empty(); + } + + let start = Instant::now(); + let qsolver = QuantumSolver::with_trace(self.trace_module.clone()); + for inst in self.instructions.iter() { + match inst.deref() { + QuantumOperations::Initialize() => {} + QuantumOperations::Id(_) => {} + QuantumOperations::Reset(qbs) => { + for qubit in qbs { + qsolver.reset(qubit); + } + } + QuantumOperations::U(qb, theta, phi, lambda) => { + qsolver.Z(qb, lambda); + qsolver.Y(qb, phi); + qsolver.Z(qb, theta); + } + QuantumOperations::X(qb, radians) => { + qsolver.X(qb, radians); + } + QuantumOperations::Y(qb, radians) => { + qsolver.Y(qb, radians); + } + QuantumOperations::Z(qb, radians) => { + qsolver.Z(qb, radians); + } + QuantumOperations::CX(controls, targets, radians) => { + qsolver.CX(controls, targets, radians); + } + QuantumOperations::CZ(controls, targets, radians) => { + qsolver.CZ(controls, targets, radians); + } + QuantumOperations::CY(controls, targets, radians) => { + qsolver.CY(controls, targets, radians); + } + QuantumOperations::Measure(qbs) => { + for qb in qbs { + qsolver.measure(qb); + } + } + } + } + + let result = AnalysisResult::from_solver_result(qsolver.solve()); + let took = start.elapsed(); + log!(Level::Info, "Solving took {}ms.", took.as_millis()); + result + } /// Get results for this entire state. pub fn results(&mut self) -> AnalysisResult { self.concretize().clone() } @@ -836,9 +1193,9 @@ impl QuantumProjection { return self.cached_result.as_ref().unwrap(); } - let query_result = if self.can_predict() { - self.predict() - } else { + let mut query_result = self.solve(); + if query_result.is_empty() { + let start = Instant::now(); let features = QuantumFeatures::default(); let runtime = self.engines.find_capable_QPU(&features).unwrap_or_else(|| { panic!( @@ -888,8 +1245,10 @@ impl QuantumProjection { } } - runtime.execute(&builder) - }; + query_result = runtime.execute(&builder); + let took = start.elapsed(); + log!(Level::Info, "QPU execution took {}ms.", took.as_millis()); + } self.cached_result = Some(query_result); @@ -925,7 +1284,8 @@ impl Clone for QuantumProjection { engines: self.engines.clone(), instructions: self.instructions.clone(), cached_result: self.cached_result.clone(), - cached_filtered: self.cached_filtered.clone() + cached_filtered: self.cached_filtered.clone(), + solver_config: self.solver_config.clone() } } } @@ -957,6 +1317,8 @@ impl AnalysisResult { AnalysisResult { distribution } } + pub fn from_solver_result(res: SolverResult) -> AnalysisResult { AnalysisResult::empty() } + pub fn is_empty(&self) -> bool { self.size() == 0 } /// Return size of the results register in qubits. diff --git a/src/rasqal/src/config.rs b/src/rasqal/src/config.rs index fe85d5c..3c1fcf4 100644 --- a/src/rasqal/src/config.rs +++ b/src/rasqal/src/config.rs @@ -3,7 +3,14 @@ use crate::runtime::ActiveTracers; pub struct RasqalConfig { /// How many steps the symbolic executor is allowed to make before failing. pub step_count_limit: Option, - pub debug_tracers: ActiveTracers + + /// Currently active debug tracers. + pub debug_tracers: ActiveTracers, + + /// Whether projection circuit solving should be activated. If this is true every circuit will + /// be included into the solver to help run it. Can drastically change what sort of circuits are + /// run. + pub solver_active: bool } impl RasqalConfig { @@ -14,13 +21,35 @@ impl RasqalConfig { pub fn trace_projections(&mut self) { self.debug_tracers.insert(ActiveTracers::Projections); } pub fn trace_graphs(&mut self) { self.debug_tracers.insert(ActiveTracers::Graphs); } + + pub fn with_trace_runtime(mut self) -> RasqalConfig { self.debug_tracers.insert(ActiveTracers::Runtime); self } + + pub fn with_trace_projections(mut self) -> RasqalConfig { self.debug_tracers.insert(ActiveTracers::Projections); self } + + pub fn with_trace_graphs(mut self) -> RasqalConfig { self.debug_tracers.insert(ActiveTracers::Graphs); self } + + pub fn with_step_count_limit(mut self, count: i64) -> RasqalConfig { + self.step_count_limit = Some(count); + self + } + + pub fn with_trace_solver(mut self) -> RasqalConfig { + self.debug_tracers.insert(ActiveTracers::Solver); + self + } + + pub fn with_activate_solver(mut self) -> RasqalConfig { + self.solver_active = true; + self + } } impl Default for RasqalConfig { fn default() -> Self { RasqalConfig { step_count_limit: None, - debug_tracers: ActiveTracers::empty() + debug_tracers: ActiveTracers::empty(), + solver_active: false } } } diff --git a/src/rasqal/src/evaluator.rs b/src/rasqal/src/evaluator.rs index 7260c81..3bb8508 100644 --- a/src/rasqal/src/evaluator.rs +++ b/src/rasqal/src/evaluator.rs @@ -1399,8 +1399,7 @@ impl QIREvaluator { } // Output recording doesn't matter for us. - "__quantum__rt__tuple_record_output" | "__quantum__rt__array_record_output" => { - } + "__quantum__rt__tuple_record_output" | "__quantum__rt__array_record_output" => {} // Bigint support that hopefully we'll just be able to ignore. "__quantum__rt__bigint_add" diff --git a/src/rasqal/src/execution.rs b/src/rasqal/src/execution.rs index 559f659..5db531f 100644 --- a/src/rasqal/src/execution.rs +++ b/src/rasqal/src/execution.rs @@ -208,28 +208,29 @@ mod tests { use std::fs::canonicalize; /// Just run a QIR file to make sure it parses and returns the value. - fn run(path: &str) -> Option> { - run_with_config(path, RasqalConfig::default()).expect("Execution failed.") + fn run(path: &str) -> Option> { run_with_config(path, RasqalConfig::default()) } + + fn run_with_args(path: &str, args: &Vec) -> Option> { + run_with_args_and_config(path, args, RasqalConfig::default()).expect("Execution failed.") } - fn run_with_args(path: &str, args: &Vec) -> Result>, String> { - let relative_path = canonicalize(path).unwrap(); - let path = relative_path.to_str().unwrap(); + fn run_with_config(path: &str, config: RasqalConfig) -> Option> { + run_with_args_and_config(path, &Vec::new(), config).expect("Execution failed.") + } - let runtimes = Ptr::from(RuntimeCollection::from(&Ptr::from( - IntegrationRuntime::default() - ))); + fn fail(path: &str) -> Option { fail_with_config(path, RasqalConfig::default()) } + + fn fail_with_args(path: &str, args: &Vec) -> Option { + run_with_args_and_config(path, args, RasqalConfig::default()).err() + } - run_file( - path, - args, - runtimes.borrow(), - None, - &Ptr::from(RasqalConfig::default()) - ) + fn fail_with_config(path: &str, config: RasqalConfig) -> Option { + run_with_args_and_config(path, &Vec::new(), config).err() } - fn run_with_config(path: &str, config: RasqalConfig) -> Result>, String> { + fn run_with_args_and_config( + path: &str, args: &Vec, config: RasqalConfig + ) -> Result>, String> { let relative_path = canonicalize(path).unwrap(); let path = relative_path.to_str().unwrap(); @@ -237,17 +238,13 @@ mod tests { IntegrationRuntime::default() ))); - run_file( - path, - &Vec::new(), - runtimes.borrow(), - None, - &Ptr::from(config) - ) + run_file(path, args, runtimes.borrow(), None, &Ptr::from(config)) } #[test] - fn execute_qaoa() { run("../tests/qsharp/qaoa/qir/qaoa.ll"); } + fn execute_qaoa() { + run("../tests/qsharp/qaoa/qir/qaoa.ll"); + } #[test] fn execute_simplified_oracle_generator() { @@ -255,7 +252,9 @@ mod tests { } #[test] - fn execute_oracle_generator() { run("../tests/qsharp/oracle-generator/qir/oracle-generator.ll"); } + fn execute_oracle_generator() { + run("../tests/qsharp/oracle-generator/qir/oracle-generator.ll"); + } #[test] fn execute_minified_oracle_generator() { @@ -272,8 +271,8 @@ mod tests { fn test_step_count() { let mut config = RasqalConfig::default(); config.step_count_limit(2); - let results = run_with_config("../tests/files/qir/unrestricted_bell.ll", config); - assert!(results.is_err()) + let results = fail_with_config("../tests/files/qir/unrestricted_bell.ll", config); + assert!(results.is_some()) } #[test] diff --git a/src/rasqal/src/runtime.rs b/src/rasqal/src/runtime.rs index d1efe9c..eacd113 100644 --- a/src/rasqal/src/runtime.rs +++ b/src/rasqal/src/runtime.rs @@ -290,14 +290,15 @@ impl Expression { } bitflags! { - /// Flags enabling various runtime tracing operations. Turning these on will drastically - /// affect performance and should only be done to debug output and issues. - #[derive(Clone)] - pub struct ActiveTracers: u8 { - const Runtime = 1; - const Projections = 1 << 1; - const Graphs = 1 << 2; - } + /// Flags enabling various runtime tracing operations. Turning these on will drastically + /// affect performance and should only be done to debug output and issues. + #[derive(Clone)] + pub struct ActiveTracers: u8 { + const Runtime = 1; + const Projections = 2; + const Graphs = 3; + const Solver = 4; + } } /// Tracing module for runtime for in-depth detailed logging of how our runtime functions. @@ -341,14 +342,14 @@ impl RuntimeConstraints { pub struct QuantumRuntime { engines: Ptr, trace_module: Ptr, - constraints: RuntimeConstraints + config: Ptr } impl QuantumRuntime { pub fn new(engines: &Ptr, config: &Ptr) -> QuantumRuntime { QuantumRuntime { engines: engines.clone(), - constraints: RuntimeConstraints::new(config.step_count_limit), + config: config.clone(), trace_module: Ptr::from(TracingModule::with(config.debug_tracers.clone())) } } @@ -489,7 +490,7 @@ impl QuantumRuntime { let mut seen_nodes = HashSet::new(); loop { context.step_count.add_assign(1); - if let Some(limit) = &self.constraints.step_limit { + if let Some(limit) = &self.config.step_count_limit { if context.step_count.deref() > limit { return Err(String::from(format!( "Execution step count limitation of {limit} exceeded." @@ -1114,9 +1115,10 @@ impl RuntimeContext { // In general running a single projection covers all current qubits, so we // steal the one that's currently active if it's there. let projection = if self.projections.is_empty() { - Ptr::from(QuantumProjection::with_tracer( - self.associated_runtime.engines.borrow(), - self.associated_runtime.trace_module.borrow() + Ptr::from(QuantumProjection::with_tracer_and_config( + &self.associated_runtime.engines, + &self.associated_runtime.trace_module, + &self.associated_runtime.config )) } else { self.projections.values().next().unwrap().clone()