use crate::{
Circuit, DenseSimulator, Error, Observable, ResourceLimits, Result as QuantumResult,
StateVector,
};
use burn::config::Config;
use burn::module::{Module, Param};
use burn::tensor::{Device, Tensor, TensorData};
#[derive(Config, Debug)]
pub struct QuantumLayerConfig {
pub circuit: Circuit,
pub observables: Vec<Observable>,
pub initial: Vec<f64>,
#[config(default = "ResourceLimits::default()")]
pub limits: ResourceLimits,
}
impl QuantumLayerConfig {
pub fn init(&self, device: &Device) -> QuantumResult<QuantumLayer> {
QuantumLayer::new(
self.circuit.clone(),
self.observables.clone(),
self.initial.clone(),
DenseSimulator::new(self.limits),
device,
)
}
}
#[derive(Module, Debug)]
pub struct QuantumLayer {
pub parameters: Param<Tensor<1>>,
#[module(skip)]
circuit: Circuit,
#[module(skip)]
observables: Vec<Observable>,
#[module(skip)]
engine: DenseSimulator,
}
impl QuantumLayer {
pub fn new(
circuit: Circuit,
observables: Vec<Observable>,
initial: Vec<f64>,
engine: DenseSimulator,
device: &Device,
) -> QuantumResult<Self> {
if initial.is_empty()
|| initial.len() != circuit.num_parameters()
|| initial.iter().any(|x| !x.is_finite())
{
return Err(Error::Shape(
"layer requires finite, nonempty initial parameters matching its circuit".into(),
));
}
for o in &observables {
if circuit.register().local_size(o.wires())? != o.matrix().dimension() {
return Err(Error::Shape("readout dimension mismatch".into()));
}
}
let n = initial.len();
let parameters = Param::from_data(TensorData::new(initial, [n]), device);
Ok(Self {
parameters,
circuit,
observables,
engine,
})
}
pub fn circuit(&self) -> &Circuit {
&self.circuit
}
pub fn observables(&self) -> &[Observable] {
&self.observables
}
pub fn state(&self, inputs: Option<Tensor<2>>, batch: usize) -> QuantumResult<StateVector> {
let parameters = self.parameters.val();
let device = parameters.device();
self.circuit
.run(&self.engine, Some(parameters), inputs, batch, &device)
}
pub fn forward(&self, inputs: Option<Tensor<2>>, batch: usize) -> QuantumResult<Tensor<2>> {
if self.observables.is_empty() {
return Err(Error::Shape(
"forward requires at least one readout; use state for state-based losses".into(),
));
}
let state = self.state(inputs, batch)?;
let columns: QuantumResult<Vec<_>> = self
.observables
.iter()
.map(|o| Ok(o.expectation(&self.engine, &state)?.reshape([batch, 1])))
.collect();
Ok(Tensor::cat(columns?, 1))
}
}