use crate::complex::compatible;
use crate::gate::{Axis, rotation};
use crate::{DenseSimulator, Error, Register, Result, StateVector, Unitary};
use burn::tensor::{DType, Device, Tensor, TensorData};
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub enum Angle {
Constant(f64),
Parameter {
index: usize,
scale: f64,
offset: f64,
},
Input {
index: usize,
scale: f64,
offset: f64,
},
}
impl Angle {
pub fn parameter(index: usize) -> Self {
Self::Parameter {
index,
scale: 1.,
offset: 0.,
}
}
pub fn input(index: usize) -> Self {
Self::Input {
index,
scale: 1.,
offset: 0.,
}
}
fn validate(&self, parameters: usize, inputs: usize) -> Result<()> {
let valid = match self {
Self::Constant(x) => x.is_finite(),
Self::Parameter {
index,
scale,
offset,
} => *index < parameters && scale.is_finite() && offset.is_finite(),
Self::Input {
index,
scale,
offset,
} => *index < inputs && scale.is_finite() && offset.is_finite(),
};
if valid {
Ok(())
} else {
Err(Error::InvalidValue(
"invalid angle expression or binding index".into(),
))
}
}
fn bind(
&self,
parameters: &Option<Tensor<1>>,
inputs: &Option<Tensor<2>>,
batch: usize,
device: &Device,
dtype: DType,
) -> Tensor<1> {
match self {
Self::Constant(x) => Tensor::from_data(TensorData::new(vec![*x], [1]), (device, dtype)),
Self::Parameter {
index,
scale,
offset,
} => {
parameters
.as_ref()
.unwrap()
.clone()
.slice_dim(0, *index..*index + 1)
* *scale
+ *offset
}
Self::Input {
index,
scale,
offset,
} => {
inputs
.as_ref()
.unwrap()
.clone()
.slice([0..batch, *index..*index + 1])
.reshape([batch])
* *scale
+ *offset
}
}
}
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct Rotation {
pub axis: Axis,
pub levels: [usize; 2],
pub angle: Angle,
}
impl Rotation {
pub fn qubit(axis: Axis, angle: Angle) -> Self {
Self {
axis,
levels: [0, 1],
angle,
}
}
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub enum Operation {
Unitary {
wires: Vec<usize>,
gate: Unitary,
},
Rotate {
wire: usize,
rotation: Rotation,
},
}
#[derive(Serialize, Deserialize)]
struct CircuitData {
version: u32,
register: Register,
parameters: usize,
inputs: usize,
operations: Vec<Operation>,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(try_from = "CircuitData", into = "CircuitData")]
pub struct Circuit {
register: Register,
parameters: usize,
inputs: usize,
operations: Vec<Operation>,
}
impl Circuit {
pub fn new(register: Register, parameters: usize, inputs: usize) -> Self {
Self {
register,
parameters,
inputs,
operations: Vec::new(),
}
}
pub fn register(&self) -> &Register {
&self.register
}
pub fn num_parameters(&self) -> usize {
self.parameters
}
pub fn num_inputs(&self) -> usize {
self.inputs
}
pub fn operations(&self) -> &[Operation] {
&self.operations
}
pub fn push(&mut self, operation: Operation) -> Result<&mut Self> {
self.validate_operation(&operation)?;
self.operations.push(operation);
Ok(self)
}
pub fn unitary(&mut self, wires: Vec<usize>, gate: Unitary) -> Result<&mut Self> {
self.push(Operation::Unitary { wires, gate })
}
pub fn rotate(&mut self, wire: usize, rotation: Rotation) -> Result<&mut Self> {
self.push(Operation::Rotate { wire, rotation })
}
fn validate_operation(&self, op: &Operation) -> Result<()> {
match op {
Operation::Unitary { wires, gate } => {
if self.register.local_size(wires)? != gate.matrix().dimension() {
return Err(Error::Shape(
"unitary dimension differs from target dimension".into(),
));
}
}
Operation::Rotate { wire, rotation } => {
let d = self.register.local_size(&[*wire])?;
let [a, b] = rotation.levels;
if a == b || a >= d || b >= d {
return Err(Error::Basis);
}
rotation.angle.validate(self.parameters, self.inputs)?;
}
}
Ok(())
}
pub fn run(
&self,
engine: &DenseSimulator,
parameters: Option<Tensor<1>>,
inputs: Option<Tensor<2>>,
batch: usize,
device: &Device,
) -> Result<StateVector> {
self.run_shifted(engine, parameters, inputs, batch, device, None)
}
pub(crate) fn run_shifted(
&self,
engine: &DenseSimulator,
parameters: Option<Tensor<1>>,
inputs: Option<Tensor<2>>,
batch: usize,
device: &Device,
shift: Option<(usize, f64)>,
) -> Result<StateVector> {
if batch == 0 {
return Err(Error::Shape("batch must be positive".into()));
}
match ¶meters {
Some(p) if p.dims() == [self.parameters] && p.device() == *device => {}
None if self.parameters == 0 => {}
_ => return Err(Error::Shape("parameter shape or device mismatch".into())),
}
match &inputs {
Some(x) if x.dims() == [batch, self.inputs] && x.device() == *device => {}
None if self.inputs == 0 => {}
_ => return Err(Error::Shape("input shape or device mismatch".into())),
}
if let (Some(p), Some(x)) = (¶meters, &inputs) {
compatible(p, x)?;
}
let dtype = parameters
.as_ref()
.map(|x| x.dtype())
.or_else(|| inputs.as_ref().map(|x| x.dtype()))
.unwrap_or_else(|| device.settings().float_dtype.into());
engine.limits.check_state(batch, self.register.size())?;
if !self.operations.is_empty() {
engine.limits.check_plan(self.register.size())?;
}
for op in &self.operations {
match op {
Operation::Unitary { gate, .. } => {
engine.limits.check_matrix(1, gate.matrix().dimension())?
}
Operation::Rotate { wire, rotation } => {
let d = self.register.dimensions()[*wire];
let entries = crate::error::checked_product([d, d])?;
if entries > crate::gate::MAX_BUILTIN_MATRIX_ELEMENTS {
return Err(Error::ResourceLimit {
resource: "built-in rotation",
required: entries,
limit: crate::gate::MAX_BUILTIN_MATRIX_ELEMENTS,
});
}
let gb = if matches!(rotation.angle, Angle::Input { .. }) {
batch
} else {
1
};
engine
.limits
.check_matrix(gb, self.register.dimensions()[*wire])?;
}
}
}
let mut state =
StateVector::zero(self.register.clone(), batch, device, dtype, engine.limits)?;
for (i, op) in self.operations.iter().enumerate() {
state = match op {
Operation::Unitary { wires, gate } => engine.apply(&state, wires, gate)?,
Operation::Rotate {
wire,
rotation: spec,
} => {
let mut angle = spec.angle.bind(¶meters, &inputs, batch, device, dtype);
if let Some((target, delta)) = shift
&& target == i
{
angle = angle + delta;
}
let gate = rotation(
spec.axis,
self.register.dimensions()[*wire],
spec.levels,
angle,
)?;
engine.apply_tensor(&state, &[*wire], &gate)?
}
};
}
Ok(state)
}
}
impl TryFrom<CircuitData> for Circuit {
type Error = Error;
fn try_from(data: CircuitData) -> Result<Self> {
if data.version != 1 {
return Err(Error::Unsupported(format!(
"circuit schema version {}",
data.version
)));
}
let mut circuit = Self::new(data.register, data.parameters, data.inputs);
for op in data.operations {
circuit.push(op)?;
}
Ok(circuit)
}
}
impl From<Circuit> for CircuitData {
fn from(c: Circuit) -> Self {
Self {
version: 1,
register: c.register,
parameters: c.parameters,
inputs: c.inputs,
operations: c.operations,
}
}
}