use super::{GetValueMixed, HermitianMixedProduct, MixedIndex};
use crate::bosons::BosonProduct;
use crate::fermions::FermionProduct;
use crate::spins::PauliProduct;
use crate::{CorrespondsTo, StruqtureError, SymmetricIndex};
use num_complex::Complex64;
use serde::{
de::{Error, SeqAccess, Visitor},
ser::SerializeTuple,
Deserialize, Deserializer, Serialize, Serializer,
};
use std::{ops::Mul, str::FromStr};
use tinyvec::TinyVec;
#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Default)]
pub struct MixedProduct {
pub(crate) spins: TinyVec<[PauliProduct; 2]>,
pub(crate) bosons: TinyVec<[BosonProduct; 2]>,
pub(crate) fermions: TinyVec<[FermionProduct; 2]>,
}
#[cfg(feature = "json_schema")]
impl schemars::JsonSchema for MixedProduct {
fn schema_name() -> std::borrow::Cow<'static, str> {
"MixedProduct".into()
}
fn json_schema(_generator: &mut schemars::SchemaGenerator) -> schemars::Schema {
schemars::json_schema!({
"type": "string",
"description": "Represents products of Spin operators and Bosonic and Fermionic creators and annhilators by a string. Spin Operators X, Y and Z are preceeded and creators (c) and annihilators (a) are followed by the modes they are acting on. E.g. :S0X1Y:Bc0a0:Fc0a0:."
})
}
}
impl crate::SerializationSupport for MixedProduct {
fn struqture_type() -> crate::StruqtureType {
crate::StruqtureType::MixedProduct
}
}
impl Serialize for MixedProduct {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let readable = serializer.is_human_readable();
if readable {
serializer.serialize_str(&self.to_string())
} else {
let mut tuple = serializer.serialize_tuple(3)?;
tuple.serialize_element(&self.spins.as_slice())?;
tuple.serialize_element(&self.bosons.as_slice())?;
tuple.serialize_element(&self.fermions.as_slice())?;
tuple.end()
}
}
}
impl<'de> Deserialize<'de> for MixedProduct {
fn deserialize<D>(deserializer: D) -> Result<MixedProduct, D::Error>
where
D: Deserializer<'de>,
{
let human_readable = deserializer.is_human_readable();
if human_readable {
struct TemporaryVisitor;
impl<'de> Visitor<'de> for TemporaryVisitor {
type Value = MixedProduct;
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
formatter.write_str("String")
}
fn visit_str<E>(self, v: &str) -> Result<MixedProduct, E>
where
E: serde::de::Error,
{
MixedProduct::from_str(v).map_err(|err| E::custom(format!("{err:?}")))
}
fn visit_borrowed_str<E>(self, v: &'de str) -> Result<MixedProduct, E>
where
E: serde::de::Error,
{
MixedProduct::from_str(v).map_err(|err| E::custom(format!("{err:?}")))
}
}
deserializer.deserialize_str(TemporaryVisitor)
} else {
struct MixedProductVisitor;
impl<'de> serde::de::Visitor<'de> for MixedProductVisitor {
type Value = MixedProduct;
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
std::fmt::Formatter::write_str(
formatter,
"Tuple of two sequences of unsigned integers",
)
}
fn visit_seq<M>(self, mut access: M) -> Result<Self::Value, M::Error>
where
M: SeqAccess<'de>,
{
let spins: TinyVec<[PauliProduct; 2]> = match access.next_element()? {
Some(x) => x,
None => {
return Err(M::Error::custom("Missing spin sequence".to_string()));
}
};
let bosons: TinyVec<[BosonProduct; 2]> = match access.next_element()? {
Some(x) => x,
None => {
return Err(M::Error::custom("Missing boson sequence".to_string()));
}
};
let fermions: TinyVec<[FermionProduct; 2]> = match access.next_element()? {
Some(x) => x,
None => {
return Err(M::Error::custom("Missing fermion sequence".to_string()));
}
};
Ok(MixedProduct {
spins,
bosons,
fermions,
})
}
}
let pp_visitor = MixedProductVisitor;
deserializer.deserialize_tuple(3, pp_visitor)
}
}
}
impl MixedProduct {
#[cfg(feature = "struqture_1_export")]
pub fn to_struqture_1(
&self,
) -> Result<struqture_1::mixed_systems::MixedProduct, StruqtureError> {
let self_string = self.to_string();
let struqture_1_product = struqture_1::mixed_systems::MixedProduct::from_str(&self_string)
.map_err(|err| StruqtureError::GenericError {
msg: format!("{err}"),
})?;
Ok(struqture_1_product)
}
#[cfg(feature = "struqture_1_import")]
pub fn from_struqture_1(
value: &struqture_1::mixed_systems::MixedProduct,
) -> Result<Self, StruqtureError> {
let value_string = value.to_string();
let pauli_product = Self::from_str(&value_string)?;
Ok(pauli_product)
}
}
impl MixedIndex for MixedProduct {
type SpinIndexType = PauliProduct;
type BosonicIndexType = BosonProduct;
type FermionicIndexType = FermionProduct;
fn new(
spins: impl IntoIterator<Item = Self::SpinIndexType>,
bosons: impl IntoIterator<Item = Self::BosonicIndexType>,
fermions: impl IntoIterator<Item = Self::FermionicIndexType>,
) -> Result<Self, StruqtureError> {
Ok(Self {
spins: spins.into_iter().collect(),
bosons: bosons.into_iter().collect(),
fermions: fermions.into_iter().collect(),
})
}
fn spins(&self) -> std::slice::Iter<'_, PauliProduct> {
self.spins.iter()
}
fn bosons(&self) -> std::slice::Iter<'_, BosonProduct> {
self.bosons.iter()
}
fn fermions(&self) -> std::slice::Iter<'_, FermionProduct> {
self.fermions.iter()
}
fn create_valid_pair(
spins: impl IntoIterator<Item = Self::SpinIndexType>,
bosons: impl IntoIterator<Item = Self::BosonicIndexType>,
fermions: impl IntoIterator<Item = Self::FermionicIndexType>,
value: qoqo_calculator::CalculatorComplex,
) -> Result<(Self, qoqo_calculator::CalculatorComplex), StruqtureError> {
let spins: TinyVec<[PauliProduct; 2]> = spins.into_iter().collect();
let bosons: TinyVec<[BosonProduct; 2]> = bosons.into_iter().collect();
let fermions: TinyVec<[FermionProduct; 2]> = fermions.into_iter().collect();
Ok((
Self {
spins,
bosons,
fermions,
},
value,
))
}
}
impl FromStr for MixedProduct {
type Err = StruqtureError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let mut spins: TinyVec<[PauliProduct; 2]> = TinyVec::<[PauliProduct; 2]>::with_capacity(2);
let mut bosons: TinyVec<[BosonProduct; 2]> = TinyVec::<[BosonProduct; 2]>::with_capacity(2);
let mut fermions: TinyVec<[FermionProduct; 2]> =
TinyVec::<[FermionProduct; 2]>::with_capacity(2);
let subsystems = s.split(':').filter(|s| !s.is_empty());
for subsystem in subsystems {
if let Some(rest) = subsystem.strip_prefix('S') {
spins.push(PauliProduct::from_str(rest)?);
} else if let Some(rest) = subsystem.strip_prefix('B') {
bosons.push(BosonProduct::from_str(rest)?);
} else if let Some(rest) = subsystem.strip_prefix('F') {
fermions.push(FermionProduct::from_str(rest)?);
} else {
return Err(StruqtureError::ParsingError {
target_type: "MixedIndex".to_string(),
msg: format!(
"Encountered subsystem that is neither spin, nor boson, nor fermion: {subsystem}"
),
});
}
}
Ok(Self {
spins,
bosons,
fermions,
})
}
}
impl GetValueMixed<'_, MixedProduct> for MixedProduct {
fn get_key(index: &MixedProduct) -> Self {
index.clone()
}
fn get_transform(
_index: &MixedProduct,
value: qoqo_calculator::CalculatorComplex,
) -> qoqo_calculator::CalculatorComplex {
value
}
}
impl GetValueMixed<'_, HermitianMixedProduct> for MixedProduct {
fn get_key(index: &HermitianMixedProduct) -> Self {
Self {
spins: index.spins().cloned().collect(),
bosons: index.bosons().cloned().collect(),
fermions: index.fermions().cloned().collect(),
}
}
fn get_transform(
_index: &HermitianMixedProduct,
value: qoqo_calculator::CalculatorComplex,
) -> qoqo_calculator::CalculatorComplex {
value
}
}
impl CorrespondsTo<MixedProduct> for MixedProduct {
fn corresponds_to(&self) -> MixedProduct {
self.clone()
}
}
impl SymmetricIndex for MixedProduct {
fn hermitian_conjugate(&self) -> (Self, f64) {
let mut coefficient = 1.0;
let mut new_spins = self.spins.clone();
for spin in new_spins.iter_mut() {
let (conj_spin, coeff) = spin.hermitian_conjugate();
*spin = conj_spin;
coefficient *= coeff;
}
let mut new_bosons = self.bosons.clone();
for boson in new_bosons.iter_mut() {
let (conj_boson, coeff) = boson.hermitian_conjugate();
*boson = conj_boson;
coefficient *= coeff;
}
let mut new_fermions = self.fermions.clone();
for fermion in new_fermions.iter_mut() {
let (conj_fermion, coeff) = fermion.hermitian_conjugate();
*fermion = conj_fermion;
coefficient *= coeff;
}
(
Self {
spins: new_spins,
bosons: new_bosons,
fermions: new_fermions,
},
coefficient,
)
}
fn is_natural_hermitian(&self) -> bool {
self.bosons.iter().all(|b| b.is_natural_hermitian())
&& self.fermions.iter().all(|f| f.is_natural_hermitian())
}
}
impl Mul<MixedProduct> for MixedProduct {
type Output = Result<Vec<(MixedProduct, Complex64)>, StruqtureError>;
fn mul(self, rhs: MixedProduct) -> Self::Output {
if self.spins().len() != rhs.spins().len()
|| self.bosons().len() != rhs.bosons().len()
|| self.fermions().len() != rhs.fermions().len()
{
return Err(StruqtureError::MismatchedNumberSubsystems {
target_number_spin_subsystems: self.spins().len(),
target_number_boson_subsystems: self.bosons().len(),
target_number_fermion_subsystems: self.fermions().len(),
actual_number_spin_subsystems: rhs.spins().len(),
actual_number_boson_subsystems: rhs.bosons().len(),
actual_number_fermion_subsystems: rhs.fermions().len(),
});
}
let mut coefficient = Complex64::new(1.0, 0.0);
let mut result_vec: Vec<(MixedProduct, Complex64)> = Vec::new();
let mut tmp_spins: Vec<PauliProduct> = Vec::with_capacity(self.spins().len());
let mut tmp_bosons: Vec<Vec<BosonProduct>> = Vec::with_capacity(self.bosons().len());
let mut tmp_fermions: Vec<Vec<(FermionProduct, f64)>> =
Vec::with_capacity(self.fermions().len());
for (left, right) in self.spins.into_iter().zip(rhs.spins) {
let (val, coeff) = left * right;
tmp_spins.push(val);
coefficient *= coeff;
}
for (left, right) in self.bosons.into_iter().zip(rhs.bosons) {
let boson_multiplication = left.clone() * right.clone();
if !tmp_bosons.is_empty() {
let mut internal_tmp_bosons: Vec<Vec<BosonProduct>> = Vec::new();
for bp in boson_multiplication.clone() {
for tmp_bp in tmp_bosons.iter() {
let mut tmp_entry = tmp_bp.clone();
tmp_entry.push(bp.clone());
internal_tmp_bosons.push(tmp_entry);
}
}
tmp_bosons.clone_from(&internal_tmp_bosons);
} else {
for bp in boson_multiplication.clone() {
tmp_bosons.push(vec![bp]);
}
}
}
for (left, right) in self.fermions.into_iter().zip(rhs.fermions) {
let fermion_multiplication = left * right;
if !tmp_fermions.is_empty() {
let mut internal_tmp_fermions: Vec<Vec<(FermionProduct, f64)>> = Vec::new();
for fp in fermion_multiplication {
for tmp_fp in tmp_fermions.iter() {
let mut tmp_entry = tmp_fp.clone();
tmp_entry.push(fp.clone());
internal_tmp_fermions.push(tmp_entry);
}
}
tmp_fermions = internal_tmp_fermions;
} else {
for fp in fermion_multiplication.clone() {
tmp_fermions.push(vec![fp]);
}
}
}
for boson in tmp_bosons.clone() {
if !tmp_fermions.is_empty() {
for fermion in tmp_fermions.iter() {
let mut fermion_vec: Vec<FermionProduct> = Vec::new();
let mut sign = Complex64::new(1.0, 0.0);
for (f, val) in fermion {
fermion_vec.push(f.clone());
sign *= val;
}
result_vec.push((
MixedProduct::new(tmp_spins.clone(), boson.clone(), fermion_vec)?,
coefficient * sign,
));
}
} else {
result_vec.push((
MixedProduct::new(tmp_spins.clone(), boson.clone(), vec![])?,
coefficient,
));
}
}
if tmp_bosons.is_empty() && !tmp_fermions.is_empty() {
for fermion in tmp_fermions.iter() {
let mut fermion_vec: Vec<FermionProduct> = Vec::new();
let mut sign = Complex64::new(1.0, 0.0);
for (f, val) in fermion {
fermion_vec.push(f.clone());
sign *= val;
}
result_vec.push((
MixedProduct::new(tmp_spins.clone(), [], fermion_vec)?,
coefficient * sign,
));
}
} else if tmp_bosons.is_empty() && tmp_fermions.is_empty() {
result_vec.push((MixedProduct::new(tmp_spins.clone(), [], [])?, coefficient))
}
Ok(result_vec)
}
}
impl Mul<HermitianMixedProduct> for MixedProduct {
type Output = Result<Vec<(MixedProduct, Complex64)>, StruqtureError>;
fn mul(self, rhs: HermitianMixedProduct) -> Self::Output {
if self.spins().len() != rhs.spins().len()
|| self.bosons().len() != rhs.bosons().len()
|| self.fermions().len() != rhs.fermions().len()
{
return Err(StruqtureError::MismatchedNumberSubsystems {
target_number_spin_subsystems: self.spins().len(),
target_number_boson_subsystems: self.bosons().len(),
target_number_fermion_subsystems: self.fermions().len(),
actual_number_spin_subsystems: rhs.spins().len(),
actual_number_boson_subsystems: rhs.bosons().len(),
actual_number_fermion_subsystems: rhs.fermions().len(),
});
}
let mut result_vec: Vec<(MixedProduct, Complex64)> = Vec::new();
let mut right_to_mul: Vec<(MixedProduct, f64)> = Vec::new();
let mhp_right = MixedProduct::new(rhs.spins, rhs.bosons, rhs.fermions)
.expect("Could not convert rhs into a MixedProduct");
right_to_mul.push((mhp_right.clone(), 1.0));
if !mhp_right.is_natural_hermitian() {
right_to_mul.push(mhp_right.hermitian_conjugate());
}
for (rhs, rsign) in right_to_mul {
let mut coefficient = Complex64::new(rsign, 0.0);
let mut tmp_spins: Vec<PauliProduct> = Vec::with_capacity(self.spins().len());
let mut tmp_bosons: Vec<Vec<BosonProduct>> = Vec::with_capacity(self.bosons().len());
let mut tmp_fermions: Vec<Vec<(FermionProduct, f64)>> =
Vec::with_capacity(self.fermions().len());
for (left, right) in self.clone().spins.into_iter().zip(rhs.spins) {
let (val, coeff) = left * right;
tmp_spins.push(val);
coefficient *= coeff;
}
for (left, right) in self.clone().bosons.into_iter().zip(rhs.bosons) {
let boson_multiplication = left.clone() * right.clone();
if !tmp_bosons.is_empty() {
let mut internal_tmp_bosons: Vec<Vec<BosonProduct>> = Vec::new();
for bp in boson_multiplication.clone() {
for tmp_bp in tmp_bosons.iter() {
let mut tmp_entry = tmp_bp.clone();
tmp_entry.push(bp.clone());
internal_tmp_bosons.push(tmp_entry);
}
}
tmp_bosons.clone_from(&internal_tmp_bosons);
} else {
for bp in boson_multiplication.clone() {
tmp_bosons.push(vec![bp]);
}
}
}
for (left, right) in self.fermions.clone().into_iter().zip(rhs.fermions) {
let fermion_multiplication = left * right;
if !tmp_fermions.is_empty() {
let mut internal_tmp_fermions: Vec<Vec<(FermionProduct, f64)>> = Vec::new();
for fp in fermion_multiplication {
for tmp_fp in tmp_fermions.iter() {
let mut tmp_entry = tmp_fp.clone();
tmp_entry.push(fp.clone());
internal_tmp_fermions.push(tmp_entry);
}
}
tmp_fermions = internal_tmp_fermions;
} else {
for fp in fermion_multiplication.clone() {
tmp_fermions.push(vec![fp]);
}
}
}
for boson in tmp_bosons.clone() {
if !tmp_fermions.is_empty() {
for fermion in tmp_fermions.iter() {
let mut fermion_vec: Vec<FermionProduct> = Vec::new();
let mut sign = Complex64::new(1.0, 0.0);
for (f, val) in fermion {
fermion_vec.push(f.clone());
sign *= val;
}
result_vec.push((
MixedProduct::new(tmp_spins.clone(), boson.clone(), fermion_vec)?,
coefficient * sign,
));
}
} else {
result_vec.push((
MixedProduct::new(tmp_spins.clone(), boson.clone(), vec![])?,
coefficient,
));
}
}
if tmp_bosons.is_empty() && !tmp_fermions.is_empty() {
for fermion in tmp_fermions.iter() {
let mut fermion_vec: Vec<FermionProduct> = Vec::new();
let mut sign = Complex64::new(1.0, 0.0);
for (f, val) in fermion {
fermion_vec.push(f.clone());
sign *= val;
}
result_vec.push((
MixedProduct::new(tmp_spins.clone(), [], fermion_vec)?,
coefficient * sign,
));
}
} else if tmp_bosons.is_empty() && tmp_fermions.is_empty() {
result_vec.push((MixedProduct::new(tmp_spins.clone(), [], [])?, coefficient))
}
}
Ok(result_vec)
}
}
impl std::fmt::Display for MixedProduct {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut string: String = String::new();
for spin in self.spins() {
string.push_str(format!("S{spin}:").as_str());
}
for boson in self.bosons() {
string.push_str(format!("B{boson}:").as_str());
}
for fermion in self.fermions() {
string.push_str(format!("F{fermion}:").as_str());
}
write!(f, "{string}")
}
}
#[cfg(test)]
mod test {
use super::*;
use itertools::Itertools;
use test_case::test_case;
#[test_case("", &[], &[]; "empty")]
#[test_case(":S0X1X:", &[], &[PauliProduct::from_str("0X1X").unwrap()]; "single spin systems")]
#[test_case(":S0X1X:S0Z:", &[], &[PauliProduct::from_str("0X1X").unwrap(), PauliProduct::from_str("0Z").unwrap()]; "two spin systems")]
#[test_case(":S0X1X:Bc0a1:", &[BosonProduct::from_str("c0a1").unwrap()], &[PauliProduct::from_str("0X1X").unwrap()]; "spin-boson systems")]
fn from_string(stringformat: &str, bosons: &[BosonProduct], spins: &[PauliProduct]) {
let test_new = <MixedProduct as std::str::FromStr>::from_str(stringformat);
assert!(test_new.is_ok());
let res = test_new.unwrap();
let empty_bosons: Vec<BosonProduct> = bosons.to_vec();
let res_bosons: Vec<BosonProduct> = res.bosons.iter().cloned().collect_vec();
assert_eq!(res_bosons, empty_bosons);
let empty_spins: Vec<PauliProduct> = spins.to_vec();
let res_spins: Vec<PauliProduct> = res.spins.iter().cloned().collect_vec();
assert_eq!(res_spins, empty_spins);
}
}