struqture/mixed_systems/
mixed_hermitian_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, MixedIndex, MixedProduct};
14use crate::fermions::FermionProduct;
15use crate::prelude::*;
16use crate::CorrespondsTo;
17use crate::{bosons::BosonProduct, spins::PauliProduct, StruqtureError, SymmetricIndex};
18use num_complex::Complex64;
19use qoqo_calculator::CalculatorFloat;
20use serde::{
21    de::{Error, SeqAccess, Visitor},
22    ser::SerializeTuple,
23    Deserialize, Deserializer, Serialize, Serializer,
24};
25use std::{ops::Mul, str::FromStr};
26use tinyvec::TinyVec;
27
28/// A hermitian mixed product of pauli products, boson products and fermion products.
29///
30/// 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.
31///
32/// A [crate::bosons::BosonProduct] is a product of bosonic creation and annihilation operators.
33/// Here it is used as an index for hermitian, normal ordered bosonic operators.
34///
35/// A [crate::fermions::FermionProduct] is a product of fermionic creation and annihilation operators.
36/// It is used as an index for non-hermitian, normal ordered fermionic operators.
37///
38/// # Example
39///
40/// ```
41/// use struqture::prelude::*;
42/// use struqture::spins::PauliProduct;
43/// use struqture::bosons::BosonProduct;
44/// use struqture::fermions::FermionProduct;
45/// use struqture::mixed_systems::HermitianMixedProduct;
46///
47/// let m_product = HermitianMixedProduct::new([PauliProduct::new().z(0)], [BosonProduct::new([0], [1]).unwrap()], [FermionProduct::new([0], [0]).unwrap()]).unwrap();
48/// println!("{}", m_product);
49///
50/// ```
51#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Default)]
52pub struct HermitianMixedProduct {
53    /// List of spin sub-indices
54    pub(crate) spins: TinyVec<[PauliProduct; 2]>,
55    /// List of boson sub-indices
56    pub(crate) bosons: TinyVec<[BosonProduct; 2]>,
57    /// List of fermion sub-indices
58    pub(crate) fermions: TinyVec<[FermionProduct; 2]>,
59}
60
61#[cfg(feature = "json_schema")]
62impl schemars::JsonSchema for HermitianMixedProduct {
63    fn schema_name() -> std::borrow::Cow<'static, str> {
64        "HermitianMixedProduct".into()
65    }
66
67    fn json_schema(_generator: &mut schemars::SchemaGenerator) -> schemars::Schema {
68        schemars::json_schema!({
69            "type": "string",
70            "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:."
71        })
72    }
73}
74
75impl crate::SerializationSupport for HermitianMixedProduct {
76    fn struqture_type() -> crate::StruqtureType {
77        crate::StruqtureType::HermitianMixedProduct
78    }
79}
80
81impl Serialize for HermitianMixedProduct {
82    /// Serialization function for HermitianMixedProduct according to string type.
83    ///
84    /// # Arguments
85    ///
86    /// * `self` - HermitianMixedProduct to be serialized.
87    /// * `serializer` - Serializer used for serialization.
88    ///
89    /// # Returns
90    ///
91    /// `S::Ok` - Serialized instance of HermitianMixedProduct.
92    /// `S::Error` - Error in the serialization process.
93    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
94    where
95        S: Serializer,
96    {
97        let readable = serializer.is_human_readable();
98        if readable {
99            serializer.serialize_str(&self.to_string())
100        } else {
101            let mut tuple = serializer.serialize_tuple(3)?;
102            tuple.serialize_element(&self.spins.as_slice())?;
103            tuple.serialize_element(&self.bosons.as_slice())?;
104            tuple.serialize_element(&self.fermions.as_slice())?;
105            tuple.end()
106        }
107    }
108}
109
110/// Deserializing directly from string.
111///
112impl<'de> Deserialize<'de> for HermitianMixedProduct {
113    /// Deserialization function for HermitianMixedProduct.
114    ///
115    /// # Arguments
116    ///
117    /// * `self` - Serialized instance of HermitianMixedProduct to be deserialized.
118    /// * `deserializer` - Deserializer used for deserialization.
119    ///
120    /// # Returns
121    ///
122    /// `DecoherenceProduct` - Deserialized instance of HermitianMixedProduct.
123    /// `D::Error` - Error in the deserialization process.
124    fn deserialize<D>(deserializer: D) -> Result<HermitianMixedProduct, D::Error>
125    where
126        D: Deserializer<'de>,
127    {
128        let human_readable = deserializer.is_human_readable();
129        if human_readable {
130            struct TemporaryVisitor;
131            impl<'de> Visitor<'de> for TemporaryVisitor {
132                type Value = HermitianMixedProduct;
133
134                fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
135                    formatter.write_str("String")
136                }
137
138                fn visit_str<E>(self, v: &str) -> Result<HermitianMixedProduct, E>
139                where
140                    E: serde::de::Error,
141                {
142                    HermitianMixedProduct::from_str(v).map_err(|err| E::custom(format!("{err:?}")))
143                }
144
145                fn visit_borrowed_str<E>(self, v: &'de str) -> Result<HermitianMixedProduct, E>
146                where
147                    E: serde::de::Error,
148                {
149                    HermitianMixedProduct::from_str(v).map_err(|err| E::custom(format!("{err:?}")))
150                }
151            }
152
153            deserializer.deserialize_str(TemporaryVisitor)
154        } else {
155            struct HermitianMixedProductVisitor;
156            impl<'de> serde::de::Visitor<'de> for HermitianMixedProductVisitor {
157                type Value = HermitianMixedProduct;
158                fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
159                    std::fmt::Formatter::write_str(
160                        formatter,
161                        "Tuple of two sequences of unsigned integers",
162                    )
163                }
164                // when variants are marked by String values
165                fn visit_seq<M>(self, mut access: M) -> Result<Self::Value, M::Error>
166                where
167                    M: SeqAccess<'de>,
168                {
169                    let spins: TinyVec<[PauliProduct; 2]> = match access.next_element()? {
170                        Some(x) => x,
171                        None => {
172                            return Err(M::Error::custom("Missing creator sequence".to_string()));
173                        }
174                    };
175                    let bosons: TinyVec<[BosonProduct; 2]> = match access.next_element()? {
176                        Some(x) => x,
177                        None => {
178                            return Err(M::Error::custom(
179                                "Missing annihilator sequence".to_string(),
180                            ));
181                        }
182                    };
183                    let fermions: TinyVec<[FermionProduct; 2]> = match access.next_element()? {
184                        Some(x) => x,
185                        None => {
186                            return Err(M::Error::custom(
187                                "Missing annihilator sequence".to_string(),
188                            ));
189                        }
190                    };
191
192                    Ok(HermitianMixedProduct {
193                        spins,
194                        bosons,
195                        fermions,
196                    })
197                }
198            }
199            let pp_visitor = HermitianMixedProductVisitor;
200
201            deserializer.deserialize_tuple(3, pp_visitor)
202        }
203    }
204}
205
206impl HermitianMixedProduct {
207    /// Export to struqture_1 format.
208    #[cfg(feature = "struqture_1_export")]
209    pub fn to_struqture_1(
210        &self,
211    ) -> Result<struqture_1::mixed_systems::HermitianMixedProduct, StruqtureError> {
212        let self_string = self.to_string();
213        let struqture_1_product = struqture_1::mixed_systems::HermitianMixedProduct::from_str(
214            &self_string,
215        )
216        .map_err(|err| StruqtureError::GenericError {
217            msg: format!("{err}"),
218        })?;
219        Ok(struqture_1_product)
220    }
221
222    /// Export to struqture_1 format.
223    #[cfg(feature = "struqture_1_import")]
224    pub fn from_struqture_1(
225        value: &struqture_1::mixed_systems::HermitianMixedProduct,
226    ) -> Result<Self, StruqtureError> {
227        let value_string = value.to_string();
228        let pauli_product = Self::from_str(&value_string)?;
229        Ok(pauli_product)
230    }
231}
232
233impl MixedIndex for HermitianMixedProduct {
234    type SpinIndexType = PauliProduct;
235    type BosonicIndexType = BosonProduct;
236    type FermionicIndexType = FermionProduct;
237
238    /// Creates a new HermitianMixedProduct.
239    ///
240    /// # Arguments
241    ///
242    /// * `spins` - Products of pauli operators acting on qubits.
243    /// * `bosons` - Products of bosonic creation and annihilation operators.
244    /// * `fermions` - Products of fermionic creation and annihilation operators.
245    ///
246    /// # Returns
247    ///
248    /// * Ok(`Self`) - a new HermitianMixedProduct with the input of spins and bosons.
249    /// * Err(`StruqtureError::CreatorsAnnihilatorsMinimumIndex`) - The minimum index of the bosonic creators is larger than the minimum index of the bosonic annihilators.
250    /// * Err(`StruqtureError::CreatorsAnnihilatorsMinimumIndex`) - The minimum index of the fermionic creators is larger than the minimum index of the fermionic annihilators.
251    fn new(
252        spins: impl IntoIterator<Item = Self::SpinIndexType>,
253        bosons: impl IntoIterator<Item = Self::BosonicIndexType>,
254        fermions: impl IntoIterator<Item = Self::FermionicIndexType>,
255    ) -> Result<Self, StruqtureError> {
256        let spins: TinyVec<[PauliProduct; 2]> = spins.into_iter().collect();
257        let bosons: TinyVec<[BosonProduct; 2]> = bosons.into_iter().collect();
258        let fermions: TinyVec<[FermionProduct; 2]> = fermions.into_iter().collect();
259        // We need to determine a hierarchy for deciding which of the hermitian
260        // conjugated pairs of operators we are going to store.
261        // The decision tree is the following:
262        // If bosons are not empty we search through all boson products in order to find the first that
263        // decides which of the two hermitian conjugates is stored. A BosonProduct decides which variant is
264        // stored when there is at least one annihilator or creator in the BosonProduct that is not paired with
265        // a creator or annihilator acting on the same index. In that case the variant with the annihilator acting on the higher
266        // index is stored.
267        // If no boson subsystem can choose which variant is stored, the choice is based on the fermionic subsystems
268        // in the same way.
269        let mut hermitian_decision_made = false;
270        for b in bosons.iter() {
271            let mut number_equal_indices = 0;
272            for (creator, annihilator) in b.creators().zip(b.annihilators()) {
273                match annihilator.cmp(creator) {
274                    std::cmp::Ordering::Less => {
275                        return Err(StruqtureError::CreatorsAnnihilatorsMinimumIndex {
276                            creators_min: Some(*creator),
277                            annihilators_min: Some(*annihilator),
278                        });
279                    }
280                    std::cmp::Ordering::Greater => {
281                        hermitian_decision_made = true;
282                        break;
283                    }
284                    _ => {
285                        number_equal_indices += 1;
286                    }
287                }
288            }
289            if b.creators().len() > number_equal_indices
290                && b.annihilators().len() == number_equal_indices
291            {
292                return Err(StruqtureError::CreatorsAnnihilatorsMinimumIndex {
293                    creators_min: b.creators().nth(number_equal_indices).copied(),
294                    annihilators_min: None,
295                });
296            }
297
298            if hermitian_decision_made {
299                break;
300            }
301        }
302        if !hermitian_decision_made {
303            for f in fermions.iter() {
304                let mut number_equal_indices = 0;
305                for (creator, annihilator) in f.creators().zip(f.annihilators()) {
306                    match annihilator.cmp(creator) {
307                        std::cmp::Ordering::Less => {
308                            return Err(StruqtureError::CreatorsAnnihilatorsMinimumIndex {
309                                creators_min: Some(*creator),
310                                annihilators_min: Some(*annihilator),
311                            });
312                        }
313                        std::cmp::Ordering::Greater => {
314                            hermitian_decision_made = true;
315                            break;
316                        }
317                        _ => {
318                            number_equal_indices += 1;
319                        }
320                    }
321                }
322                if f.creators().len() > number_equal_indices
323                    && f.annihilators().len() == number_equal_indices
324                {
325                    return Err(StruqtureError::CreatorsAnnihilatorsMinimumIndex {
326                        creators_min: f.creators().nth(number_equal_indices).copied(),
327                        annihilators_min: None,
328                    });
329                }
330                if hermitian_decision_made {
331                    break;
332                }
333            }
334        }
335        Ok(Self {
336            spins,
337            bosons,
338            fermions,
339        })
340    }
341
342    // From trait
343    fn spins(&self) -> std::slice::Iter<'_, PauliProduct> {
344        self.spins.iter()
345    }
346
347    // From trait
348    fn bosons(&self) -> std::slice::Iter<'_, BosonProduct> {
349        self.bosons.iter()
350    }
351
352    // From trait
353    fn fermions(&self) -> std::slice::Iter<'_, FermionProduct> {
354        self.fermions.iter()
355    }
356
357    /// Creates a pair (HermitianMixedProduct, CalculatorComplex).
358    ///
359    /// The first item is the valid HermitianMixedProduct created from the input spins, bosons and fermions.
360    /// The second term is the input CalculatorComplex transformed according to the valid order of inputs.
361    ///
362    /// # Arguments
363    ///
364    /// * `spins` - The PauliProducts to have in the HermitianMixedProduct.
365    /// * `bosons` - The BosonProducts to have in the HermitianMixedProduct.
366    /// * `fermions` - The FermionProducts to have in the HermitianMixedProduct.
367    /// * `value` - The CalculatorComplex to transform.
368    ///
369    /// # Returns
370    ///
371    /// * `Ok((HermitianMixedProduct, CalculatorComplex))` - The valid HermitianMixedProduct and the corresponding transformed CalculatorComplex.
372    /// * `Err(StruqtureError::NonHermitianOperator)` - Key is naturally hermitian (on-diagonal term), but its corresponding value is not real.
373    fn create_valid_pair(
374        spins: impl IntoIterator<Item = Self::SpinIndexType>,
375        bosons: impl IntoIterator<Item = Self::BosonicIndexType>,
376        fermions: impl IntoIterator<Item = Self::FermionicIndexType>,
377        value: qoqo_calculator::CalculatorComplex,
378    ) -> Result<(Self, qoqo_calculator::CalculatorComplex), StruqtureError> {
379        let spins: TinyVec<[PauliProduct; 2]> = spins.into_iter().collect();
380        let bosons: TinyVec<[BosonProduct; 2]> = bosons.into_iter().collect();
381        let fermions: TinyVec<[FermionProduct; 2]> = fermions.into_iter().collect();
382
383        let mut hermitian_conjugate = false;
384        let mut hermitian_decision_made = false;
385        for b in bosons.iter() {
386            let mut number_equal_indices = 0;
387            for (creator, annihilator) in b.creators().zip(b.annihilators()) {
388                match annihilator.cmp(creator) {
389                    std::cmp::Ordering::Less => {
390                        hermitian_conjugate = true;
391                        hermitian_decision_made = true;
392                        break;
393                    }
394                    std::cmp::Ordering::Greater => {
395                        hermitian_decision_made = true;
396                        break;
397                    }
398                    _ => {
399                        number_equal_indices += 1;
400                    }
401                }
402            }
403            if b.creators().len() > number_equal_indices
404                && b.annihilators().len() == number_equal_indices
405            {
406                hermitian_conjugate = true;
407                hermitian_decision_made = true;
408            }
409            if hermitian_decision_made {
410                break;
411            }
412        }
413        if !hermitian_decision_made {
414            for f in fermions.iter() {
415                let mut number_equal_indices = 0;
416                for (creator, annihilator) in f.creators().zip(f.annihilators()) {
417                    match annihilator.cmp(creator) {
418                        std::cmp::Ordering::Less => {
419                            hermitian_conjugate = true;
420                            break;
421                        }
422                        std::cmp::Ordering::Greater => {
423                            hermitian_decision_made = true;
424                            break;
425                        }
426                        _ => {
427                            number_equal_indices += 1;
428                        }
429                    }
430                }
431                if f.creators().len() > number_equal_indices
432                    && f.annihilators().len() == number_equal_indices
433                {
434                    hermitian_conjugate = true;
435                    hermitian_decision_made = true;
436                }
437                if hermitian_decision_made {
438                    break;
439                }
440            }
441        }
442        let (new_index, new_val) = if hermitian_conjugate {
443            let mut new_spins: TinyVec<[PauliProduct; 2]> = TinyVec::<[PauliProduct; 2]>::new();
444            let mut prefactor = 1.0;
445            for s in spins {
446                let (new_s, pf) = s.hermitian_conjugate();
447                new_spins.push(new_s);
448                prefactor *= pf;
449            }
450            let mut new_bosons: TinyVec<[BosonProduct; 2]> = TinyVec::<[BosonProduct; 2]>::new();
451            for b in bosons {
452                let (new_b, pf) = b.hermitian_conjugate();
453                new_bosons.push(new_b);
454                prefactor *= pf;
455            }
456            let mut new_fermions: TinyVec<[FermionProduct; 2]> =
457                TinyVec::<[FermionProduct; 2]>::new();
458            for f in fermions {
459                let (new_f, pf) = f.hermitian_conjugate();
460                new_fermions.push(new_f);
461                prefactor *= pf;
462            }
463            (
464                Self {
465                    spins: new_spins,
466                    bosons: new_bosons,
467                    fermions: new_fermions,
468                },
469                value.conj() * prefactor,
470            )
471        } else {
472            (
473                Self {
474                    spins,
475                    bosons,
476                    fermions,
477                },
478                value,
479            )
480        };
481
482        if new_index.is_natural_hermitian() && new_val.im != CalculatorFloat::ZERO {
483            Err(StruqtureError::NonHermitianOperator)
484        } else {
485            Ok((new_index, new_val))
486        }
487    }
488}
489
490impl FromStr for HermitianMixedProduct {
491    type Err = StruqtureError;
492
493    /// Constructs a HermitianMixedProduct from a string.
494    ///
495    /// # Arguments
496    ///
497    /// * `s` - The string to convert.
498    ///
499    /// # Returns
500    ///
501    /// * `Ok(Self)` - The successfully converted HermitianMixedProduct.
502    /// * `Err(StruqtureError::ParsingError)` - Encountered subsystem that is neither spin, nor boson, nor fermion.
503    fn from_str(s: &str) -> Result<Self, Self::Err> {
504        let mut spins: TinyVec<[PauliProduct; 2]> = TinyVec::<[PauliProduct; 2]>::with_capacity(2);
505        let mut bosons: TinyVec<[BosonProduct; 2]> = TinyVec::<[BosonProduct; 2]>::with_capacity(2);
506        let mut fermions: TinyVec<[FermionProduct; 2]> =
507            TinyVec::<[FermionProduct; 2]>::with_capacity(2);
508        let subsystems = s.split(':').filter(|s| !s.is_empty());
509        for subsystem in subsystems {
510            if let Some(rest) = subsystem.strip_prefix('S') {
511                spins.push(PauliProduct::from_str(rest)?);
512            } else if let Some(rest) = subsystem.strip_prefix('B') {
513                bosons.push(BosonProduct::from_str(rest)?);
514            } else if let Some(rest) = subsystem.strip_prefix('F') {
515                fermions.push(FermionProduct::from_str(rest)?);
516            } else {
517                return Err(StruqtureError::ParsingError {
518                    target_type: "MixedIndex".to_string(),
519                    msg: format!(
520                        "Encountered subsystem that is neither spin nor boson: {subsystem}"
521                    ),
522                });
523            }
524        }
525
526        HermitianMixedProduct::new(spins, bosons, fermions)
527    }
528}
529
530impl GetValueMixed<'_, HermitianMixedProduct> for HermitianMixedProduct {
531    /// Gets the key corresponding to the input index (here, itself).
532    ///
533    /// # Arguments
534    ///
535    /// * `index` - The index for which to get the corresponding Product.
536    ///
537    /// # Returns
538    ///
539    /// * `Self` - The corresponding HermitianMixedProduct.
540    fn get_key(index: &HermitianMixedProduct) -> Self {
541        index.clone()
542    }
543
544    /// Gets the transformed value corresponding to the input index and value (here, itself).
545    ///
546    /// # Arguments
547    ///
548    /// * `index` - The index to transform the value by.
549    /// * `value` - The value to be transformed.
550    ///
551    /// # Returns
552    ///
553    /// * `CalculatorComplex` - The transformed value.
554    fn get_transform(
555        _index: &HermitianMixedProduct,
556        value: qoqo_calculator::CalculatorComplex,
557    ) -> qoqo_calculator::CalculatorComplex {
558        value
559    }
560}
561
562impl CorrespondsTo<HermitianMixedProduct> for HermitianMixedProduct {
563    /// Gets the HermitianMixedProduct corresponding to self (here, itself).
564    ///
565    /// # Returns
566    ///
567    /// * `HermitianMixedProduct` - The HermitianMixedProduct corresponding to Self.
568    fn corresponds_to(&self) -> HermitianMixedProduct {
569        self.clone()
570    }
571}
572
573impl SymmetricIndex for HermitianMixedProduct {
574    // From trait
575    fn hermitian_conjugate(&self) -> (Self, f64) {
576        (self.clone(), 1.0)
577    }
578
579    // From trait
580    fn is_natural_hermitian(&self) -> bool {
581        self.bosons.iter().all(|b| b.is_natural_hermitian())
582            && self.fermions.iter().all(|f| f.is_natural_hermitian())
583    }
584}
585
586/// Implements the multiplication function of HermitianMixedProduct by HermitianMixedProduct.
587///
588impl Mul<HermitianMixedProduct> for HermitianMixedProduct {
589    type Output = Result<Vec<(MixedProduct, Complex64)>, StruqtureError>;
590
591    /// Implement `*` for HermitianMixedProduct and HermitianMixedProduct.
592    ///
593    /// # Arguments
594    ///
595    /// * `other` - The HermitianMixedProduct to multiply by.
596    ///
597    /// # Returns
598    ///
599    /// * `Ok(Vec<(MixedProduct, Complex64)>)` - The two HermitianMixedProducts multiplied.
600    /// * `Err(StruqtureError::MismatchedNumberSubsystems)` - Number of subsystems in left and right do not match.
601    ///
602    /// # Panics
603    ///
604    /// * Could not convert self into a MixedProduct.
605    /// * Could not convert rhs into a MixedProduct.
606    fn mul(self, rhs: HermitianMixedProduct) -> Self::Output {
607        if self.spins().len() != rhs.spins().len()
608            || self.bosons().len() != rhs.bosons().len()
609            || self.fermions().len() != rhs.fermions().len()
610        {
611            return Err(StruqtureError::MismatchedNumberSubsystems {
612                target_number_spin_subsystems: self.spins().len(),
613                target_number_boson_subsystems: self.bosons().len(),
614                target_number_fermion_subsystems: self.fermions().len(),
615                actual_number_spin_subsystems: rhs.spins().len(),
616                actual_number_boson_subsystems: rhs.bosons().len(),
617                actual_number_fermion_subsystems: rhs.fermions().len(),
618            });
619        }
620        let mut result_vec: Vec<(MixedProduct, Complex64)> = Vec::new();
621
622        let mut left_to_mul: Vec<(MixedProduct, f64)> = Vec::new();
623        let mhp_left = MixedProduct::new(self.spins, self.bosons, self.fermions)
624            .expect("Could not convert self into a MixedProduct");
625        left_to_mul.push((mhp_left.clone(), 1.0));
626        if !mhp_left.is_natural_hermitian() {
627            left_to_mul.push(mhp_left.hermitian_conjugate());
628        }
629
630        let mut right_to_mul: Vec<(MixedProduct, f64)> = Vec::new();
631        let mhp_right = MixedProduct::new(rhs.spins, rhs.bosons, rhs.fermions)
632            .expect("Could not convert rhs into a MixedProduct");
633        right_to_mul.push((mhp_right.clone(), 1.0));
634        if !mhp_right.is_natural_hermitian() {
635            right_to_mul.push(mhp_right.hermitian_conjugate());
636        }
637
638        for (lhs, lsign) in left_to_mul {
639            for (rhs, rsign) in right_to_mul.clone() {
640                let mut coefficient = Complex64::new(lsign * rsign, 0.0);
641                let mut tmp_spins: Vec<PauliProduct> = Vec::with_capacity(lhs.spins().len());
642                let mut tmp_bosons: Vec<Vec<BosonProduct>> = Vec::with_capacity(lhs.bosons().len());
643                let mut tmp_fermions: Vec<Vec<(FermionProduct, f64)>> =
644                    Vec::with_capacity(lhs.fermions().len());
645                for (left, right) in lhs.clone().spins.into_iter().zip(rhs.spins.into_iter()) {
646                    let (val, coeff) = left * right;
647                    tmp_spins.push(val);
648                    coefficient *= coeff;
649                }
650                // iterate through boson subsystems and multiply subsystem
651                for (left, right) in lhs.clone().bosons.into_iter().zip(rhs.bosons.into_iter()) {
652                    let boson_multiplication = left.clone() * right.clone();
653                    if !tmp_bosons.is_empty() {
654                        let mut internal_tmp_bosons: Vec<Vec<BosonProduct>> = Vec::new();
655                        for bp in boson_multiplication.clone() {
656                            for tmp_bp in tmp_bosons.iter() {
657                                let mut tmp_entry = tmp_bp.clone();
658                                tmp_entry.push(bp.clone());
659                                internal_tmp_bosons.push(tmp_entry);
660                            }
661                        }
662                        tmp_bosons.clone_from(&internal_tmp_bosons);
663                    } else {
664                        for bp in boson_multiplication.clone() {
665                            tmp_bosons.push(vec![bp]);
666                        }
667                    }
668                }
669                for (left, right) in lhs
670                    .fermions
671                    .clone()
672                    .into_iter()
673                    .zip(rhs.fermions.into_iter())
674                {
675                    let fermion_multiplication = left * right;
676                    if !tmp_fermions.is_empty() {
677                        let mut internal_tmp_fermions: Vec<Vec<(FermionProduct, f64)>> = Vec::new();
678                        for fp in fermion_multiplication {
679                            for tmp_fp in tmp_fermions.iter() {
680                                let mut tmp_entry = tmp_fp.clone();
681                                tmp_entry.push(fp.clone());
682                                internal_tmp_fermions.push(tmp_entry);
683                            }
684                        }
685                        tmp_fermions = internal_tmp_fermions;
686                    } else {
687                        for fp in fermion_multiplication.clone() {
688                            tmp_fermions.push(vec![fp]);
689                        }
690                    }
691                }
692
693                // Combining results
694                for boson in tmp_bosons.clone() {
695                    if !tmp_fermions.is_empty() {
696                        for fermion in tmp_fermions.iter() {
697                            let mut fermion_vec: Vec<FermionProduct> = Vec::new();
698                            let mut sign = Complex64::new(1.0, 0.0);
699                            for (f, val) in fermion {
700                                fermion_vec.push(f.clone());
701                                sign *= val;
702                            }
703                            result_vec.push((
704                                MixedProduct::new(tmp_spins.clone(), boson.clone(), fermion_vec)?,
705                                coefficient * sign,
706                            ));
707                        }
708                    } else {
709                        result_vec.push((
710                            MixedProduct::new(tmp_spins.clone(), boson.clone(), vec![])?,
711                            coefficient,
712                        ));
713                    }
714                }
715                if tmp_bosons.is_empty() && !tmp_fermions.is_empty() {
716                    for fermion in tmp_fermions.iter() {
717                        let mut fermion_vec: Vec<FermionProduct> = Vec::new();
718                        let mut sign = Complex64::new(1.0, 0.0);
719                        for (f, val) in fermion {
720                            fermion_vec.push(f.clone());
721                            sign *= val;
722                        }
723                        result_vec.push((
724                            MixedProduct::new(tmp_spins.clone(), [], fermion_vec)?,
725                            coefficient * sign,
726                        ));
727                    }
728                } else if tmp_bosons.is_empty() && tmp_fermions.is_empty() {
729                    result_vec.push((MixedProduct::new(tmp_spins.clone(), [], [])?, coefficient))
730                }
731            }
732        }
733
734        Ok(result_vec)
735    }
736}
737
738impl Mul<MixedProduct> for HermitianMixedProduct {
739    type Output = Result<Vec<(MixedProduct, Complex64)>, StruqtureError>;
740
741    /// Implement `*` for a HermitianMixedProduct and a MixedProduct.
742    ///
743    /// # Arguments
744    ///
745    /// * `other` - The MixedProduct to multiply by.
746    ///
747    /// # Returns
748    ///
749    /// * `Ok(Vec<(MixedProduct, Complex64)>)` - The two (Hermitian)MixedProduct multiplied.
750    /// * `Err(StruqtureError::MismatchedNumberSubsystems)` - Number of subsystems in left and right do not match.
751    ///
752    /// # Panics
753    ///
754    /// * Could not convert self into a MixedProduct.
755    fn mul(self, rhs: MixedProduct) -> Self::Output {
756        if self.spins().len() != rhs.spins().len()
757            || self.bosons().len() != rhs.bosons().len()
758            || self.fermions().len() != rhs.fermions().len()
759        {
760            return Err(StruqtureError::MismatchedNumberSubsystems {
761                target_number_spin_subsystems: self.spins().len(),
762                target_number_boson_subsystems: self.bosons().len(),
763                target_number_fermion_subsystems: self.fermions().len(),
764                actual_number_spin_subsystems: rhs.spins().len(),
765                actual_number_boson_subsystems: rhs.bosons().len(),
766                actual_number_fermion_subsystems: rhs.fermions().len(),
767            });
768        }
769        let mut result_vec: Vec<(MixedProduct, Complex64)> = Vec::new();
770
771        let mut left_to_mul: Vec<(MixedProduct, f64)> = Vec::new();
772        let mhp_left = MixedProduct::new(self.spins, self.bosons, self.fermions)
773            .expect("Could not convert self into a MixedProduct");
774        left_to_mul.push((mhp_left.clone(), 1.0));
775        if !mhp_left.is_natural_hermitian() {
776            left_to_mul.push(mhp_left.hermitian_conjugate());
777        }
778
779        for (lhs, lsign) in left_to_mul {
780            let mut coefficient = Complex64::new(lsign, 0.0);
781            let mut tmp_spins: Vec<PauliProduct> = Vec::with_capacity(lhs.spins().len());
782            let mut tmp_bosons: Vec<Vec<BosonProduct>> = Vec::with_capacity(lhs.bosons().len());
783            let mut tmp_fermions: Vec<Vec<(FermionProduct, f64)>> =
784                Vec::with_capacity(lhs.fermions().len());
785            for (left, right) in lhs
786                .clone()
787                .spins
788                .into_iter()
789                .zip(rhs.clone().spins.into_iter())
790            {
791                let (val, coeff) = left * right;
792                tmp_spins.push(val);
793                coefficient *= coeff;
794            }
795            // iterate through boson subsystems and multiply subsystem
796            for (left, right) in lhs
797                .clone()
798                .bosons
799                .into_iter()
800                .zip(rhs.clone().bosons.into_iter())
801            {
802                let boson_multiplication = left.clone() * right.clone();
803                if !tmp_bosons.is_empty() {
804                    let mut internal_tmp_bosons: Vec<Vec<BosonProduct>> = Vec::new();
805                    for bp in boson_multiplication.clone() {
806                        for tmp_bp in tmp_bosons.iter() {
807                            let mut tmp_entry = tmp_bp.clone();
808                            tmp_entry.push(bp.clone());
809                            internal_tmp_bosons.push(tmp_entry);
810                        }
811                    }
812                    tmp_bosons.clone_from(&internal_tmp_bosons);
813                } else {
814                    for bp in boson_multiplication.clone() {
815                        tmp_bosons.push(vec![bp]);
816                    }
817                }
818            }
819            for (left, right) in lhs
820                .fermions
821                .clone()
822                .into_iter()
823                .zip(rhs.clone().fermions.into_iter())
824            {
825                let fermion_multiplication = left * right;
826                if !tmp_fermions.is_empty() {
827                    let mut internal_tmp_fermions: Vec<Vec<(FermionProduct, f64)>> = Vec::new();
828                    for fp in fermion_multiplication {
829                        for tmp_fp in tmp_fermions.iter() {
830                            let mut tmp_entry = tmp_fp.clone();
831                            tmp_entry.push(fp.clone());
832                            internal_tmp_fermions.push(tmp_entry);
833                        }
834                    }
835                    tmp_fermions = internal_tmp_fermions;
836                } else {
837                    for fp in fermion_multiplication.clone() {
838                        tmp_fermions.push(vec![fp]);
839                    }
840                }
841            }
842
843            // Combining results
844            for boson in tmp_bosons.clone() {
845                if !tmp_fermions.is_empty() {
846                    for fermion in tmp_fermions.iter() {
847                        let mut fermion_vec: Vec<FermionProduct> = Vec::new();
848                        let mut sign = Complex64::new(1.0, 0.0);
849                        for (f, val) in fermion {
850                            fermion_vec.push(f.clone());
851                            sign *= val;
852                        }
853                        result_vec.push((
854                            MixedProduct::new(tmp_spins.clone(), boson.clone(), fermion_vec)?,
855                            coefficient * sign,
856                        ));
857                    }
858                } else {
859                    result_vec.push((
860                        MixedProduct::new(tmp_spins.clone(), boson.clone(), vec![])?,
861                        coefficient,
862                    ));
863                }
864            }
865            if tmp_bosons.is_empty() && !tmp_fermions.is_empty() {
866                for fermion in tmp_fermions.iter() {
867                    let mut fermion_vec: Vec<FermionProduct> = Vec::new();
868                    let mut sign = Complex64::new(1.0, 0.0);
869                    for (f, val) in fermion {
870                        fermion_vec.push(f.clone());
871                        sign *= val;
872                    }
873                    result_vec.push((
874                        MixedProduct::new(tmp_spins.clone(), [], fermion_vec)?,
875                        coefficient * sign,
876                    ));
877                }
878            } else if tmp_bosons.is_empty() && tmp_fermions.is_empty() {
879                result_vec.push((MixedProduct::new(tmp_spins.clone(), [], [])?, coefficient))
880            }
881        }
882
883        Ok(result_vec)
884    }
885}
886
887/// Implements the format function (Display trait) of HermitianMixedProduct.
888///
889impl std::fmt::Display for HermitianMixedProduct {
890    /// Formats the HermitianMixedProduct using the given formatter.
891    ///
892    /// # Arguments
893    ///
894    /// * `f` - The formatter to use.
895    ///
896    /// # Returns
897    ///
898    /// * `std::fmt::Result` - The formatted HermitianMixedProduct.
899    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
900        let mut string: String = String::new();
901        for spin in self.spins() {
902            string.push_str(format!("S{spin}:").as_str());
903        }
904        for boson in self.bosons() {
905            string.push_str(format!("B{boson}:").as_str());
906        }
907        for fermion in self.fermions() {
908            string.push_str(format!("F{fermion}:").as_str());
909        }
910        write!(f, "{string}")
911    }
912}
913
914#[cfg(test)]
915mod test {
916    use super::*;
917    use itertools::Itertools;
918    use test_case::test_case;
919
920    #[test_case("", &[], &[]; "empty")]
921    #[test_case(":S0X1X:", &[], &[PauliProduct::from_str("0X1X").unwrap()]; "single spin systems")]
922    #[test_case(":S0X1X:S0Z:", &[], &[PauliProduct::from_str("0X1X").unwrap(), PauliProduct::from_str("0Z").unwrap()]; "two spin systems")]
923    #[test_case(":S0X1X:Bc0a1:", &[BosonProduct::from_str("c0a1").unwrap()], &[PauliProduct::from_str("0X1X").unwrap()]; "spin-boson systems")]
924    fn from_string(stringformat: &str, bosons: &[BosonProduct], spins: &[PauliProduct]) {
925        let test_new = <HermitianMixedProduct as std::str::FromStr>::from_str(stringformat);
926        assert!(test_new.is_ok());
927        let res = test_new.unwrap();
928        let empty_bosons: Vec<BosonProduct> = bosons.to_vec();
929        let res_bosons: Vec<BosonProduct> = res.bosons.iter().cloned().collect_vec();
930        assert_eq!(res_bosons, empty_bosons);
931        let empty_spins: Vec<PauliProduct> = spins.to_vec();
932        let res_spins: Vec<PauliProduct> = res.spins.iter().cloned().collect_vec();
933        assert_eq!(res_spins, empty_spins);
934    }
935}