diff --git a/crates/lib/src/executable.rs b/crates/lib/src/executable.rs index fcae8dc97..f58b88432 100644 --- a/crates/lib/src/executable.rs +++ b/crates/lib/src/executable.rs @@ -14,7 +14,6 @@ use crate::client::Qcs; use crate::compiler::quilc::{self, CompilerOpts}; use crate::execution_data::{self, ResultData}; use crate::qpu::api::{ExecutionOptions, JobId}; -use crate::qpu::rewrite_arithmetic; use crate::qpu::translation::TranslationOptions; use crate::qpu::ExecutionError; use crate::qvm::http::AddressRequest; @@ -658,9 +657,6 @@ pub enum Error { /// There was a problem when translating the Quil program. #[error("There was a problem translating the Quil program: {0}")] Translation(String), - /// There was a problem when rewriting parameter arithmetic in the Quil program. - #[error("There was a problem rewriting parameter arithmetic in the Quil program: {0}")] - RewriteArithmetic(#[from] rewrite_arithmetic::Error), /// There was a problem when substituting parameters in the Quil program. #[error("There was a problem substituting parameters in the Quil program: {0}")] Substitution(String), @@ -721,9 +717,7 @@ impl From for Error { ExecutionError::Quil(e) => Self::Quil(e), ExecutionError::ToQuil(e) => Self::ToQuil(e), ExecutionError::Compilation { details } => Self::Compilation(details), - ExecutionError::RewriteArithmetic(e) => Self::RewriteArithmetic(e), ExecutionError::RpcqClient(e) => Self::Unexpected(format!("{e:?}")), - ExecutionError::Substitution(message) => Self::Substitution(message), ExecutionError::QpuApi(e) => Self::QpuApiError(e), } } diff --git a/crates/lib/src/qpu/execution.rs b/crates/lib/src/qpu/execution.rs index e8b6e8f87..1cd8bbabf 100644 --- a/crates/lib/src/qpu/execution.rs +++ b/crates/lib/src/qpu/execution.rs @@ -7,21 +7,21 @@ use std::sync::Arc; use std::time::Duration; use quil_rs::program::ProgramError; -use quil_rs::quil::ToQuilError; +use quil_rs::quil::{Quil, ToQuilError}; +use quil_rs::Program; #[cfg(feature = "tracing")] use tracing::trace; use crate::compiler::rpcq; use crate::executable::Parameters; use crate::execution_data::{MemoryReferenceParseError, ResultData}; -use crate::qpu::{rewrite_arithmetic, translation::translate}; +use crate::qpu::translation::translate; use crate::{ExecutionData, JobHandle}; use super::api::{ retrieve_results, submit, ConnectionStrategy, ExecutionOptions, ExecutionOptionsBuilder, }; -use super::rewrite_arithmetic::RewrittenProgram; use super::translation::{EncryptedTranslationResult, TranslationOptions}; use super::QpuResultData; use super::{get_isa, GetIsaError}; @@ -33,7 +33,7 @@ use crate::compiler::quilc::{self, CompilerOpts, TargetDevice}; /// same number of shots. #[derive(Debug, Clone)] pub(crate) struct Execution<'a> { - program: RewrittenProgram, + program: Program, pub(crate) quantum_processor_id: Cow<'a, str>, pub(crate) shots: NonZeroU16, client: Arc, @@ -59,12 +59,8 @@ pub(crate) enum Error { ReadoutParse(#[from] MemoryReferenceParseError), #[error("Problem when compiling program: {details}")] Compilation { details: String }, - #[error("Program when translating the program: {0}")] - RewriteArithmetic(#[from] rewrite_arithmetic::Error), #[error("Problem when getting RPCQ client: {0}")] RpcqClient(#[from] rpcq::Error), - #[error("Program when getting substitutions for program: {0}")] - Substitution(String), #[error("Problem making a request to the QPU: {0}")] QpuApi(#[from] super::api::QpuApiError), } @@ -155,13 +151,11 @@ impl<'a> Execution<'a> { quil.parse().map_err(Error::Quil)? }; - let rewritten = RewrittenProgram::try_from(program).map_err(Error::RewriteArithmetic)?; - Ok(Self { - program: rewritten, + program, quantum_processor_id, - client, shots, + client, }) } @@ -172,7 +166,7 @@ impl<'a> Execution<'a> { ) -> Result { let encrpyted_translation_result = translate( self.quantum_processor_id.as_ref(), - &self.program.to_string()?.0, + &self.program.to_quil()?, self.shots.get().into(), self.client.as_ref(), options, @@ -234,14 +228,10 @@ impl<'a> Execution<'a> { let EncryptedTranslationResult { job, readout_map } = self.translate(translation_options).await?; - let patch_values = self - .get_substitutions(params) - .map_err(Error::Substitution)?; - let job_id = submit( quantum_processor_id, job, - &patch_values, + params, self.client.as_ref(), execution_options, ) @@ -303,37 +293,4 @@ impl<'a> Execution<'a> { )), }) } - - /// Take the user-provided map of [`Parameters`] and produce the map of substitutions which - /// should be given to QCS with the executable. - /// - /// # Example - /// - /// If there was a Quil program: - /// - /// ```quil - /// DECLARE theta REAL - /// - /// RX(theta) 0 - /// RX(theta + 1) 0 - /// RX(theta + 2) 0 - /// ``` - /// - /// It would be converted (in [`Execution::new`]) to something like: - /// - /// ```quil - /// DECLARE __SUBST REAL[2] - /// DECLARE theta REAL[1] - /// - /// RX(theta) 0 - /// RX(__SUBST[0]) 0 - /// RX(__SUBST[1]) 0 - /// ``` - /// - /// Because QPUs do not evaluate expressions themselves. This function creates the values for - /// `__SUBST` by calculating the original expressions given the user-provided params (in this - /// case just `theta`). - fn get_substitutions(&self, params: &Parameters) -> Result { - rewrite_arithmetic::get_substitutions(&self.program.substitutions, params) - } } diff --git a/crates/lib/src/qpu/mod.rs b/crates/lib/src/qpu/mod.rs index f048e6cb5..c7204f5b8 100644 --- a/crates/lib/src/qpu/mod.rs +++ b/crates/lib/src/qpu/mod.rs @@ -17,7 +17,6 @@ use tokio::time::error::Elapsed; pub mod api; mod execution; pub mod result_data; -pub mod rewrite_arithmetic; pub mod translation; pub(crate) use execution::{Error as ExecutionError, Execution}; diff --git a/crates/lib/src/qpu/rewrite_arithmetic.rs b/crates/lib/src/qpu/rewrite_arithmetic.rs deleted file mode 100644 index 479dc54f5..000000000 --- a/crates/lib/src/qpu/rewrite_arithmetic.rs +++ /dev/null @@ -1,563 +0,0 @@ -//! This module provides functions to rewrite arithmetic in programs where -//! it is required to do so for Rigetti QPUs to understand that program. - -use std::{collections::HashMap, convert::TryFrom}; - -use indexmap::set::IndexSet; -use num::complex::Complex64; -use quil_rs::{ - expression::{Expression, InfixExpression, InfixOperator}, - instruction::{ - AttributeValue, FrameIdentifier, Gate, Instruction, MemoryReference, ScalarType, - SetFrequency, SetPhase, SetScale, ShiftFrequency, ShiftPhase, Vector, - }, - program::{FrameSet, MemoryRegion}, - quil::{Quil, ToQuilError}, - Program, -}; - -use crate::executable::Parameters; - -/// A function for converting `program` into a form that Rigetti QPUs can understand. -/// -/// # Unit Conversion -/// -/// QPUs expect a different unit space than Quil does. For example, gate parameters are given in -/// radians in Quil, but should be in "rotations" for QPUs where, essentially, 1 == 2π. -/// This function will convert all units to a form that QPUs can understand. -/// -/// # Arithmetic Simplification -/// -/// QPUs are capable of only a very limited subset of arithmetic operations. Therefore, __all__ -/// arithmetic expressions will be substituted for parameters which will be precalculated -/// locally. -/// -/// # Example -/// -/// ```quil -/// DECLARE theta REAL -/// RZ(theta * 1.5) 0 -/// ``` -/// -/// will be converted to something like -/// -/// ```quil -/// DECLARE __SUBST REAL[1] -/// DECLARE theta REAL[1] -/// RZ(__SUBST[0]) 0 -/// ``` -/// -/// where `__SUBST[0]` will be recalculated for each parameter set that is run and passed as a -/// distinct parameter from theta. Note that the value of `__SUBST[0]` will actually be -/// `theta * 1.5 / 2π`. -pub fn rewrite_arithmetic(program: Program) -> Result<(Program, Substitutions), Error> { - #[cfg(feature = "tracing")] - tracing::debug!("rewriting arithmetic"); - - let mut substitutions = Substitutions::new(); - let mut new_program = program.clone_without_body_instructions(); - let instructions = program.into_body_instructions(); - - let instructions = instructions - .into_iter() - .map(|instruction| { - process_instruction(instruction, &mut substitutions, &new_program.frames) - }) - .collect::, Error>>()?; - - if !substitutions.is_empty() { - new_program.memory_regions.insert( - String::from(SUBSTITUTION_NAME), - MemoryRegion { - size: Vector { - data_type: ScalarType::Real, - length: substitutions.len() as u64, - }, - sharing: None, - }, - ); - } - - new_program.add_instructions(instructions); - - Ok((new_program, substitutions)) -} - -/// Take the user-provided map of [`Parameters`] and produce the map of substitutions which -/// should be given to QCS with the executable. -/// -/// # Example -/// -/// If there was a Quil program: -/// -/// ```quil -/// DECLARE theta REAL -/// -/// RX(theta) 0 -/// RX(theta + 1) 0 -/// RX(theta + 2) 0 -/// ``` -/// -/// It would be converted (in [`Execution::new`]) to something like: -/// -/// ```quil -/// DECLARE __SUBST REAL[2] -/// DECLARE theta REAL[1] -/// -/// RX(theta) 0 -/// RX(__SUBST[0]) 0 -/// RX(__SUBST[1]) 0 -/// ``` -/// -/// Because QPUs do not evaluate expressions themselves. This function creates the values for -/// `__SUBST` by calculating the original expressions given the user-provided params (in this -/// case just `theta`). -pub fn get_substitutions( - substitutions: &Substitutions, - params: &Parameters, -) -> Result { - // Convert into the format that quil-rs expects. - let params: HashMap<&str, Vec> = params - .iter() - .map(|(key, value)| (key.as_ref(), value.clone())) - .collect(); - let values = substitutions - .iter() - .map(|substitution: &Expression| { - substitution - .evaluate(&HashMap::new(), ¶ms) - .map_err(|e| { - format!( - "Could not evaluate expression {}: {e:?}", - substitution.to_quil_or_debug() - ) - }) - .and_then(|complex| { - if complex.im == 0.0 { - Ok(complex.re) - } else { - Err(String::from( - "Cannot substitute imaginary numbers for QPU execution", - )) - } - }) - }) - .collect::, String>>()?; - // Convert back to the format that this library expects - let mut patch_values: Parameters = params - .into_iter() - .map(|(key, value)| (key.into(), value)) - .collect(); - patch_values.insert(SUBSTITUTION_NAME.into(), values); - Ok(patch_values) -} - -/// All of the errors that can occur in this module. -#[derive(Debug, thiserror::Error)] -pub enum Error { - /// A DEFRAME is missing for the given frame. - #[error("No DEFFRAME for {0}")] - MissingDefFrame(String), - - /// The SAMPLE-RATE value is invalid for the given frame. - #[error("Unable to use SAMPLE-RATE {sample_rate} for frame {frame}")] - InvalidSampleRate { - /// The invalid SAMPLE-RATE value. - sample_rate: String, - /// The given frame name. - frame: String, - }, - - /// A SAMPLE-RATE is missing for the given frame. - #[error("SAMPLE-RATE is required for frame {0}")] - MissingSampleRate(String), -} - -pub(crate) const SUBSTITUTION_NAME: &str = "__SUBST"; - -fn process_instruction( - instruction: Instruction, - substitutions: &mut Substitutions, - frames: &FrameSet, -) -> Result { - match instruction { - Instruction::Gate(gate) => Ok(process_gate(gate, substitutions)), - Instruction::SetScale(set_scale) => Ok(process_set_scale(set_scale, substitutions)), - Instruction::ShiftFrequency(ShiftFrequency { frequency, frame }) => { - process_frequency_expression(frequency, &frame, frames, substitutions).map( - |expression| { - Instruction::ShiftFrequency(ShiftFrequency { - frame, - frequency: expression, - }) - }, - ) - } - Instruction::SetFrequency(SetFrequency { frequency, frame }) => { - process_frequency_expression(frequency, &frame, frames, substitutions).map( - |expression| { - Instruction::SetFrequency(SetFrequency { - frame, - frequency: expression, - }) - }, - ) - } - Instruction::SetPhase(SetPhase { frame, phase }) => Ok(Instruction::SetPhase(SetPhase { - frame, - phase: process_phase(phase, substitutions), - })), - Instruction::ShiftPhase(ShiftPhase { frame, phase }) => { - Ok(Instruction::ShiftPhase(ShiftPhase { - frame, - phase: process_phase(phase, substitutions), - })) - } - instruction => Ok(instruction), - } -} - -fn process_gate(mut gate: Gate, substitutions: &mut Substitutions) -> Instruction { - gate.parameters = gate - .parameters - .into_iter() - .map(|mut expression| { - expression = expression.into_simplified(); - if matches!(expression, Expression::Number(_)) { - return expression; - } - // exp => exp / 2π but in a way that can be simplified - expression = divide_2_pi(expression); - substitution(expression, substitutions) - }) - .collect(); - Instruction::Gate(gate) -} - -fn divide_2_pi(expression: Expression) -> Expression { - Expression::Infix(InfixExpression { - left: Box::new(expression), - operator: InfixOperator::Slash, - right: Box::new(Expression::Infix(InfixExpression { - left: Box::new(Expression::Number(Complex64::from(2.0))), - operator: InfixOperator::Star, - right: Box::new(Expression::PiConstant), - })), - }) - .into_simplified() -} - -fn process_set_scale(mut set_scale: SetScale, substitutions: &mut Substitutions) -> Instruction { - set_scale.scale = set_scale.scale.into_simplified(); - if matches!(set_scale.scale, Expression::Number(_)) { - return Instruction::SetScale(set_scale); - } - - let SetScale { frame, scale } = set_scale; - - let expression = Expression::Infix(InfixExpression { - left: Box::new(scale), - operator: InfixOperator::Slash, - right: Box::new(Expression::Number(Complex64::from(8.0))), - }) - .into_simplified(); - - Instruction::SetScale(SetScale { - frame, - scale: substitution(expression, substitutions), - }) -} - -/// Substitute the expression (as necessary) for a SET-FREQUENCY or SHIFT-FREQUENCY instruction -fn process_frequency_expression( - mut expression: Expression, - frame: &FrameIdentifier, - frames: &FrameSet, - substitutions: &mut Substitutions, -) -> Result { - expression = expression.into_simplified(); - if matches!(expression, Expression::Number(_)) { - return Ok(expression); - } - let attributes = frames - .get(frame) - .ok_or_else(|| Error::MissingDefFrame(frame.name.clone()))?; - let sample_rate = match attributes.get("SAMPLE-RATE") { - Some(AttributeValue::Expression(expression)) => expression, - Some(AttributeValue::String(sample_rate)) => { - return Err(Error::InvalidSampleRate { - sample_rate: sample_rate.clone(), - frame: frame.name.clone(), - }); - } - None => { - return Err(Error::MissingSampleRate(frame.name.clone())); - } - }; - if let Some(AttributeValue::Expression(center_frequency)) = attributes.get("CENTER-FREQUENCY") { - expression = Expression::Infix(InfixExpression { - left: Box::new(expression), - operator: InfixOperator::Minus, - right: Box::new(center_frequency.clone()), - }); - } - expression = Expression::Infix(InfixExpression { - left: Box::new(expression), - operator: InfixOperator::Slash, - right: Box::new(sample_rate.clone()), - }); - Ok(substitution(expression, substitutions)) -} - -fn process_phase(phase: Expression, substitutions: &mut Substitutions) -> Expression { - if matches!(phase, Expression::Number(_)) { - return phase; - } - - let expression = divide_2_pi(phase); - - substitution(expression, substitutions) -} - -/// Take an expression and produce or return the existing substitution for it, recording that -/// substitution in `substitutions`. -fn substitution(expression: Expression, substitutions: &mut Substitutions) -> Expression { - let index = substitutions.get_index_of(&expression).unwrap_or_else(|| { - substitutions.insert(expression); - substitutions.len() - 1 - }); - let reference = MemoryReference { - name: String::from(SUBSTITUTION_NAME), - index: index as u64, - }; - - Expression::Address(reference) -} - -#[derive(Debug, Clone, PartialEq)] -pub(crate) struct RewrittenProgram { - inner: Program, - pub(crate) substitutions: Substitutions, -} - -#[derive(Debug, Clone, PartialEq, Eq, Ord, PartialOrd, Hash)] -pub(crate) struct RewrittenQuil(pub(crate) String); - -impl From for String { - fn from(quil: RewrittenQuil) -> String { - quil.0 - } -} - -impl TryFrom for RewrittenProgram { - type Error = Error; - - fn try_from(program: Program) -> Result { - let (inner, substitutions) = rewrite_arithmetic(program)?; - Ok(Self { - inner, - substitutions, - }) - } -} - -impl RewrittenProgram { - pub(crate) fn to_string(&self) -> Result { - Ok(RewrittenQuil(self.inner.to_quil()?)) - } -} - -pub(crate) type Substitutions = IndexSet; - -#[cfg(test)] -mod describe_rewrite_arithmetic { - use std::str::FromStr; - - use quil_rs::{quil::Quil, Program}; - - use crate::qpu::rewrite_arithmetic::rewrite_arithmetic; - - #[test] - fn it_substitutes_gate_parameters() { - let program = Program::from_str("DECLARE theta REAL; RZ(theta) 0").unwrap(); - let expected = - Program::from_str("DECLARE __SUBST REAL[1]; DECLARE theta REAL[1]; RZ(__SUBST[0]) 0") - .unwrap(); - let (actual, substitutions) = rewrite_arithmetic(program).unwrap(); - assert_eq!(actual, expected); - assert_eq!(substitutions.len(), 1); - insta::assert_snapshot!(substitutions[0].to_quil_or_debug()); - } - - #[test] - fn it_leaves_literal_gates_alone() { - let program = Program::from_str("RZ(1.0) 0").unwrap(); - let (actual, substitutions) = rewrite_arithmetic(program.clone()).unwrap(); - assert_eq!(actual, program); - assert_eq!(substitutions.len(), 0); - } - - #[test] - fn it_substitutes_and_reuses_gate_expressions() { - let program = - Program::from_str("DECLARE theta REAL; RZ(theta*1.5) 0; RX(theta*1.5) 0").unwrap(); - let expected = Program::from_str( - "DECLARE __SUBST REAL[1]; DECLARE theta REAL[1]; RZ(__SUBST[0]) 0; RX(__SUBST[0]) 0", - ) - .unwrap(); - let (actual, substitutions) = rewrite_arithmetic(program).unwrap(); - assert_eq!(actual, expected); - assert_eq!(substitutions.len(), 1); - insta::assert_snapshot!(substitutions[0].to_quil_or_debug()); - } - - #[test] - fn it_allocates_for_multiple_expressions() { - let program = Program::from_str( - r" -DECLARE theta REAL -DECLARE beta REAL -RZ(3 * theta) 0 -RZ(beta+theta) 0 -", - ) - .unwrap(); - let expected = Program::from_str( - r" -DECLARE __SUBST REAL[2] -DECLARE theta REAL[1] -DECLARE beta REAL[1] -RZ(__SUBST[0]) 0 -RZ(__SUBST[1]) 0 -", - ) - .unwrap(); - let (actual, substitutions) = rewrite_arithmetic(program).unwrap(); - assert_eq!(actual, expected); - assert_eq!(substitutions.len(), 2); - insta::assert_snapshot!(substitutions[0].to_quil_or_debug()); - insta::assert_snapshot!(substitutions[1].to_quil_or_debug()); - } - - #[test] - fn it_converts_set_scale_units() { - let program = Program::from_str( - r#" -DECLARE theta REAL -SET-SCALE 0 "rf" 1.0 -SET-SCALE 0 "rf" theta -"#, - ) - .unwrap(); - let expected = Program::from_str( - r#" -DECLARE __SUBST REAL[1] -DECLARE theta REAL[1] -SET-SCALE 0 "rf" 1.0 -SET-SCALE 0 "rf" __SUBST[0] -"#, - ) - .unwrap(); - let (actual, substitutions) = rewrite_arithmetic(program).unwrap(); - assert_eq!(actual, expected); - assert_eq!(substitutions.len(), 1); - insta::assert_snapshot!(substitutions[0].to_quil_or_debug()); - } - - #[test] - fn it_converts_frequency_expressions() { - let program = Program::from_str( - r#" -DEFFRAME 0 "rf": - CENTER-FREQUENCY: 10.0 - SAMPLE-RATE: 20.0 -DEFFRAME 1 "rf": - SAMPLE-RATE: 20.0 -DECLARE theta REAL -SET-FREQUENCY 0 "rf" theta -SHIFT-FREQUENCY 0 "rf" theta -SET-FREQUENCY 1 "rf" theta -"#, - ) - .unwrap(); - let expected = Program::from_str( - r#" -DEFFRAME 0 "rf": - CENTER-FREQUENCY: 10.0 - SAMPLE-RATE: 20.0 -DEFFRAME 1 "rf": - SAMPLE-RATE: 20.0 -DECLARE __SUBST REAL[2] -DECLARE theta REAL -SET-FREQUENCY 0 "rf" __SUBST[0] -SHIFT-FREQUENCY 0 "rf" __SUBST[0] -SET-FREQUENCY 1 "rf" __SUBST[1] -"#, - ) - .unwrap(); - let (actual, substitutions) = rewrite_arithmetic(program).unwrap(); - assert_eq!(actual, expected); - assert_eq!(substitutions.len(), 2); - insta::assert_snapshot!(substitutions[0].to_quil_or_debug()); - insta::assert_snapshot!(substitutions[1].to_quil_or_debug()); - } - - #[test] - fn it_errs_when_converting_frequency_without_frame() { - let program = Program::from_str( - r#" -DEFFRAME 0 "rf": - CENTER-FREQUENCY: 10.0 - SAMPLE-RATE: 20.0 -DECLARE theta REAL -SET-FREQUENCY 0 "rf" theta -SHIFT-FREQUENCY 0 "rf" theta -SET-FREQUENCY 1 "rf" theta -"#, - ) - .unwrap(); - let result = rewrite_arithmetic(program); - assert!(result.is_err()); - } - - #[test] - fn it_errs_when_converting_frequency_without_sample_rate() { - let program = Program::from_str( - r#" -DEFFRAME 0 "rf": - CENTER-FREQUENCY: 10.0 -DECLARE theta REAL -SET-FREQUENCY 0 "rf" theta -SHIFT-FREQUENCY 0 "rf" theta -"#, - ) - .unwrap(); - let result = rewrite_arithmetic(program); - assert!(result.is_err()); - } - - #[test] - fn it_converts_phases() { - let program = Program::from_str( - r#" -DECLARE theta REAL -SET-PHASE 0 "rf" theta -SHIFT-PHASE 0 "rf" theta -"#, - ) - .unwrap(); - let expected = Program::from_str( - r#" -DECLARE __SUBST REAL[1] -DECLARE theta REAL[1] -SET-PHASE 0 "rf" __SUBST[0] -SHIFT-PHASE 0 "rf" __SUBST[0] -"#, - ) - .unwrap(); - let (actual, substitutions) = rewrite_arithmetic(program).unwrap(); - assert_eq!(actual, expected); - assert_eq!(substitutions.len(), 1); - insta::assert_snapshot!(substitutions[0].to_quil_or_debug()); - } -} diff --git a/crates/python/qcs_sdk/qpu/__init__.pyi b/crates/python/qcs_sdk/qpu/__init__.pyi index 195f57c92..147f92836 100644 --- a/crates/python/qcs_sdk/qpu/__init__.pyi +++ b/crates/python/qcs_sdk/qpu/__init__.pyi @@ -11,7 +11,6 @@ from qcs_sdk.client import QCSClient from qcs_sdk.qpu import ( api as api, isa as isa, - rewrite_arithmetic as rewrite_arithmetic, translation as translation, ) diff --git a/crates/python/qcs_sdk/qpu/rewrite_arithmetic.pyi b/crates/python/qcs_sdk/qpu/rewrite_arithmetic.pyi deleted file mode 100644 index 7cffb9242..000000000 --- a/crates/python/qcs_sdk/qpu/rewrite_arithmetic.pyi +++ /dev/null @@ -1,65 +0,0 @@ -from typing import Dict, List, Sequence, Union, Mapping, final - -class RewriteArithmeticError(RuntimeError): - """ - Errors that can result from rewriting arithmetic: - - - The Quil program could not be parsed. - - Parametric arithmetic in the Quil program could not be rewritten. - """ - - ... - -class BuildPatchValuesError(RuntimeError): - """ - Errors that can result from building patch values: - - - Failed to interpret the recalculation values. - - Failed to build patch values. - """ - - ... - -@final -class RewriteArithmeticResult: - """ - Result of a ``rewrite_arithmetic call``. - - Provides the information necessary to later patch-in memory values to a compiled program. - """ - - @property - def program(self) -> str: - """The rewritten program.""" - ... - @property - def recalculation_table(self) -> List[str]: - """ - The expressions used to fill-in the `__SUBST` memory location. - - The expression index in this vec is the same as that in `__SUBST`. - """ - ... - -def build_patch_values( - recalculation_table: Sequence[str], - memory: Mapping[str, Union[Sequence[float], Sequence[int]]], -) -> Dict[str, List[float]]: - """ - Evaluate the expressions in `recalculation_table` using the numeric values provided in `memory`. - - :raises BuildPatchValuesError: If patch values could not be built. - """ - ... - -def rewrite_arithmetic(native_quil: str) -> RewriteArithmeticResult: - """ - Rewrite parametric arithmetic such that all gate parameters are only memory - references to newly declared memory location (`__SUBST`). - - A "recalculation" table is provided which can be used to populate the memory - when needed (see ``build_patch_values``). - - :raises RewriteArithmeticError: If the program fails to parse, or parameter arithmetic cannot be rewritten. - """ - ... diff --git a/crates/python/src/qpu/mod.rs b/crates/python/src/qpu/mod.rs index c088583de..b256ef35f 100644 --- a/crates/python/src/qpu/mod.rs +++ b/crates/python/src/qpu/mod.rs @@ -8,7 +8,6 @@ pub use result_data::{PyQpuResultData, PyReadoutValues, RawQpuReadoutData}; pub mod api; pub mod isa; mod result_data; -pub mod rewrite_arithmetic; pub mod translation; use crate::client::PyQcsClient; @@ -33,7 +32,6 @@ create_init_submodule! { submodules: [ "api": api::init_submodule, "isa": isa::init_submodule, - "rewrite_arithmetic": rewrite_arithmetic::init_submodule, "translation": translation::init_submodule ], } diff --git a/crates/python/src/qpu/rewrite_arithmetic.rs b/crates/python/src/qpu/rewrite_arithmetic.rs deleted file mode 100644 index 766d76e9d..000000000 --- a/crates/python/src/qpu/rewrite_arithmetic.rs +++ /dev/null @@ -1,141 +0,0 @@ -//! Rewriting program arithmetic. -use std::{collections::HashMap, str::FromStr}; - -use pyo3::{exceptions::PyRuntimeError, pyclass, pyfunction, PyResult}; -use quil_rs::{expression::Expression, quil::Quil}; -use rigetti_pyo3::{create_init_submodule, py_wrap_error, ToPythonError}; - -create_init_submodule! { - classes: [ - PyRewriteArithmeticResult - ], - errors: [ - BuildPatchValuesError, - RewriteArithmeticError - ], - funcs: [ - build_patch_values, - rewrite_arithmetic - ], -} - -/// Collection of errors that can result from rewriting arithmetic. -#[derive(Debug, thiserror::Error)] -pub enum RustRewriteArithmeticError { - /// The Quil program could not be parsed. - #[error("Could not parse program: {0}")] - Program(#[from] quil_rs::program::ProgramError), - - /// Parameteric arithmetic in the Quil program could not be rewritten. - #[error("Could not rewrite arithmetic: {0}")] - Rewrite(#[from] qcs::qpu::rewrite_arithmetic::Error), -} - -py_wrap_error!( - rewrite_arithmetic, - RustRewriteArithmeticError, - RewriteArithmeticError, - PyRuntimeError -); - -/// The result of a call to [`rewrite_arithmetic()`] which provides the -/// information necessary to later patch-in memory values to a compiled program. -#[pyclass] -#[pyo3(name = "RewriteArithmeticResult")] -pub struct PyRewriteArithmeticResult { - /// The rewritten program - #[pyo3(get)] - pub program: String, - - /// The expressions used to fill-in the `__SUBST` memory location. The - /// expression index in this vec is the same as that in `__SUBST`. - #[pyo3(get)] - pub recalculation_table: Vec, -} - -/// Rewrite parametric arithmetic such that all gate parameters are only memory -/// references to newly declared memory location (`__SUBST`). -/// -/// A "recalculation" table is provided which can be used to populate the memory -/// when needed (see `build_patch_values`). -/// -/// # Errors -/// -/// May return an error if the program fails to parse, or the parameter arithmetic -/// cannot be rewritten. -#[pyfunction] -pub fn rewrite_arithmetic(native_quil: String) -> PyResult { - let native_program = native_quil - .parse::() - .map_err(RustRewriteArithmeticError::from) - .map_err(RustRewriteArithmeticError::to_py_err)?; - - let (program, index_set) = qcs::qpu::rewrite_arithmetic::rewrite_arithmetic(native_program) - .map_err(RustRewriteArithmeticError::from) - .map_err(RustRewriteArithmeticError::to_py_err)?; - - let program = program - .to_quil() - .expect("Successfully parsed program should convert to valid Quil."); - let recalculation_table = index_set - .into_iter() - .map(|e| { - e.to_quil().expect( - "Expressions built from a successfully parsed program should convert to valid Quil.", - ) - }) - .collect(); - - Ok(PyRewriteArithmeticResult { - program, - recalculation_table, - }) -} - -/// Collection of errors that can result from building patch values. -#[derive(Debug, thiserror::Error)] -pub enum RustBuildPatchValuesError { - /// Failed to interpret the recalculation table. - #[error("Unable to interpret recalculation table: {0:?}")] - Substitutions(#[from] quil_rs::program::ParseProgramError), - - /// Failed to build patch values. - #[error("Failed to build patch values: {0}")] - PatchValues(String), -} - -py_wrap_error!( - rewrite_arithmetic, - RustBuildPatchValuesError, - BuildPatchValuesError, - PyRuntimeError -); - -/// Evaluate the expressions in `recalculation_table` using the numeric values -/// provided in `memory`. -/// -/// # Errors -#[pyfunction] -pub fn build_patch_values( - recalculation_table: Vec, - memory: HashMap>, -) -> PyResult>> { - let memory = memory - .into_iter() - .map(|(k, v)| (k.into_boxed_str(), v)) - .collect(); - let substitutions = recalculation_table - .iter() - .map(|expr| Expression::from_str(expr)) - .collect::>() - .map_err(RustBuildPatchValuesError::from) - .map_err(RustBuildPatchValuesError::to_py_err)?; - let patch_values = qcs::qpu::rewrite_arithmetic::get_substitutions(&substitutions, &memory) - .map_err(RustBuildPatchValuesError::PatchValues) - .map_err(RustBuildPatchValuesError::to_py_err)? - .into_iter() - .map(|(k, v)| (k.to_string(), v)) - .collect(); - - Ok(patch_values) -} diff --git a/crates/python/tests/qpu/test_api.py b/crates/python/tests/qpu/test_api.py index ce28144e5..da51e2e8f 100644 --- a/crates/python/tests/qpu/test_api.py +++ b/crates/python/tests/qpu/test_api.py @@ -1,9 +1,5 @@ import pytest -from qcs_sdk.qpu.rewrite_arithmetic import ( - rewrite_arithmetic, - build_patch_values, -) from qcs_sdk.qpu.translation import ( translate, ) @@ -45,8 +41,6 @@ def test_submit_retrieve( memory = { "theta": [0.5] } translated = translate(program, 1, quantum_processor_id) - rewritten = rewrite_arithmetic(translated.program) - patch_values = build_patch_values(rewritten.recalculation_table, memory) - job_id = submit(rewritten.program, patch_values, quantum_processor_id) + job_id = submit(program, memory, quantum_processor_id) results = retrieve_results(job_id) diff --git a/crates/python/tests/qpu/test_rewrite_arithmetic.py b/crates/python/tests/qpu/test_rewrite_arithmetic.py deleted file mode 100644 index 30bdefec0..000000000 --- a/crates/python/tests/qpu/test_rewrite_arithmetic.py +++ /dev/null @@ -1,32 +0,0 @@ -import pytest - -from qcs_sdk.qpu.rewrite_arithmetic import ( - rewrite_arithmetic, - build_patch_values, - BuildPatchValuesError, - RewriteArithmeticError, -) - - -def test_rewrite_arithmetic( - bell_program: str, -): - rewritten = rewrite_arithmetic(bell_program) - assert rewritten.program.strip() == bell_program.strip() - assert rewritten.recalculation_table == [] - - -def test_rewrite_arithmetic_error(): - with pytest.raises(RewriteArithmeticError): - rewrite_arithmetic("DECLARE --") - - -def test_build_patch_values(): - memory = { "a": [1.0], "b": [3] } - output = build_patch_values(["a/b"], memory) - assert output == { "__SUBST": [1/3], **memory } - - -def test_build_patch_values_error(): - with pytest.raises(BuildPatchValuesError): - build_patch_values(["a/b"], {"a": [1], "b": [0]})