Skip to content

Commit

Permalink
Merge pull request #42 from oqc-community/release/0.1.1
Browse files Browse the repository at this point in the history
Release/0.1.1
  • Loading branch information
chemix-lunacy authored Mar 20, 2024
2 parents ed958e4 + 71829b5 commit a5e7ee3
Show file tree
Hide file tree
Showing 13 changed files with 209 additions and 125 deletions.
1 change: 0 additions & 1 deletion .github/workflows/deploy-wheels.yml
Original file line number Diff line number Diff line change
Expand Up @@ -81,5 +81,4 @@ jobs:
- name: Publish to PyPi
uses: pypa/gh-action-pypi-publish@release/v1
with:
repository-url: https://pypi.org/p/rasqal
packages-dir: dist/
2 changes: 1 addition & 1 deletion features_and_concepts.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,4 +82,4 @@ These will be available only if you enable them explicitly:
1. Quantum state analysis structures for performing indepth static analysis as we go. This powers many other features.
2. Quantum fragment simulation. Finding points in a circuit that if simulated/predicted allow for better optimization or distributed processing.
3. Using our analysis tools and splice/weaving techniques to split up and run large quantum circuits across multiple smaller machines.
community/rasqal/blob/develop/examples.md) for the sorts of code you could send to Munchkin as well as what it returns.
community/rasqal/blob/develop/examples.md) for the sorts of code you could send to Rasqal as well as what it returns.
2 changes: 1 addition & 1 deletion src/rasqal/pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "rasqal"
version = "0.1.0"
version = "0.1.1"
requires-python = ">=3.9"
description = "A dynamically executed quantum-classical hybrid optimizing runtime."
license = { file = "LICENSE" }
Expand Down
57 changes: 28 additions & 29 deletions src/rasqal/src/builders.rs
Original file line number Diff line number Diff line change
Expand Up @@ -217,30 +217,26 @@ impl IntegrationBuilder {
}

macro_rules! python_methods {
(self.$wrapped_obj:ident.$python_gate:ident()) => {
pub fn $python_gate(&self) -> Option<PyResult<&PyAny>> {
if Ptr::is_not_null(&self.$wrapped_obj) {
let pyobj: &PyAny = self.$wrapped_obj.borrow();
let has_gate = pyobj.hasattr(stringify!($python_gate)).unwrap_or(false);
if has_gate {
let func = pyobj.getattr(stringify!($python_gate)).unwrap();
Some(func.call0())
} else { None }
} else { None }
}
};
(self.$wrapped_obj:ident.$python_gate:ident($($var:ident: $ty:ty),*)) => {
pub fn $python_gate(&self, $($var: $ty),*) -> Option<PyResult<&PyAny>> {
if Ptr::is_not_null(&self.$wrapped_obj) {
let pyobj: &PyAny = self.$wrapped_obj.borrow();
let has_gate = pyobj.hasattr(stringify!($python_gate)).unwrap_or(false);
if has_gate {
let func = pyobj.getattr(stringify!($python_gate)).unwrap();
Some(func.call1(($($var),*,)))
} else { None }
} else { None }
}
(self.$wrapped_obj:ident.$python_gate:ident()) => {
pub fn $python_gate(&self) -> Result<&PyAny, String> {
Python::with_gil(|py| {
let target = self.$wrapped_obj.getattr(stringify!($python_gate))
.map_err(|err| err.value(py).to_string())
.expect(format!("'{}' can't be found on {}", stringify!($python_gate), stringify!($wrapped_obj)).as_str());
target.call0().map_err(|err| err.value(py).to_string())
})
}
};
(self.$wrapped_obj:ident.$python_gate:ident($($var:ident: $ty:ty),*)) => {
pub fn $python_gate(&self, $($var: $ty),*) -> Result<&PyAny, String> {
Python::with_gil(|py| {
let target = self.$wrapped_obj.getattr(stringify!($python_gate))
.map_err(|err| err.value(py).to_string())
.expect(format!("'{}' can't be found {}", stringify!($python_gate), stringify!($wrapped_obj)).as_str());
target.call1(($($var),*,)).map_err(|err| err.value(py).to_string())
})
}
}
}

/// Rust wrapper for our Python builders.
Expand All @@ -259,6 +255,13 @@ impl PyBuilderAdaptor {
return Ptr::is_null(self.builder.borrow()) || self.builder.is_none();
}

pub fn ab(&self) -> Result<&PyAny, String> {
let target = self.builder.getattr("ab").expect("'ab' doesn't exist on builder");
Python::with_gil(|py| {
target.call0().map_err(|err| err.value(py).to_string())
})
}

python_methods!(self.builder.x(qubit: i64, radians: f64));
python_methods!(self.builder.y(qubit: i64, radians: f64));
python_methods!(self.builder.z(qubit: i64, radians: f64));
Expand Down Expand Up @@ -352,8 +355,7 @@ impl PythonRuntime {
let result = self
.wrapped
.execute(builder.wrapped.deref())
.expect("Engine doesn't have an execute method.")
.expect("QPU didn't return a result.");
.expect("QPU didn't return a result");

AnalysisResult::new(
result
Expand All @@ -367,8 +369,7 @@ impl PythonRuntime {
self
.wrapped
.create_builder()
.expect("Runtime doesn't have a 'create_builder' method.")
.expect("Couldn't create a builder from runtime.")
.expect("Couldn't create a builder from runtime")
);
Ptr::from(IntegrationBuilder::Python(pybuilder))
}
Expand All @@ -382,7 +383,6 @@ impl PythonRuntime {
self
.wrapped
.has_features(pyfeature)
.expect("Runtime doesn't have a 'has_features' method.")
.map_or(false, |obj| obj.extract().expect("Unable to extract type."))
}
}
Expand Down Expand Up @@ -428,7 +428,6 @@ impl PythonBuilder {
}
}

