use crate::error::{MLError, Result};
use scirs2_core::ndarray::{Array1, Array2, ArrayD, IxDyn};
use scirs2_core::Complex64;
use std::f64::consts::PI;
pub mod ansatz;
pub mod autograd;
pub mod conv;
pub mod encoding;
pub mod functional;
pub mod gates;
pub mod layer;
pub mod measurement;
pub mod noise;
pub mod pooling;
pub mod tensor_network;
pub type CType = Complex64;
pub type FType = f64;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum WiresEnum {
AnyWires,
AllWires,
Fixed(usize),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum NParamsEnum {
AnyNParams,
Fixed(usize),
}
pub trait TQModule: Send + Sync {
fn forward(&mut self, qdev: &mut TQDevice) -> Result<()>;
fn forward_with_input(&mut self, qdev: &mut TQDevice, _x: Option<&Array2<f64>>) -> Result<()> {
self.forward(qdev)
}
fn parameters(&self) -> Vec<TQParameter>;
fn n_wires(&self) -> Option<usize>;
fn set_n_wires(&mut self, n_wires: usize);
fn is_static_mode(&self) -> bool;
fn static_on(&mut self);
fn static_off(&mut self);
fn get_unitary(&self) -> Option<Array2<CType>> {
None
}
fn name(&self) -> &str;
fn zero_grad(&mut self) {
}
fn train(&mut self, _mode: bool) {
}
fn training(&self) -> bool {
true
}
}
#[derive(Debug, Clone)]
pub struct TQParameter {
pub data: ArrayD<f64>,
pub name: String,
pub requires_grad: bool,
pub grad: Option<ArrayD<f64>>,
}
impl TQParameter {
pub fn new(data: ArrayD<f64>, name: impl Into<String>) -> Self {
Self {
data,
name: name.into(),
requires_grad: true,
grad: None,
}
}
pub fn no_grad(data: ArrayD<f64>, name: impl Into<String>) -> Self {
Self {
data,
name: name.into(),
requires_grad: false,
grad: None,
}
}
pub fn shape(&self) -> &[usize] {
self.data.shape()
}
pub fn numel(&self) -> usize {
self.data.len()
}
pub fn zero_grad(&mut self) {
self.grad = None;
}
pub fn init_uniform_pi(&mut self) {
for elem in self.data.iter_mut() {
*elem = (fastrand::f64() * 2.0 - 1.0) * PI;
}
}
pub fn init_constant(&mut self, value: f64) {
for elem in self.data.iter_mut() {
*elem = value;
}
}
}
#[derive(Debug, Clone)]
pub struct TQDevice {
pub n_wires: usize,
pub device_name: String,
pub bsz: usize,
pub states: ArrayD<CType>,
pub record_op: bool,
pub op_history: Vec<OpHistoryEntry>,
}
#[derive(Debug, Clone)]
pub struct OpHistoryEntry {
pub name: String,
pub wires: Vec<usize>,
pub params: Option<Vec<f64>>,
pub inverse: bool,
pub trainable: bool,
}
impl TQDevice {
pub fn new(n_wires: usize) -> Self {
Self::with_batch_size(n_wires, 1)
}
pub fn with_batch_size(n_wires: usize, bsz: usize) -> Self {
let state_size = 1 << n_wires; let mut state_data = vec![CType::new(0.0, 0.0); state_size * bsz];
for b in 0..bsz {
state_data[b * state_size] = CType::new(1.0, 0.0);
}
let mut shape = vec![bsz];
shape.extend(vec![2; n_wires]);
let states = ArrayD::from_shape_vec(IxDyn(&shape), state_data)
.unwrap_or_else(|_| ArrayD::zeros(IxDyn(&shape)));
Self {
n_wires,
device_name: "default".to_string(),
bsz,
states,
record_op: false,
op_history: Vec::new(),
}
}
pub fn reset_states(&mut self, bsz: usize) {
self.bsz = bsz;
let state_size = 1 << self.n_wires;
let mut state_data = vec![CType::new(0.0, 0.0); state_size * bsz];
for b in 0..bsz {
state_data[b * state_size] = CType::new(1.0, 0.0);
}
let mut shape = vec![bsz];
shape.extend(vec![2; self.n_wires]);
self.states = ArrayD::from_shape_vec(IxDyn(&shape), state_data)
.unwrap_or_else(|_| ArrayD::zeros(IxDyn(&shape)));
}
pub fn reset_identity_states(&mut self) {
let state_size = 1 << self.n_wires;
self.bsz = state_size;
let mut state_data = vec![CType::new(0.0, 0.0); state_size * state_size];
for i in 0..state_size {
state_data[i * state_size + i] = CType::new(1.0, 0.0);
}
let mut shape = vec![state_size];
shape.extend(vec![2; self.n_wires]);
self.states = ArrayD::from_shape_vec(IxDyn(&shape), state_data)
.unwrap_or_else(|_| ArrayD::zeros(IxDyn(&shape)));
}
pub fn reset_all_eq_states(&mut self, bsz: usize) {
self.bsz = bsz;
let state_size = 1 << self.n_wires;
let amplitude = 1.0 / (state_size as f64).sqrt();
let state_data = vec![CType::new(amplitude, 0.0); state_size * bsz];
let mut shape = vec![bsz];
shape.extend(vec![2; self.n_wires]);
self.states = ArrayD::from_shape_vec(IxDyn(&shape), state_data)
.unwrap_or_else(|_| ArrayD::zeros(IxDyn(&shape)));
}
pub fn clone_states(&mut self, other: &TQDevice) {
self.states = other.states.clone();
self.bsz = other.bsz;
}
pub fn set_states(&mut self, states: ArrayD<CType>) {
self.bsz = states.shape()[0];
self.states = states;
}
pub fn get_states_1d(&self) -> Array2<CType> {
let state_size = 1 << self.n_wires;
let flat: Vec<CType> = self.states.iter().cloned().collect();
Array2::from_shape_vec((self.bsz, state_size), flat)
.unwrap_or_else(|_| Array2::zeros((self.bsz, state_size)))
}
pub fn get_probs_1d(&self) -> Array2<f64> {
let states_1d = self.get_states_1d();
states_1d.mapv(|c| c.norm_sqr())
}
pub fn record_operation(&mut self, entry: OpHistoryEntry) {
if self.record_op {
self.op_history.push(entry);
}
}
pub fn reset_op_history(&mut self) {
self.op_history.clear();
}
pub fn apply_single_qubit_gate(&mut self, wire: usize, matrix: &Array2<CType>) -> Result<()> {
if wire >= self.n_wires {
return Err(MLError::InvalidConfiguration(format!(
"Wire {} out of range for {} qubits",
wire, self.n_wires
)));
}
let state_size = 1 << self.n_wires;
let states_1d = self.get_states_1d();
let mut new_states = states_1d.clone();
for batch in 0..self.bsz {
for i in 0..state_size {
let bit = (i >> (self.n_wires - 1 - wire)) & 1;
if bit == 0 {
let j = i | (1 << (self.n_wires - 1 - wire));
let amp0 = states_1d[[batch, i]];
let amp1 = states_1d[[batch, j]];
new_states[[batch, i]] = matrix[[0, 0]] * amp0 + matrix[[0, 1]] * amp1;
new_states[[batch, j]] = matrix[[1, 0]] * amp0 + matrix[[1, 1]] * amp1;
}
}
}
let flat: Vec<CType> = new_states.iter().cloned().collect();
let mut shape = vec![self.bsz];
shape.extend(vec![2; self.n_wires]);
self.states = ArrayD::from_shape_vec(IxDyn(&shape), flat)
.map_err(|e| MLError::InvalidConfiguration(e.to_string()))?;
Ok(())
}
pub fn apply_two_qubit_gate(
&mut self,
wire0: usize,
wire1: usize,
matrix: &Array2<CType>,
) -> Result<()> {
if wire0 >= self.n_wires || wire1 >= self.n_wires {
return Err(MLError::InvalidConfiguration(format!(
"Wires ({}, {}) out of range for {} qubits",
wire0, wire1, self.n_wires
)));
}
let state_size = 1 << self.n_wires;
let states_1d = self.get_states_1d();
let mut new_states = states_1d.clone();
let pos0 = self.n_wires - 1 - wire0;
let pos1 = self.n_wires - 1 - wire1;
for batch in 0..self.bsz {
let mut visited = vec![false; state_size];
for i in 0..state_size {
if visited[i] {
continue;
}
let base = i & !(1 << pos0) & !(1 << pos1);
let indices = [
base, base | (1 << pos1), base | (1 << pos0), base | (1 << pos0) | (1 << pos1), ];
let amps: Vec<CType> = indices.iter().map(|&idx| states_1d[[batch, idx]]).collect();
for (row, &idx) in indices.iter().enumerate() {
let mut new_amp = CType::new(0.0, 0.0);
for (col, &) in amps.iter().enumerate() {
new_amp += matrix[[row, col]] * amp;
}
new_states[[batch, idx]] = new_amp;
visited[idx] = true;
}
}
}
let flat: Vec<CType> = new_states.iter().cloned().collect();
let mut shape = vec![self.bsz];
shape.extend(vec![2; self.n_wires]);
self.states = ArrayD::from_shape_vec(IxDyn(&shape), flat)
.map_err(|e| MLError::InvalidConfiguration(e.to_string()))?;
Ok(())
}
pub fn apply_multi_qubit_gate(
&mut self,
wires: &[usize],
matrix: &Array2<CType>,
) -> Result<()> {
let n_qubits = wires.len();
for &wire in wires {
if wire >= self.n_wires {
return Err(MLError::InvalidConfiguration(format!(
"Wire {} out of range for {} qubits",
wire, self.n_wires
)));
}
}
let gate_dim = 1 << n_qubits;
if matrix.nrows() != gate_dim || matrix.ncols() != gate_dim {
return Err(MLError::InvalidConfiguration(format!(
"Gate matrix must be {}x{} for {}-qubit gate",
gate_dim, gate_dim, n_qubits
)));
}
let state_size = 1 << self.n_wires;
let states_1d = self.get_states_1d();
let mut new_states = states_1d.clone();
let positions: Vec<usize> = wires.iter().map(|&w| self.n_wires - 1 - w).collect();
let mut wire_mask: usize = 0;
for &pos in &positions {
wire_mask |= 1 << pos;
}
for batch in 0..self.bsz {
let mut visited = vec![false; state_size];
for base_idx in 0..state_size {
if visited[base_idx] {
continue;
}
let base = base_idx & !wire_mask;
let mut indices = Vec::with_capacity(gate_dim);
for gate_idx in 0..gate_dim {
let mut idx = base;
for (bit_pos, &pos) in positions.iter().enumerate() {
if (gate_idx >> (n_qubits - 1 - bit_pos)) & 1 == 1 {
idx |= 1 << pos;
}
}
indices.push(idx);
}
let amps: Vec<CType> = indices.iter().map(|&idx| states_1d[[batch, idx]]).collect();
for (row, &idx) in indices.iter().enumerate() {
let mut new_amp = CType::new(0.0, 0.0);
for (col, &) in amps.iter().enumerate() {
new_amp += matrix[[row, col]] * amp;
}
new_states[[batch, idx]] = new_amp;
visited[idx] = true;
}
}
}
let flat: Vec<CType> = new_states.iter().cloned().collect();
let mut shape = vec![self.bsz];
shape.extend(vec![2; self.n_wires]);
self.states = ArrayD::from_shape_vec(IxDyn(&shape), flat)
.map_err(|e| MLError::InvalidConfiguration(e.to_string()))?;
Ok(())
}
}
pub trait TQOperator: TQModule {
fn num_wires(&self) -> WiresEnum;
fn num_params(&self) -> NParamsEnum;
fn get_matrix(&self, params: Option<&[f64]>) -> Array2<CType>;
fn get_eigvals(&self, _params: Option<&[f64]>) -> Option<Array1<CType>> {
None
}
fn apply(&mut self, qdev: &mut TQDevice, wires: &[usize]) -> Result<()>;
fn apply_with_params(
&mut self,
qdev: &mut TQDevice,
wires: &[usize],
params: Option<&[f64]>,
) -> Result<()>;
fn has_params(&self) -> bool;
fn trainable(&self) -> bool;
fn inverse(&self) -> bool;
fn set_inverse(&mut self, inverse: bool);
}
pub struct TQModuleList {
modules: Vec<Box<dyn TQModule>>,
static_mode: bool,
}
impl TQModuleList {
pub fn new() -> Self {
Self {
modules: Vec::new(),
static_mode: false,
}
}
pub fn append(&mut self, module: Box<dyn TQModule>) {
self.modules.push(module);
}
pub fn len(&self) -> usize {
self.modules.len()
}
pub fn is_empty(&self) -> bool {
self.modules.is_empty()
}
pub fn get(&self, index: usize) -> Option<&Box<dyn TQModule>> {
self.modules.get(index)
}
pub fn get_mut(&mut self, index: usize) -> Option<&mut Box<dyn TQModule>> {
self.modules.get_mut(index)
}
pub fn iter(&self) -> impl Iterator<Item = &Box<dyn TQModule>> {
self.modules.iter()
}
pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut Box<dyn TQModule>> {
self.modules.iter_mut()
}
}
impl Default for TQModuleList {
fn default() -> Self {
Self::new()
}
}
impl TQModule for TQModuleList {
fn forward(&mut self, qdev: &mut TQDevice) -> Result<()> {
for module in &mut self.modules {
module.forward(qdev)?;
}
Ok(())
}
fn parameters(&self) -> Vec<TQParameter> {
self.modules.iter().flat_map(|m| m.parameters()).collect()
}
fn n_wires(&self) -> Option<usize> {
self.modules.first().and_then(|m| m.n_wires())
}
fn set_n_wires(&mut self, n_wires: usize) {
for module in &mut self.modules {
module.set_n_wires(n_wires);
}
}
fn is_static_mode(&self) -> bool {
self.static_mode
}
fn static_on(&mut self) {
self.static_mode = true;
for module in &mut self.modules {
module.static_on();
}
}
fn static_off(&mut self) {
self.static_mode = false;
for module in &mut self.modules {
module.static_off();
}
}
fn name(&self) -> &str {
"ModuleList"
}
fn zero_grad(&mut self) {
for module in &mut self.modules {
module.zero_grad();
}
}
}
pub mod prelude {
pub use super::{
CType, FType, NParamsEnum, OpHistoryEntry, TQDevice, TQModule, TQModuleList, TQOperator,
TQParameter, WiresEnum,
};
pub use super::gates::{
TQHadamard,
TQPauliX,
TQPauliY,
TQPauliZ,
TQRx,
TQRy,
TQRz,
TQCNOT,
TQCRX,
TQCRY,
TQCRZ,
TQCZ,
TQRXX,
TQRYY,
TQRZX,
TQRZZ,
TQS,
TQSWAP,
TQSX,
TQT,
TQU1,
TQU2,
TQU3,
};
pub use super::encoding::{
EncodingOp, TQAmplitudeEncoder, TQEncoder, TQGeneralEncoder, TQPhaseEncoder, TQStateEncoder,
};
pub use super::measurement::{
expval_joint_analytical, expval_joint_sampling, gen_bitstrings, measure, TQMeasureAll,
};
pub use super::layer::{
TQBarrenLayer, TQFarhiLayer, TQLayerConfig, TQMaxwellLayer, TQOp1QAllLayer, TQOp2QAllLayer,
TQRXYZCXLayer, TQSethLayer, TQStrongEntanglingLayer,
};
pub use super::autograd::{
gradient_norm, gradient_statistics, ClippingStatistics, ClippingStrategy,
GradientAccumulator, GradientCheckResult, GradientChecker, GradientClipper,
GradientStatistics, ParameterGroup, ParameterGroupManager, ParameterRegistry,
ParameterStatistics,
};
pub use super::ansatz::{
EfficientSU2Layer, EntanglementPattern, RealAmplitudesLayer, TwoLocalLayer,
};
pub use super::conv::{QConv1D, QConv2D};
pub use super::pooling::{QAvgPool, QMaxPool};
pub use super::tensor_network::{
CompressionMethod, MPSTensor, MatrixProductState, TQTensorNetworkBackend,
TensorNetworkConfig,
};
pub use super::noise::{
GateTimes, MitigatedExpectation, MitigatedExpectationConfig, MitigationMethod,
NoiseAwareGradient, NoiseAwareGradientConfig, NoiseAwareTrainer, NoiseModel,
SingleQubitNoiseType, TrainingHistory, TrainingStatistics, TwoQubitNoiseType,
VarianceReduction, ZNEExtrapolation,
};
}
#[cfg(test)]
mod tests {
use super::prelude::*;
use std::f64::consts::PI;
#[test]
fn test_tq_device_creation() {
let qdev = TQDevice::new(4);
assert_eq!(qdev.n_wires, 4);
assert_eq!(qdev.bsz, 1);
let probs = qdev.get_probs_1d();
assert!((probs[[0, 0]] - 1.0).abs() < 1e-10);
for i in 1..(1 << 4) {
assert!(probs[[0, i]].abs() < 1e-10);
}
}
#[test]
fn test_tq_device_reset() {
let mut qdev = TQDevice::new(2);
qdev.reset_all_eq_states(1);
let probs = qdev.get_probs_1d();
let expected = 0.25; for i in 0..4 {
assert!((probs[[0, i]] - expected).abs() < 1e-10);
}
}
#[test]
fn test_tq_parameter() {
use scirs2_core::ndarray::ArrayD;
let mut param =
TQParameter::new(ArrayD::zeros(scirs2_core::ndarray::IxDyn(&[2, 3])), "test");
assert_eq!(param.shape(), &[2, 3]);
assert_eq!(param.numel(), 6);
param.init_constant(1.5);
for elem in param.data.iter() {
assert!((elem - 1.5).abs() < 1e-10);
}
}
#[test]
fn test_hadamard_gate() {
let mut qdev = TQDevice::new(1);
let mut h = TQHadamard::new();
h.apply(&mut qdev, &[0]).expect("Hadamard should succeed");
let probs = qdev.get_probs_1d();
assert!((probs[[0, 0]] - 0.5).abs() < 1e-10);
assert!((probs[[0, 1]] - 0.5).abs() < 1e-10);
}
#[test]
fn test_pauli_x_gate() {
let mut qdev = TQDevice::new(1);
let mut x = TQPauliX::new();
x.apply(&mut qdev, &[0]).expect("PauliX should succeed");
let probs = qdev.get_probs_1d();
assert!(probs[[0, 0]].abs() < 1e-10);
assert!((probs[[0, 1]] - 1.0).abs() < 1e-10);
}
#[test]
fn test_rx_gate() {
let mut qdev = TQDevice::new(1);
let mut rx = TQRx::new(true, false);
rx.apply_with_params(&mut qdev, &[0], Some(&[PI]))
.expect("RX should succeed");
let probs = qdev.get_probs_1d();
assert!(probs[[0, 0]].abs() < 1e-10);
assert!((probs[[0, 1]] - 1.0).abs() < 1e-10);
}
#[test]
fn test_cnot_gate() {
let mut qdev = TQDevice::new(2);
let mut x = TQPauliX::new();
let mut cnot = TQCNOT::new();
x.apply(&mut qdev, &[0]).expect("X should succeed");
cnot.apply(&mut qdev, &[0, 1]).expect("CNOT should succeed");
let probs = qdev.get_probs_1d();
assert!(probs[[0, 0]].abs() < 1e-10); assert!(probs[[0, 1]].abs() < 1e-10); assert!(probs[[0, 2]].abs() < 1e-10); assert!((probs[[0, 3]] - 1.0).abs() < 1e-10); }
#[test]
fn test_bell_state() {
let mut qdev = TQDevice::new(2);
let mut h = TQHadamard::new();
let mut cnot = TQCNOT::new();
h.apply(&mut qdev, &[0]).expect("H should succeed");
cnot.apply(&mut qdev, &[0, 1]).expect("CNOT should succeed");
let probs = qdev.get_probs_1d();
assert!((probs[[0, 0]] - 0.5).abs() < 1e-10); assert!(probs[[0, 1]].abs() < 1e-10); assert!(probs[[0, 2]].abs() < 1e-10); assert!((probs[[0, 3]] - 0.5).abs() < 1e-10); }
#[test]
fn test_module_list() {
let mut qdev = TQDevice::new(2);
let mut module_list = TQModuleList::new();
module_list.append(Box::new(TQHadamard::new()));
module_list.append(Box::new(TQPauliX::new()));
assert_eq!(module_list.len(), 2);
assert!(!module_list.is_empty());
}
}