use crate::complex::compatible;
use crate::gate::Axis;
use crate::state::compatible_states;
use crate::{Angle, Circuit, Error, ResourceLimits, Result, Rotation, StateVector, Unitary};
use burn::tensor::Tensor;
fn qubit_targets(circuit: &Circuit, wires: &[usize]) -> Result<()> {
circuit.register().local_size(wires)?;
if wires
.iter()
.any(|&w| circuit.register().dimensions()[w] != 2)
{
return Err(Error::InvalidDimensions(
"this template requires qubit targets; use explicit level rotations for qudits".into(),
));
}
Ok(())
}
pub fn angle_embedding(
circuit: &mut Circuit,
wires: &[usize],
axis: Axis,
input_offset: usize,
) -> Result<()> {
qubit_targets(circuit, wires)?;
let end = input_offset
.checked_add(wires.len())
.ok_or(Error::Overflow)?;
if end > circuit.num_inputs() {
return Err(Error::Shape("insufficient circuit input features".into()));
}
let mut candidate = circuit.clone();
for (j, &wire) in wires.iter().enumerate() {
candidate.rotate(wire, Rotation::qubit(axis, Angle::input(input_offset + j)))?;
}
*circuit = candidate;
Ok(())
}
pub fn ry_cnot_layer(
circuit: &mut Circuit,
wires: &[usize],
parameter_offset: usize,
) -> Result<()> {
qubit_targets(circuit, wires)?;
let end = parameter_offset
.checked_add(wires.len())
.ok_or(Error::Overflow)?;
if end > circuit.num_parameters() {
return Err(Error::Shape("insufficient circuit parameters".into()));
}
let mut candidate = circuit.clone();
for (j, &wire) in wires.iter().enumerate() {
candidate.rotate(
wire,
Rotation::qubit(Axis::Y, Angle::parameter(parameter_offset + j)),
)?;
}
for pair in wires.windows(2) {
candidate.unitary(pair.to_vec(), Unitary::cnot())?;
}
*circuit = candidate;
Ok(())
}
pub fn pauli_z_probabilities(expectations: Tensor<1>) -> Tensor<2> {
let [batch] = expectations.dims();
let zero = (expectations.clone() + 1.) / 2.;
let one = (-expectations + 1.) / 2.;
Tensor::cat(vec![zero.reshape([batch, 1]), one.reshape([batch, 1])], 1)
}
pub fn distribution_cross_entropy(
probabilities: Tensor<2>,
targets: Tensor<2>,
epsilon: f64,
) -> Result<Tensor<1>> {
let [batch, classes] = probabilities.dims();
if batch == 0 || classes < 2 || targets.dims() != [batch, classes] {
return Err(Error::Shape(
"cross-entropy needs matching nonempty [batch, classes>=2] tensors".into(),
));
}
compatible(&probabilities, &targets)?;
crate::backend::supported_dtype(probabilities.dtype())?;
crate::error::tolerance(epsilon)?;
if probabilities.dtype() == burn::tensor::DType::F32 && (epsilon as f32) == 0. {
return Err(Error::InvalidValue(
"epsilon underflows the probability dtype".into(),
));
}
Ok(-(targets * probabilities.clamp_min(epsilon).log())
.sum_dim(1)
.mean())
}
pub fn fidelity_kernel(
left: &StateVector,
right: &StateVector,
limits: ResourceLimits,
) -> Result<Tensor<2>> {
compatible_states(left, right)?;
limits.check_state(left.batch_size(), left.register().size())?;
limits.check_state(right.batch_size(), right.register().size())?;
limits.check_state(left.batch_size(), right.batch_size())?;
let a = left.amplitudes();
let b = right.amplitudes();
let re = crate::precision::matmul2(a.real(), b.real().transpose())
+ crate::precision::matmul2(a.imag(), b.imag().transpose());
let im = crate::precision::matmul2(a.real(), b.imag().transpose())
- crate::precision::matmul2(a.imag(), b.real().transpose());
Ok(re.square() + im.square())
}