// TODO: Make sure we propagate Python exceptions for easy debugging.
impl InstructionBuilder for PythonBuilder {
fn measure(&self, qb: &Qubit) -> &Self {
self.wrapped.measure(qb.index);
Expand Down
31 changes: 14 additions & 17 deletions src/rasqal/src/config.rs
Original file line number Diff line number Diff line change
@@ -1,30 +1,27 @@
use crate::runtime::ActiveTracers;

pub struct RasqalConfig {
/// How many steps the symbolic executor is allowed to make before failing.
///
pub step_count_limit: Option<i64>,
pub debug_tracers: ActiveTracers
/// How many steps the symbolic executor is allowed to make before failing.
///
pub step_count_limit: Option<i64>,
pub debug_tracers: ActiveTracers
}

impl RasqalConfig {
pub fn step_count_limit(&mut self, count: i64) {
self.step_count_limit = Some(count);
}

pub fn trace_runtime(&mut self) { self.debug_tracers.insert(ActiveTracers::Runtime); }
pub fn step_count_limit(&mut self, count: i64) { self.step_count_limit = Some(count); }

pub fn trace_projections(&mut self) { self.debug_tracers.insert(ActiveTracers::Projections); }
pub fn trace_runtime(&mut self) { self.debug_tracers.insert(ActiveTracers::Runtime); }

pub fn trace_graphs(&mut self) { self.debug_tracers.insert(ActiveTracers::Graphs); }
pub fn trace_projections(&mut self) { self.debug_tracers.insert(ActiveTracers::Projections); }

pub fn trace_graphs(&mut self) { self.debug_tracers.insert(ActiveTracers::Graphs); }
}

impl Default for RasqalConfig {
fn default() -> Self {
RasqalConfig {
step_count_limit: None,
debug_tracers: ActiveTracers::empty()
}
fn default() -> Self {
RasqalConfig {
step_count_limit: None,
debug_tracers: ActiveTracers::empty()
}
}
}
}
77 changes: 36 additions & 41 deletions src/rasqal/src/exceptions.rs
Original file line number Diff line number Diff line change
@@ -1,60 +1,55 @@
use std::{panic};
use std::panic::{AssertUnwindSafe};
use std::panic;
use std::panic::AssertUnwindSafe;

/// Because we are primarily interfaced with from Python we want to make sure we don't panic
/// and kill the external process. This is also important because we have errors that occur in places
/// that [`Results`] cannot be used, and when we fail other systems should then respond.
///
/// This just wraps the lambda call in [`panic::catch_unwind`] and folds the panic message into
/// the expected error.
pub fn catch_panics<R, F: FnOnce() -> Result<R, String>>(wrapped: F) -> Result<R, String> {
let result = panic::catch_unwind(AssertUnwindSafe(wrapped));
match result {
Ok(thread_result) => {
if let Ok(result) = thread_result {
Ok(result)
} else {
Err(thread_result.err().unwrap())
}
}
Err(panic_value) => {
Err(if let Some(message) = panic_value.downcast_ref::<&str>() {
message.to_string()
} else {
"Unavailable error message.".to_string()
})
}
pub fn catch_panics<R, F: FnOnce() -> Result<R, String>>(wrapped: F) -> Result<R, String> {
let result = panic::catch_unwind(AssertUnwindSafe(wrapped));
match result {
Ok(thread_result) => {
if let Ok(result) = thread_result {
Ok(result)
} else {
Err(thread_result.err().unwrap())
}
}
Err(panic_value) => Err(
if let Some(message) = panic_value.downcast_ref::<String>() {
message.clone()
} else if let Some(message) = panic_value.downcast_ref::<&str>() {
message.to_string()
} else {
"Unavailable error message.".to_string()
})
}
}

#[cfg(test)]
mod tests {
use crate::exceptions::catch_panics;
use crate::exceptions::catch_panics;

#[test]
fn success() {
let result = catch_panics(|| {
Ok("dave")
});
#[test]
fn success() {
let result = catch_panics(|| Ok("dave"));

assert!(result.is_ok() && result.ok().unwrap() == "dave")
}
assert!(result.is_ok() && result.ok().unwrap() == "dave")
}

#[test]
fn panic() {
let result: Result<(), String> = catch_panics(|| {
panic!("Ahhh?!")
});
#[test]
fn panic() {
let result: Result<(), String> = catch_panics(|| panic!("Ahhh?!"));

assert!(result.is_err() && result.err().unwrap().as_str() == "Ahhh?!")
}
assert!(result.is_err() && result.err().unwrap().as_str() == "Ahhh?!")
}

#[test]
fn error() {
let result: Result<(), String> = catch_panics(|| {
Err("Eh.".into())
});
#[test]
fn error() {
let result: Result<(), String> = catch_panics(|| Err("Eh.".into()));

assert!(result.is_err() && result.err().unwrap().as_str() == "Eh.")
}
assert!(result.is_err() && result.err().unwrap().as_str() == "Eh.")
}
}
20 changes: 7 additions & 13 deletions src/rasqal/src/execution.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use crate::evaluator::QIREvaluator;
use crate::features::QuantumFeatures;
use crate::graphs::ExecutableAnalysisGraph;
use crate::instructions::Value;
use crate::runtime::{QuantumRuntime};
use crate::runtime::QuantumRuntime;
use crate::smart_pointers::Ptr;
use crate::with_mutable;
use inkwell::attributes::AttributeLoc;
Expand All @@ -22,29 +22,25 @@ use inkwell::{
OptimizationLevel
};

use std::{ffi::OsStr, path::Path};
use std::ops::Deref;
use crate::config::RasqalConfig;
use crate::exceptions::catch_panics;
use std::ops::Deref;
use std::{ffi::OsStr, path::Path};

/// Executes the file.
pub fn run_file(
path: impl AsRef<Path>, args: &Vec<Value>, runtimes: &Ptr<RuntimeCollection>,
entry_point: Option<&str>, config: &Ptr<RasqalConfig>
) -> Result<Option<Ptr<Value>>, String> {
catch_panics(|| {
run_graph(&parse_file(path, entry_point)?, args, runtimes, config)
})
catch_panics(|| run_graph(&parse_file(path, entry_point)?, args, runtimes, config))
}

pub fn parse_file(
path: impl AsRef<Path>, entry_point: Option<&str>
) -> Result<Ptr<ExecutableAnalysisGraph>, String> {
let context = Context::create();
let module = file_to_module(path, &context)?;
catch_panics(|| {
build_graph_from_module(&module, entry_point)
})
catch_panics(|| build_graph_from_module(&module, entry_point))
}

/// Transforms an LLVM file into an LLVM module.
Expand Down Expand Up @@ -96,9 +92,7 @@ pub fn run_graph(
config: &Ptr<RasqalConfig>
) -> Result<Option<Ptr<Value>>, String> {
let mut runtime = QuantumRuntime::new(runtimes, config);
catch_panics(|| {
runtime.execute(graph, arguments)
})
catch_panics(|| runtime.execute(graph, arguments))
}

/// Top-level collection item that holds information about target runtimes and engines for graphs.
Expand Down Expand Up @@ -196,12 +190,12 @@ pub fn choose_entry_point<'ctx>(
#[cfg(test)]
mod tests {
use crate::builders::IntegrationRuntime;
use crate::config::RasqalConfig;
use crate::execution::{run_file, RuntimeCollection};
use crate::instructions::Value;
use crate::smart_pointers::Ptr;
use std::borrow::Borrow;
use std::fs::canonicalize;
use crate::config::RasqalConfig;

#[test]
fn execute_qaoa() {
Expand Down
4 changes: 2 additions & 2 deletions src/rasqal/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,9 @@ use std::path::Path;

mod analysis;
mod builders;
mod config;
mod evaluator;
mod exceptions;
mod execution;
mod features;
mod graphs;
Expand All @@ -34,8 +36,6 @@ mod instructions;
mod python;
mod runtime;
mod smart_pointers;
mod config;
mod exceptions;

const DEFAULT_LOG_FILE: &str = "rasqal_logs.txt";

Expand Down
6 changes: 2 additions & 4 deletions src/rasqal/src/python.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// Copyright (c) 2024 Oxford Quantum Circuits Ltd

use crate::builders::{IntegrationRuntime, PythonRuntime};
use crate::config::RasqalConfig;
use crate::execution::{parse_file, run_file, run_graph, RuntimeCollection};
use crate::features::QuantumFeatures;
use crate::graphs::ExecutableAnalysisGraph;
Expand All @@ -14,7 +15,6 @@ use pyo3::exceptions::PyValueError;
use pyo3::prelude::*;
use pyo3::types::{PyBool, PyFloat, PyInt, PyList, PyString};
use std::borrow::Borrow;
use crate::config::RasqalConfig;

#[pymodule]
fn _native(_py: Python, m: &PyModule) -> PyResult<()> {
Expand Down Expand Up @@ -157,9 +157,7 @@ impl Executor {

fn trace_graphs(&mut self) { self.config.trace_graphs(); }

fn step_count_limit(&mut self, limit: i64) {
self.config.step_count_limit(limit);
}
fn step_count_limit(&mut self, limit: i64) { self.config.step_count_limit(limit); }

#[allow(clippy::unused_self)]
fn parse_file(&self, file: &str, entry_point: Option<&str>) -> PyResult<Py<Graph>> {
Expand Down
Loading

0 comments on commit a5e7ee3

Please sign in to comment.