struqture/mixed_systems/
mixed_product.rs

1// Copyright © 2021-2023 HQS Quantum Simulations GmbH. All Rights Reserved.
2//
3// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
4// in compliance with the License. You may obtain a copy of the License at
5//
6//     http://www.apache.org/licenses/LICENSE-2.0
7//
8// Unless required by applicable law or agreed to in writing, software distributed under the
9// License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
10// express or implied. See the License for the specific language governing permissions and
11// limitations under the License.
12
13use super::{GetValueMixed, HermitianMixedProduct, MixedIndex};
14use crate::bosons::BosonProduct;
15use crate::fermions::FermionProduct;
16use crate::spins::PauliProduct;
17use crate::{CorrespondsTo, StruqtureError, SymmetricIndex};
18use num_complex::Complex64;
19use serde::{
20    de::{Error, SeqAccess, Visitor},
21    ser::SerializeTuple,
22    Deserialize, Deserializer, Serialize, Serializer,
23};
24use std::{ops::Mul, str::FromStr};
25use tinyvec::TinyVec;
26
27/// A mixed product of pauli products, boson products and fermion products.
28///
29/// A [crate::spins::PauliProduct] is a representation of products of pauli matrices acting on qubits. It is used in order to build the corresponding spin terms of a hamiltonian.
30///
31/// A [crate::bosons::BosonProduct] is a product of bosonic creation and annihilation operators.
32/// It is used as an index for non-hermitian, normal ordered bosonic operators.
33///
34/// A [crate::fermions::FermionProduct] is a product of fermionic creation and annihilation operators.
35/// It is used as an index for non-hermitian, normal ordered fermionic operators.
36///
37/// # Example
38///
39/// ```
40/// use struqture::prelude::*;
41/// use struqture::spins::PauliProduct;
42/// use struqture::bosons::BosonProduct;
43/// use struqture::fermions::FermionProduct;
44/// use struqture::mixed_systems::MixedProduct;
45///
46/// let m_product = MixedProduct::new([PauliProduct::new().z(0)], [BosonProduct::new([0], [1]).unwrap()], [FermionProduct::new([0], [0]).unwrap()]).unwrap();
47/// println!("{}", m_product);
48///
49/// ```
50#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Default)]
51pub struct MixedProduct {
52    /// List of spin sub-indices
53    pub(crate) spins: TinyVec<[PauliProduct; 2]>,
54    /// List of boson sub-indices
55    pub(crate) bosons: TinyVec<[BosonProduct; 2]>,
56    /// List of fermion sub-indices
57    pub(crate) fermions: TinyVec<[FermionProduct; 2]>,
58}
59
60#[cfg(feature = "json_schema")]
61impl schemars::JsonSchema for MixedProduct {
62    fn schema_name() -> std::borrow::Cow<'static, str> {
63        "MixedProduct".into()
64    }
65
66    fn json_schema(_generator: &mut schemars::SchemaGenerator) -> schemars::Schema {
67        schemars::json_schema!({
68            "type": "string",
69            "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:."
70        })
71    }
72}
73
74impl crate::SerializationSupport for MixedProduct {
75    fn struqture_type() -> crate::StruqtureType {
76        crate::StruqtureType::MixedProduct
77    }
78}
79
80impl Serialize for MixedProduct {
81    /// Serialization function for MixedProduct according to string type.
82    ///
83    /// # Arguments
84    ///
85    /// * `self` - MixedProduct to be serialized.
86    /// * `serializer` - Serializer used for serialization.
87    ///
88    /// # Returns
89    ///
90    /// `S::Ok` - Serialized instance of MixedProduct.
91    /// `S::Error` - Error in the serialization process.
92    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
93    where
94        S: Serializer,
95    {
96        let readable = serializer.is_human_readable();
97        if readable {
98            serializer.serialize_str(&self.to_string())
99        } else {
100            let mut tuple = serializer.serialize_tuple(3)?;
101            tuple.serialize_element(&self.spins.as_slice())?;
102            tuple.serialize_element(&self.bosons.as_slice())?;
103            tuple.serialize_element(&self.fermions.as_slice())?;
104            tuple.end()
105        }
106    }
107}
108
109/// Deserializing directly from string.
110///
111impl<'de> Deserialize<'de> for MixedProduct {
112    /// Deserialization function for MixedProduct.
113    ///
114    /// # Arguments
115    ///
116    /// * `self` - Serialized instance of MixedProduct to be deserialized.
117    /// * `deserializer` - Deserializer used for deserialization.
118    ///
119    /// # Returns
120    ///
121    /// `DecoherenceProduct` - Deserialized instance of MixedProduct.
122    /// `D::Error` - Error in the deserialization process.
123    fn deserialize<D>(deserializer: D) -> Result<MixedProduct, D::Error>
124    where
125        D: Deserializer<'de>,
126    {
127        let human_readable = deserializer.is_human_readable();
128        if human_readable {
129            struct TemporaryVisitor;
130            impl<'de> Visitor<'de> for TemporaryVisitor {
131                type Value = MixedProduct;
132
133                fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
134                    formatter.write_str("String")
135                }
136
137                fn visit_str<E>(self, v: &str) -> Result<MixedProduct, E>
138                where
139                    E: serde::de::Error,
140                {
141                    MixedProduct::from_str(v).map_err(|err| E::custom(format!("{err:?}")))
142                }
143
144                fn visit_borrowed_str<E>(self, v: &'de str) -> Result<MixedProduct, E>
145                where
146                    E: serde::de::Error,
147                {
148                    MixedProduct::from_str(v).map_err(|err| E::custom(format!("{err:?}")))
149                }
150            }
151
152            deserializer.deserialize_str(TemporaryVisitor)
153        } else {
154            struct MixedProductVisitor;
155            impl<'de> serde::de::Visitor<'de> for MixedProductVisitor {
156                type Value = MixedProduct;
157                fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
158                    std::fmt::Formatter::write_str(
159                        formatter,
160                        "Tuple of two sequences of unsigned integers",
161                    )
162                }
163                // when variants are marked by String values
164                fn visit_seq<M>(self, mut access: M) -> Result<Self::Value, M::Error>
165                where
166                    M: SeqAccess<'de>,
167                {
168                    let spins: TinyVec<[PauliProduct; 2]> = match access.next_element()? {
169                        Some(x) => x,
170                        None => {
171                            return Err(M::Error::custom("Missing spin sequence".to_string()));
172                        }
173                    };
174                    let bosons: TinyVec<[BosonProduct; 2]> = match access.next_element()? {
175                        Some(x) => x,
176                        None => {
177                            return Err(M::Error::custom("Missing boson sequence".to_string()));
178                        }
179                    };
180                    let fermions: TinyVec<[FermionProduct; 2]> = match access.next_element()? {
181                        Some(x) => x,
182                        None => {
183                            return Err(M::Error::custom("Missing fermion sequence".to_string()));
184                        }
185                    };
186
187                    Ok(MixedProduct {
188                        spins,
189                        bosons,
190                        fermions,
191                    })
192                }
193            }
194            let pp_visitor = MixedProductVisitor;
195
196            deserializer.deserialize_tuple(3, pp_visitor)
197        }
198    }
199}
200
201impl MixedProduct {
202    /// Export to struqture_1 format.
203    #[cfg(feature = "struqture_1_export")]
204    pub fn to_struqture_1(
205        &self,
206    ) -> Result<struqture_1::mixed_systems::MixedProduct, StruqtureError> {
207        let self_string = self.to_string();
208        let struqture_1_product = struqture_1::mixed_systems::MixedProduct::from_str(&self_string)
209            .map_err(|err| StruqtureError::GenericError {
210                msg: format!("{err}"),
211            })?;
212        Ok(struqture_1_product)
213    }
214
215    /// Export to struqture_1 format.
216    #[cfg(feature = "struqture_1_import")]
217    pub fn from_struqture_1(
218        value: &struqture_1::mixed_systems::MixedProduct,
219    ) -> Result<Self, StruqtureError> {
220        let value_string = value.to_string();
221        let pauli_product = Self::from_str(&value_string)?;
222        Ok(pauli_product)
223    }
224}
225
226impl MixedIndex for MixedProduct {
227    type SpinIndexType = PauliProduct;
228    type BosonicIndexType = BosonProduct;
229    type FermionicIndexType = FermionProduct;
230
231    /// Creates a new MixedProduct.
232    ///
233    /// # Arguments
234    ///
235    /// * `spins` - Products of pauli operators acting on qubits.
236    /// * `bosons` - Products of bosonic creation and annihilation operators.
237    /// * `fermions` - Products of fermionic creation and annihilation operators.
238    ///
239    /// # Returns
240    ///
241    /// * Ok(`Self`) - a new MixedProduct with the input of spins and bosons.
242    fn new(
243        spins: impl IntoIterator<Item = Self::SpinIndexType>,
244        bosons: impl IntoIterator<Item = Self::BosonicIndexType>,
245        fermions: impl IntoIterator<Item = Self::FermionicIndexType>,
246    ) -> Result<Self, StruqtureError> {
247        Ok(Self {
248            spins: spins.into_iter().collect(),
249            bosons: bosons.into_iter().collect(),
250            fermions: fermions.into_iter().collect(),
251        })
252    }
253
254    // From trait
255    fn spins(&self) -> std::slice::Iter<'_, PauliProduct> {
256        self.spins.iter()
257    }
258
259    // From trait
260    fn bosons(&self) -> std::slice::Iter<'_, BosonProduct> {
261        self.bosons.iter()
262    }
263
264    // From trait
265    fn fermions(&self) -> std::slice::Iter<'_, FermionProduct> {
266        self.fermions.iter()
267    }
268
269    /// Creates a pair (MixedProduct, CalculatorComplex).
270    ///
271    /// The first item is the valid MixedProduct created from the input spins, bosons and fermions.
272    /// The second term is the input CalculatorComplex transformed according to the valid order of inputs.
273    ///
274    /// # Arguments
275    ///
276    /// * `spins` - The PauliProducts to have in the MixedProduct.
277    /// * `bosons` - The BosonProducts to have in the MixedProduct.
278    /// * `fermions` - The FermionProducts to have in the MixedProduct.
279    /// * `value` - The CalculatorComplex to transform.
280    ///
281    /// # Returns
282    ///
283    /// * `Ok((MixedProduct, CalculatorComplex))` - The valid MixedProduct and the corresponding transformed CalculatorComplex.
284    fn create_valid_pair(
285        spins: impl IntoIterator<Item = Self::SpinIndexType>,
286        bosons: impl IntoIterator<Item = Self::BosonicIndexType>,
287        fermions: impl IntoIterator<Item = Self::FermionicIndexType>,
288        value: qoqo_calculator::CalculatorComplex,
289    ) -> Result<(Self, qoqo_calculator::CalculatorComplex), StruqtureError> {
290        let spins: TinyVec<[PauliProduct; 2]> = spins.into_iter().collect();
291        let bosons: TinyVec<[BosonProduct; 2]> = bosons.into_iter().collect();
292        let fermions: TinyVec<[FermionProduct; 2]> = fermions.into_iter().collect();
293        Ok((
294            Self {
295                spins,
296                bosons,
297                fermions,
298            },
299            value,
300        ))
301    }
302}
303
304impl FromStr for MixedProduct {
305    type Err = StruqtureError;
306    /// Constructs a MixedProduct from a string.
307    ///
308    /// # Arguments
309    ///
310    /// * `s` - The string to convert.
311    ///
312    /// # Returns
313    ///
314    /// * `Ok(Self)` - The successfully converted MixedProduct.
315    /// * `Err(StruqtureError::ParsingError)` - Encountered subsystem that is neither spin, nor boson, nor fermion.
316    fn from_str(s: &str) -> Result<Self, Self::Err> {
317        let mut spins: TinyVec<[PauliProduct; 2]> = TinyVec::<[PauliProduct; 2]>::with_capacity(2);
318        let mut bosons: TinyVec<[BosonProduct; 2]> = TinyVec::<[BosonProduct; 2]>::with_capacity(2);
319        let mut fermions: TinyVec<[FermionProduct; 2]> =
320            TinyVec::<[FermionProduct; 2]>::with_capacity(2);
321        let subsystems = s.split(':').filter(|s| !s.is_empty());
322        for subsystem in subsystems {
323            if let Some(rest) = subsystem.strip_prefix('S') {
324                spins.push(PauliProduct::from_str(rest)?);
325            } else if let Some(rest) = subsystem.strip_prefix('B') {
326                bosons.push(BosonProduct::from_str(rest)?);
327            } else if let Some(rest) = subsystem.strip_prefix('F') {
328                fermions.push(FermionProduct::from_str(rest)?);
329            } else {
330                return Err(StruqtureError::ParsingError {
331                    target_type: "MixedIndex".to_string(),
332                    msg: format!(
333                        "Encountered subsystem that is neither spin, nor boson, nor fermion: {subsystem}"
334                    ),
335                });
336            }
337        }
338
339        Ok(Self {
340            spins,
341            bosons,
342            fermions,
343        })
344    }
345}
346
347impl GetValueMixed<'_, MixedProduct> for MixedProduct {
348    /// Gets the key corresponding to the input index (here, itself).
349    ///
350    /// # Arguments
351    ///
352    /// * `index` - The index for which to get the corresponding Product.
353    ///
354    /// # Returns
355    ///
356    /// * `Self` - The corresponding MixedProduct.
357    fn get_key(index: &MixedProduct) -> Self {
358        index.clone()
359    }
360
361    /// Gets the transformed value corresponding to the input index and value (here, itself).
362    ///
363    /// # Arguments
364    ///
365    /// * `index` - The index to transform the value by.
366    /// * `value` - The value to be transformed.
367    ///
368    /// # Returns
369    ///
370    /// * `CalculatorComplex` - The transformed value.
371    fn get_transform(
372        _index: &MixedProduct,
373        value: qoqo_calculator::CalculatorComplex,
374    ) -> qoqo_calculator::CalculatorComplex {
375        value
376    }
377}
378
379impl GetValueMixed<'_, HermitianMixedProduct> for MixedProduct {
380    /// Gets the key corresponding to the input index.
381    ///
382    /// # Arguments
383    ///
384    /// * `index` - The index for which to get the corresponding Product.
385    ///
386    /// # Returns
387    ///
388    /// * `Self` - The corresponding MixedProduct.
389    fn get_key(index: &HermitianMixedProduct) -> Self {
390        Self {
391            spins: index.spins().cloned().collect(),
392            bosons: index.bosons().cloned().collect(),
393            fermions: index.fermions().cloned().collect(),
394        }
395    }
396
397    /// Gets the transformed value corresponding to the input index and value (here, itself).
398    ///
399    /// # Arguments
400    ///
401    /// * `index` - The index to transform the value by.
402    /// * `value` - The value to be transformed.
403    ///
404    /// # Returns
405    ///
406    /// * `CalculatorComplex` - The transformed value.
407    fn get_transform(
408        _index: &HermitianMixedProduct,
409        value: qoqo_calculator::CalculatorComplex,
410    ) -> qoqo_calculator::CalculatorComplex {
411        value
412    }
413}
414
415impl CorrespondsTo<MixedProduct> for MixedProduct {
416    /// Gets the MixedProduct corresponding to self (here, itself).
417    ///
418    /// # Returns
419    ///
420    /// * `MixedProduct` - The MixedProduct corresponding to Self.
421    fn corresponds_to(&self) -> MixedProduct {
422        self.clone()
423    }
424}
425
426impl SymmetricIndex for MixedProduct {
427    // From trait
428    fn hermitian_conjugate(&self) -> (Self, f64) {
429        let mut coefficient = 1.0;
430
431        let mut new_spins = self.spins.clone();
432        for spin in new_spins.iter_mut() {
433            let (conj_spin, coeff) = spin.hermitian_conjugate();
434            *spin = conj_spin;
435            coefficient *= coeff;
436        }
437        let mut new_bosons = self.bosons.clone();
438        for boson in new_bosons.iter_mut() {
439            let (conj_boson, coeff) = boson.hermitian_conjugate();
440            *boson = conj_boson;
441            coefficient *= coeff;
442        }
443        let mut new_fermions = self.fermions.clone();
444        for fermion in new_fermions.iter_mut() {
445            let (conj_fermion, coeff) = fermion.hermitian_conjugate();
446            *fermion = conj_fermion;
447            coefficient *= coeff;
448        }
449        (
450            Self {
451                spins: new_spins,
452                bosons: new_bosons,
453                fermions: new_fermions,
454            },
455            coefficient,
456        )
457    }
458
459    // From trait
460    fn is_natural_hermitian(&self) -> bool {
461        self.bosons.iter().all(|b| b.is_natural_hermitian())
462            && self.fermions.iter().all(|f| f.is_natural_hermitian())
463    }
464}
465
466/// Implements the multiplication function of MixedProduct by MixedProduct.
467///
468impl Mul<MixedProduct> for MixedProduct {
469    type Output = Result<Vec<(MixedProduct, Complex64)>, StruqtureError>;
470
471    /// Implement `*` for MixedProduct and MixedProduct.
472    ///
473    /// # Arguments
474    ///
475    /// * `other` - The MixedProduct to multiply by.
476    ///
477    /// # Returns
478    ///
479    /// * `Ok(Vec<(MixedProduct, Complex64)>)` - The two MixedProducts multiplied.
480    /// * `Err(StruqtureError::MismatchedNumberSubsystems)` - Number of subsystems in left and right do not match.
481    fn mul(self, rhs: MixedProduct) -> Self::Output {
482        if self.spins().len() != rhs.spins().len()
483            || self.bosons().len() != rhs.bosons().len()
484            || self.fermions().len() != rhs.fermions().len()
485        {
486            return Err(StruqtureError::MismatchedNumberSubsystems {
487                target_number_spin_subsystems: self.spins().len(),
488                target_number_boson_subsystems: self.bosons().len(),
489                target_number_fermion_subsystems: self.fermions().len(),
490                actual_number_spin_subsystems: rhs.spins().len(),
491                actual_number_boson_subsystems: rhs.bosons().len(),
492                actual_number_fermion_subsystems: rhs.fermions().len(),
493            });
494        }
495        let mut coefficient = Complex64::new(1.0, 0.0);
496        let mut result_vec: Vec<(MixedProduct, Complex64)> = Vec::new();
497        let mut tmp_spins: Vec<PauliProduct> = Vec::with_capacity(self.spins().len());
498        let mut tmp_bosons: Vec<Vec<BosonProduct>> = Vec::with_capacity(self.bosons().len());
499        let mut tmp_fermions: Vec<Vec<(FermionProduct, f64)>> =
500            Vec::with_capacity(self.fermions().len());
501        for (left, right) in self.spins.into_iter().zip(rhs.spins.into_iter()) {
502            let (val, coeff) = left * right;
503            tmp_spins.push(val);
504            coefficient *= coeff;
505        }
506        // iterate through boson subsystems and multiply subsystem
507        for (left, right) in self.bosons.into_iter().zip(rhs.bosons.into_iter()) {
508            let boson_multiplication = left.clone() * right.clone();
509            if !tmp_bosons.is_empty() {
510                let mut internal_tmp_bosons: Vec<Vec<BosonProduct>> = Vec::new();
511                for bp in boson_multiplication.clone() {
512                    for tmp_bp in tmp_bosons.iter() {
513                        let mut tmp_entry = tmp_bp.clone();
514                        tmp_entry.push(bp.clone());
515                        internal_tmp_bosons.push(tmp_entry);
516                    }
517                }
518                tmp_bosons.clone_from(&internal_tmp_bosons);
519            } else {
520                for bp in boson_multiplication.clone() {
521                    tmp_bosons.push(vec![bp]);
522                }
523            }
524        }
525        for (left, right) in self.fermions.into_iter().zip(rhs.fermions.into_iter()) {
526            let fermion_multiplication = left * right;
527            if !tmp_fermions.is_empty() {
528                let mut internal_tmp_fermions: Vec<Vec<(FermionProduct, f64)>> = Vec::new();
529                for fp in fermion_multiplication {
530                    for tmp_fp in tmp_fermions.iter() {
531                        let mut tmp_entry = tmp_fp.clone();
532                        tmp_entry.push(fp.clone());
533                        internal_tmp_fermions.push(tmp_entry);
534                    }
535                }
536                tmp_fermions = internal_tmp_fermions;
537            } else {
538                for fp in fermion_multiplication.clone() {
539                    tmp_fermions.push(vec![fp]);
540                }
541            }
542        }
543
544        // Combining results
545        for boson in tmp_bosons.clone() {
546            if !tmp_fermions.is_empty() {
547                for fermion in tmp_fermions.iter() {
548                    let mut fermion_vec: Vec<FermionProduct> = Vec::new();
549                    let mut sign = Complex64::new(1.0, 0.0);
550                    for (f, val) in fermion {
551                        fermion_vec.push(f.clone());
552                        sign *= val;
553                    }
554                    result_vec.push((
555                        MixedProduct::new(tmp_spins.clone(), boson.clone(), fermion_vec)?,
556                        coefficient * sign,
557                    ));
558                }
559            } else {
560                result_vec.push((
561                    MixedProduct::new(tmp_spins.clone(), boson.clone(), vec![])?,
562                    coefficient,
563                ));
564            }
565        }
566        if tmp_bosons.is_empty() && !tmp_fermions.is_empty() {
567            for fermion in tmp_fermions.iter() {
568                let mut fermion_vec: Vec<FermionProduct> = Vec::new();
569                let mut sign = Complex64::new(1.0, 0.0);
570                for (f, val) in fermion {
571                    fermion_vec.push(f.clone());
572                    sign *= val;
573                }
574                result_vec.push((
575                    MixedProduct::new(tmp_spins.clone(), [], fermion_vec)?,
576                    coefficient * sign,
577                ));
578            }
579        } else if tmp_bosons.is_empty() && tmp_fermions.is_empty() {
580            result_vec.push((MixedProduct::new(tmp_spins.clone(), [], [])?, coefficient))
581        }
582
583        Ok(result_vec)
584    }
585}
586
587impl Mul<HermitianMixedProduct> for MixedProduct {
588    type Output = Result<Vec<(MixedProduct, Complex64)>, StruqtureError>;
589
590    /// Implement `*` for a MixedProduct and a HermitianMixedProduct.
591    ///
592    /// # Arguments
593    ///
594    /// * `other` - The HermitianMixedProduct to multiply by.
595    ///
596    /// # Returns
597    ///
598    /// * `Ok(Vec<(MixedProduct, Complex64)>)` - The two MixedProducts multiplied.
599    /// * `Err(StruqtureError::MismatchedNumberSubsystems)` - Number of subsystems in left and right do not match.
600    ///
601    /// # Panics
602    ///
603    /// * Could not convert rhs into a MixedProduct.
604    fn mul(self, rhs: HermitianMixedProduct) -> Self::Output {
605        if self.spins().len() != rhs.spins().len()
606            || self.bosons().len() != rhs.bosons().len()
607            || self.fermions().len() != rhs.fermions().len()
608        {
609            return Err(StruqtureError::MismatchedNumberSubsystems {
610                target_number_spin_subsystems: self.spins().len(),
611                target_number_boson_subsystems: self.bosons().len(),
612                target_number_fermion_subsystems: self.fermions().len(),
613                actual_number_spin_subsystems: rhs.spins().len(),
614                actual_number_boson_subsystems: rhs.bosons().len(),
615                actual_number_fermion_subsystems: rhs.fermions().len(),
616            });
617        }
618        let mut result_vec: Vec<(MixedProduct, Complex64)> = Vec::new();
619
620        let mut right_to_mul: Vec<(MixedProduct, f64)> = Vec::new();
621        let mhp_right = MixedProduct::new(rhs.spins, rhs.bosons, rhs.fermions)
622            .expect("Could not convert rhs into a MixedProduct");
623        right_to_mul.push((mhp_right.clone(), 1.0));
624        if !mhp_right.is_natural_hermitian() {
625            right_to_mul.push(mhp_right.hermitian_conjugate());
626        }
627
628        for (rhs, rsign) in right_to_mul {
629            let mut coefficient = Complex64::new(rsign, 0.0);
630            let mut tmp_spins: Vec<PauliProduct> = Vec::with_capacity(self.spins().len());
631            let mut tmp_bosons: Vec<Vec<BosonProduct>> = Vec::with_capacity(self.bosons().len());
632            let mut tmp_fermions: Vec<Vec<(FermionProduct, f64)>> =
633                Vec::with_capacity(self.fermions().len());
634            for (left, right) in self.clone().spins.into_iter().zip(rhs.spins.into_iter()) {
635                let (val, coeff) = left * right;
636                tmp_spins.push(val);
637                coefficient *= coeff;
638            }
639            // iterate through boson subsystems and multiply subsystem
640            for (left, right) in self.clone().bosons.into_iter().zip(rhs.bosons.into_iter()) {
641                let boson_multiplication = left.clone() * right.clone();
642                if !tmp_bosons.is_empty() {
643                    let mut internal_tmp_bosons: Vec<Vec<BosonProduct>> = Vec::new();
644                    for bp in boson_multiplication.clone() {
645                        for tmp_bp in tmp_bosons.iter() {
646                            let mut tmp_entry = tmp_bp.clone();
647                            tmp_entry.push(bp.clone());
648                            internal_tmp_bosons.push(tmp_entry);
649                        }
650                    }
651                    tmp_bosons.clone_from(&internal_tmp_bosons);
652                } else {
653                    for bp in boson_multiplication.clone() {
654                        tmp_bosons.push(vec![bp]);
655                    }
656                }
657            }
658            for (left, right) in self
659                .fermions
660                .clone()
661                .into_iter()
662                .zip(rhs.fermions.into_iter())
663            {
664                let fermion_multiplication = left * right;
665                if !tmp_fermions.is_empty() {
666                    let mut internal_tmp_fermions: Vec<Vec<(FermionProduct, f64)>> = Vec::new();
667                    for fp in fermion_multiplication {
668                        for tmp_fp in tmp_fermions.iter() {
669                            let mut tmp_entry = tmp_fp.clone();
670                            tmp_entry.push(fp.clone());
671                            internal_tmp_fermions.push(tmp_entry);
672                        }
673                    }
674                    tmp_fermions = internal_tmp_fermions;
675                } else {
676                    for fp in fermion_multiplication.clone() {
677                        tmp_fermions.push(vec![fp]);
678                    }
679                }
680            }
681
682            // Combining results
683            for boson in tmp_bosons.clone() {
684                if !tmp_fermions.is_empty() {
685                    for fermion in tmp_fermions.iter() {
686                        let mut fermion_vec: Vec<FermionProduct> = Vec::new();
687                        let mut sign = Complex64::new(1.0, 0.0);
688                        for (f, val) in fermion {
689                            fermion_vec.push(f.clone());
690                            sign *= val;
691                        }
692                        result_vec.push((
693                            MixedProduct::new(tmp_spins.clone(), boson.clone(), fermion_vec)?,
694                            coefficient * sign,
695                        ));
696                    }
697                } else {
698                    result_vec.push((
699                        MixedProduct::new(tmp_spins.clone(), boson.clone(), vec![])?,
700                        coefficient,
701                    ));
702                }
703            }
704            if tmp_bosons.is_empty() && !tmp_fermions.is_empty() {
705                for fermion in tmp_fermions.iter() {
706                    let mut fermion_vec: Vec<FermionProduct> = Vec::new();
707                    let mut sign = Complex64::new(1.0, 0.0);
708                    for (f, val) in fermion {
709                        fermion_vec.push(f.clone());
710                        sign *= val;
711                    }
712                    result_vec.push((
713                        MixedProduct::new(tmp_spins.clone(), [], fermion_vec)?,
714                        coefficient * sign,
715                    ));
716                }
717            } else if tmp_bosons.is_empty() && tmp_fermions.is_empty() {
718                result_vec.push((MixedProduct::new(tmp_spins.clone(), [], [])?, coefficient))
719            }
720        }
721
722        Ok(result_vec)
723    }
724}
725
726/// Implements the format function (Display trait) of MixedProduct.
727///
728impl std::fmt::Display for MixedProduct {
729    /// Formats the MixedProduct using the given formatter.
730    ///
731    /// # Arguments
732    ///
733    /// * `f` - The formatter to use.
734    ///
735    /// # Returns
736    ///
737    /// * `std::fmt::Result` - The formatted MixedProduct.
738    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
739        let mut string: String = String::new();
740        for spin in self.spins() {
741            string.push_str(format!("S{spin}:").as_str());
742        }
743        for boson in self.bosons() {
744            string.push_str(format!("B{boson}:").as_str());
745        }
746        for fermion in self.fermions() {
747            string.push_str(format!("F{fermion}:").as_str());
748        }
749        write!(f, "{string}")
750    }
751}
752
753#[cfg(test)]
754mod test {
755    use super::*;
756    use itertools::Itertools;
757    use test_case::test_case;
758
759    #[test_case("", &[], &[]; "empty")]
760    #[test_case(":S0X1X:", &[], &[PauliProduct::from_str("0X1X").unwrap()]; "single spin systems")]
761    #[test_case(":S0X1X:S0Z:", &[], &[PauliProduct::from_str("0X1X").unwrap(), PauliProduct::from_str("0Z").unwrap()]; "two spin systems")]
762    #[test_case(":S0X1X:Bc0a1:", &[BosonProduct::from_str("c0a1").unwrap()], &[PauliProduct::from_str("0X1X").unwrap()]; "spin-boson systems")]
763    fn from_string(stringformat: &str, bosons: &[BosonProduct], spins: &[PauliProduct]) {
764        let test_new = <MixedProduct as std::str::FromStr>::from_str(stringformat);
765        assert!(test_new.is_ok());
766        let res = test_new.unwrap();
767        let empty_bosons: Vec<BosonProduct> = bosons.to_vec();
768        let res_bosons: Vec<BosonProduct> = res.bosons.iter().cloned().collect_vec();
769        assert_eq!(res_bosons, empty_bosons);
770        let empty_spins: Vec<PauliProduct> = spins.to_vec();
771        let res_spins: Vec<PauliProduct> = res.spins.iter().cloned().collect_vec();
772        assert_eq!(res_spins, empty_spins);
773    }
774}