struqture/mixed_systems/
mixed_hamiltonian.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::{
14    HermitianMixedProduct, HermitianOperateOnMixedSystems, MixedIndex, MixedOperator,
15    OperateOnMixedSystems,
16};
17use crate::{
18    ModeIndex, OperateOnDensityMatrix, OperateOnState, SpinIndex, StruqtureError, SymmetricIndex,
19};
20use qoqo_calculator::{CalculatorComplex, CalculatorFloat};
21use serde::{Deserialize, Serialize};
22use std::fmt::{self, Write};
23use std::iter::{FromIterator, IntoIterator};
24use std::ops;
25
26use indexmap::map::{Entry, Iter};
27use indexmap::IndexMap;
28
29/// MixedHamiltonians are combinations of HermitianMixedProducts with specific CalculatorComplex coefficients.
30///
31/// This is a representation of sums of Pauli products with weightings in order to build a full Hamiltonian.
32///
33/// # Example
34///
35/// ```
36/// use struqture::prelude::*;
37/// use qoqo_calculator::CalculatorComplex;
38/// use struqture::mixed_systems::{HermitianMixedProduct, MixedHamiltonian};
39/// use struqture::spins::PauliProduct;
40/// use struqture::bosons::BosonProduct;
41/// use struqture::fermions::FermionProduct;
42///
43/// let mut sh = MixedHamiltonian::new(1, 1, 1);
44///
45/// let pp_1: HermitianMixedProduct = HermitianMixedProduct::new([PauliProduct::new().x(0),], [BosonProduct::new([], [1]).unwrap()], [FermionProduct::new([0], [1]).unwrap()]).unwrap();
46/// let pp_0: HermitianMixedProduct = HermitianMixedProduct::new([PauliProduct::new().z(0),], [BosonProduct::new([0], [1]).unwrap()], [FermionProduct::new([0], [0]).unwrap()]).unwrap();
47/// sh.set(pp_1.clone(), CalculatorComplex::from(0.5)).unwrap();
48/// sh.set(pp_0.clone(), CalculatorComplex::from(0.2)).unwrap();
49///
50/// // Access what you set:
51/// assert_eq!(sh.get(&pp_1), &CalculatorComplex::from(0.5));
52/// assert_eq!(sh.get(&pp_0), &CalculatorComplex::from(0.2));
53/// ```
54///
55#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
56#[serde(try_from = "MixedHamiltonianSerialize")]
57#[serde(into = "MixedHamiltonianSerialize")]
58pub struct MixedHamiltonian {
59    /// The internal HashMap of HermitianMixedProducts and coefficients (CalculatorFloat)
60    internal_map: IndexMap<HermitianMixedProduct, CalculatorComplex>,
61    /// Number of Spin subsystems
62    pub(crate) n_spins: usize,
63    /// Number of Boson subsystems
64    pub(crate) n_bosons: usize,
65    /// Number of Fermion subsystems
66    pub(crate) n_fermions: usize,
67}
68
69impl crate::SerializationSupport for MixedHamiltonian {
70    fn struqture_type() -> crate::StruqtureType {
71        crate::StruqtureType::MixedHamiltonian
72    }
73}
74
75#[cfg(feature = "json_schema")]
76impl schemars::JsonSchema for MixedHamiltonian {
77    fn schema_name() -> std::borrow::Cow<'static, str> {
78        "MixedHamiltonian".into()
79    }
80
81    fn json_schema(generator: &mut schemars::SchemaGenerator) -> schemars::Schema {
82        <MixedHamiltonianSerialize>::json_schema(generator)
83    }
84}
85
86#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
87#[cfg_attr(feature = "json_schema", derive(schemars::JsonSchema))]
88#[cfg_attr(feature = "json_schema", schemars(deny_unknown_fields))]
89struct MixedHamiltonianSerialize {
90    items: Vec<(HermitianMixedProduct, CalculatorFloat, CalculatorFloat)>,
91    n_spins: usize,
92    n_bosons: usize,
93    n_fermions: usize,
94    serialisation_meta: crate::StruqtureSerialisationMeta,
95}
96
97impl TryFrom<MixedHamiltonianSerialize> for MixedHamiltonian {
98    type Error = StruqtureError;
99    fn try_from(value: MixedHamiltonianSerialize) -> Result<Self, Self::Error> {
100        let target_serialisation_meta =
101            <Self as crate::SerializationSupport>::target_serialisation_meta();
102        crate::check_can_be_deserialised(&target_serialisation_meta, &value.serialisation_meta)?;
103        let mut new_noise_op =
104            MixedHamiltonian::new(value.n_spins, value.n_bosons, value.n_fermions);
105        for (key, real, imag) in value.items.iter() {
106            let _ =
107                new_noise_op.add_operator_product(key.clone(), CalculatorComplex::new(real, imag));
108        }
109        Ok(new_noise_op)
110    }
111}
112
113impl From<MixedHamiltonian> for MixedHamiltonianSerialize {
114    fn from(value: MixedHamiltonian) -> Self {
115        let serialisation_meta = crate::SerializationSupport::struqture_serialisation_meta(&value);
116        let new_noise_op: Vec<(HermitianMixedProduct, CalculatorFloat, CalculatorFloat)> = value
117            .clone()
118            .into_iter()
119            .map(|(key, val)| (key, val.re, val.im))
120            .collect();
121        Self {
122            items: new_noise_op,
123            n_spins: value.n_spins,
124            n_bosons: value.n_bosons,
125            n_fermions: value.n_fermions,
126            serialisation_meta,
127        }
128    }
129}
130
131impl<'a> OperateOnDensityMatrix<'a> for MixedHamiltonian {
132    type Index = HermitianMixedProduct;
133    type Value = CalculatorComplex;
134
135    // From trait
136    fn get(&self, key: &Self::Index) -> &Self::Value {
137        match self.internal_map.get(key) {
138            Some(value) => value,
139            None => &CalculatorComplex::ZERO,
140        }
141    }
142
143    // From trait
144    fn iter(&'a self) -> impl ExactSizeIterator<Item = (&'a Self::Index, &'a Self::Value)> {
145        self.internal_map.iter()
146    }
147
148    // From trait
149    fn keys(&'a self) -> impl ExactSizeIterator<Item = &'a Self::Index> {
150        self.internal_map.keys()
151    }
152
153    // From trait
154    fn values(&'a self) -> impl ExactSizeIterator<Item = &'a Self::Value> {
155        self.internal_map.values()
156    }
157
158    // From trait
159    fn remove(&mut self, key: &Self::Index) -> Option<Self::Value> {
160        self.internal_map.shift_remove(key)
161    }
162
163    // From trait
164    fn empty_clone(&self, capacity: Option<usize>) -> Self {
165        match capacity {
166            Some(cap) => Self::with_capacity(self.n_spins, self.n_bosons, self.n_fermions, cap),
167            None => Self::new(self.n_spins, self.n_bosons, self.n_fermions),
168        }
169    }
170
171    /// Overwrites an existing entry or sets a new entry in the MixedHamiltonian with the given (HermitianMixedProduct key, CalculatorComplex value) pair.
172    ///
173    /// # Arguments
174    ///
175    /// * `key` - The HermitianMixedProduct key to set in the MixedHamiltonian.
176    /// * `value` - The corresponding CalculatorComplex value to set for the key in the MixedHamiltonian.
177    ///
178    /// # Returns
179    ///
180    /// * `Ok(Some(CalculatorComplex))` - The key existed, this is the value it had before it was set with the value input.
181    /// * `Ok(None)` - The key did not exist, it has been set with its corresponding value.
182    /// * `Err(StruqtureError::MismatchedNumberSubsystems)` - Number of subsystems in system and key do not match.
183    /// * `Err(StruqtureError::NonHermitianOperator)` - Key is naturally hermitian (on-diagonal term), but its corresponding value is not real.
184    fn set(
185        &mut self,
186        key: Self::Index,
187        value: Self::Value,
188    ) -> Result<Option<Self::Value>, StruqtureError> {
189        if key.spins().len() != self.n_spins
190            || key.bosons().len() != self.n_bosons
191            || key.fermions().len() != self.n_fermions
192        {
193            return Err(StruqtureError::MismatchedNumberSubsystems {
194                target_number_spin_subsystems: self.n_spins,
195                target_number_boson_subsystems: self.n_bosons,
196                target_number_fermion_subsystems: self.n_fermions,
197                actual_number_spin_subsystems: key.spins().len(),
198                actual_number_boson_subsystems: key.bosons().len(),
199                actual_number_fermion_subsystems: key.fermions().len(),
200            });
201        }
202        if value.re != CalculatorFloat::ZERO || value.im != CalculatorFloat::ZERO {
203            // Catch on diagonals with non-zero imaginary values
204            if key.is_natural_hermitian() && value.im != CalculatorFloat::ZERO {
205                Err(StruqtureError::NonHermitianOperator)
206            } else {
207                Ok(self.internal_map.insert(key, value))
208            }
209        } else {
210            match self.internal_map.entry(key) {
211                Entry::Occupied(val) => Ok(Some(val.shift_remove())),
212                Entry::Vacant(_) => Ok(None),
213            }
214        }
215    }
216}
217
218impl OperateOnState<'_> for MixedHamiltonian {
219    // From trait
220    fn hermitian_conjugate(&self) -> Self {
221        self.clone()
222    }
223}
224
225impl HermitianOperateOnMixedSystems<'_> for MixedHamiltonian {}
226
227impl OperateOnMixedSystems<'_> for MixedHamiltonian {
228    // From trait
229    fn current_number_spins(&self) -> Vec<usize> {
230        let mut current_number_spins: Vec<usize> = (0..self.n_spins).map(|_| 0).collect();
231        for key in self.keys() {
232            for (index, s) in key.spins().enumerate() {
233                let maxk = s.current_number_spins();
234                if maxk > current_number_spins[index] {
235                    current_number_spins[index] = maxk
236                }
237            }
238        }
239        current_number_spins
240    }
241
242    // From trait
243    fn current_number_bosonic_modes(&self) -> Vec<usize> {
244        let mut number_bosons: Vec<usize> = (0..self.n_bosons).map(|_| 0).collect();
245        for key in self.keys() {
246            for (index, s) in key.bosons().enumerate() {
247                let maxk = s.current_number_modes();
248                if maxk > number_bosons[index] {
249                    number_bosons[index] = maxk
250                }
251            }
252        }
253        number_bosons
254    }
255
256    // From trait
257    fn current_number_fermionic_modes(&self) -> Vec<usize> {
258        let mut number_fermions: Vec<usize> = (0..self.n_fermions).map(|_| 0).collect();
259        for key in self.keys() {
260            for (index, s) in key.fermions().enumerate() {
261                let maxk = s.current_number_modes();
262                if maxk > number_fermions[index] {
263                    number_fermions[index] = maxk
264                }
265            }
266        }
267        number_fermions
268    }
269}
270
271/// Implements the default function (Default trait) of MixedHamiltonian (an empty MixedHamiltonian).
272///
273impl Default for MixedHamiltonian {
274    fn default() -> Self {
275        Self::new(0, 0, 0)
276    }
277}
278
279/// Functions for the MixedHamiltonian
280///
281impl MixedHamiltonian {
282    /// Creates a new MixedHamiltonian.
283    ///
284    /// # Arguments:
285    ///
286    /// * `n_spins` - Number of spin sub-systems
287    /// * `n_bosons` - Number of bosonic sub-systems
288    /// * `n_fermions` - Number of fermionic sub-systems
289    ///
290    /// # Returns
291    ///
292    /// * `Self` - The new (empty) MixedHamiltonian.
293    pub fn new(n_spins: usize, n_bosons: usize, n_fermions: usize) -> Self {
294        MixedHamiltonian {
295            internal_map: IndexMap::new(),
296            n_spins,
297            n_bosons,
298            n_fermions,
299        }
300    }
301
302    /// Creates a new MixedHamiltonian with capacity.
303    ///
304    /// # Arguments
305    ///
306    /// * `n_spins` - The number of spin sub-systems.
307    /// * `n_bosons` - The number of boson sub-systems.
308    /// * `n_fermions` - The number of fermion sub-systems.
309    /// * `capacity` - The pre-allocated capacity of the hamiltonian.
310    ///
311    /// # Returns
312    ///
313    /// * `Self` - The new (empty) MixedHamiltonian.
314    pub fn with_capacity(
315        n_spins: usize,
316        n_bosons: usize,
317        n_fermions: usize,
318        capacity: usize,
319    ) -> Self {
320        Self {
321            internal_map: IndexMap::with_capacity(capacity),
322            n_spins,
323            n_bosons,
324            n_fermions,
325        }
326    }
327
328    /// Export to struqture_1 format.
329    #[cfg(feature = "struqture_1_export")]
330    pub fn to_struqture_1(
331        &self,
332    ) -> Result<struqture_1::mixed_systems::MixedHamiltonianSystem, StruqtureError> {
333        let mut new_mixed_system = struqture_1::mixed_systems::MixedHamiltonianSystem::new(
334            vec![None; self.n_spins],
335            vec![None; self.n_bosons],
336            vec![None; self.n_fermions],
337        );
338        for (key, val) in self.iter() {
339            let one_key = key.to_struqture_1()?;
340            let _ = struqture_1::OperateOnDensityMatrix::set(
341                &mut new_mixed_system,
342                one_key,
343                val.clone(),
344            );
345        }
346        Ok(new_mixed_system)
347    }
348
349    /// Export to struqture_1 format.
350    #[cfg(feature = "struqture_1_import")]
351    pub fn from_struqture_1(
352        value: &struqture_1::mixed_systems::MixedHamiltonianSystem,
353    ) -> Result<Self, StruqtureError> {
354        let mut new_operator = Self::new(
355            struqture_1::mixed_systems::OperateOnMixedSystems::current_number_spins(value).len(),
356            struqture_1::mixed_systems::OperateOnMixedSystems::current_number_bosonic_modes(value)
357                .len(),
358            struqture_1::mixed_systems::OperateOnMixedSystems::current_number_fermionic_modes(
359                value,
360            )
361            .len(),
362        );
363        for (key, val) in struqture_1::OperateOnDensityMatrix::iter(value) {
364            let self_key = HermitianMixedProduct::from_struqture_1(key)?;
365            let _ = new_operator.set(self_key, val.clone());
366        }
367        Ok(new_operator)
368    }
369}
370
371/// Implements the negative sign function of MixedHamiltonian.
372///
373impl ops::Neg for MixedHamiltonian {
374    type Output = MixedHamiltonian;
375    /// Implement minus sign for MixedHamiltonian.
376    ///
377    /// # Returns
378    ///
379    /// * `Self` - The MixedHamiltonian * -1.
380    fn neg(self) -> Self {
381        let mut internal = self.internal_map.clone();
382        let n_spins = self.n_spins;
383        let n_bosons = self.n_bosons;
384        let n_fermions = self.n_fermions;
385        for key in self.keys() {
386            internal.insert(key.clone(), internal[key].clone() * -1.0);
387        }
388        MixedHamiltonian {
389            internal_map: internal,
390            n_spins,
391            n_bosons,
392            n_fermions,
393        }
394    }
395}
396
397/// Implements the plus function of MixedHamiltonian by MixedHamiltonian.
398///
399impl<T, V> ops::Add<T> for MixedHamiltonian
400where
401    T: IntoIterator<Item = (HermitianMixedProduct, V)>,
402    V: Into<CalculatorComplex>,
403{
404    type Output = Result<Self, StruqtureError>;
405    /// Implements `+` (add) for two MixedHamiltonians.
406    ///
407    /// # Arguments
408    ///
409    /// * `other` - The MixedHamiltonian to be added.
410    ///
411    /// # Returns
412    ///
413    /// * `Ok(Self)` - The two MixedHamiltonians added together.
414    /// * `Err(StruqtureError::MismatchedNumberSubsystems)` - Number of subsystems in system and key do not match.
415    /// * `Err(StruqtureError::NonHermitianOperator)` - Key is naturally hermitian (on-diagonal term), but its corresponding value is not real.
416    fn add(mut self, other: T) -> Self::Output {
417        for (key, value) in other.into_iter() {
418            self.add_operator_product(key.clone(), Into::<CalculatorComplex>::into(value))?;
419        }
420        Ok(self)
421    }
422}
423
424/// Implements the minus function of MixedHamiltonian by MixedHamiltonian.
425///
426impl<T, V> ops::Sub<T> for MixedHamiltonian
427where
428    T: IntoIterator<Item = (HermitianMixedProduct, V)>,
429    V: Into<CalculatorComplex>,
430{
431    type Output = Result<Self, StruqtureError>;
432    /// Implements `-` (subtract) for two MixedHamiltonians.
433    ///
434    /// # Arguments
435    ///
436    /// * `other` - The MixedHamiltonian to be subtracted.
437    ///
438    /// # Returns
439    ///
440    /// * `Ok(Self)` - The two MixedHamiltonians subtracted.
441    /// * `Err(StruqtureError::MismatchedNumberSubsystems)` - Number of subsystems in system and key do not match.
442    /// * `Err(StruqtureError::NonHermitianOperator)` - Key is naturally hermitian (on-diagonal term), but its corresponding value is not real.
443    fn sub(mut self, other: T) -> Self::Output {
444        for (key, value) in other.into_iter() {
445            self.add_operator_product(key.clone(), Into::<CalculatorComplex>::into(value) * -1.0)?;
446        }
447        Ok(self)
448    }
449}
450
451/// Implements the multiplication function of MixedHamiltonian by CalculatorComplex/CalculatorFloat.
452///
453impl<T> ops::Mul<T> for MixedHamiltonian
454where
455    T: Into<CalculatorComplex>,
456{
457    type Output = Self;
458    /// Implement `*` for MixedHamiltonian and CalculatorComplex/CalculatorFloat.
459    ///
460    /// # Arguments
461    ///
462    /// * `other` - The CalculatorComplex or CalculatorFloat by which to multiply.
463    ///
464    /// # Returns
465    ///
466    /// * `Self` - The MixedHamiltonian multiplied by the CalculatorComplex/CalculatorFloat.
467    fn mul(self, other: T) -> Self {
468        let other_cc = Into::<CalculatorComplex>::into(other);
469        let mut internal = self.internal_map.clone();
470        let n_spins = self.n_spins;
471        let n_bosons = self.n_bosons;
472        let n_fermions = self.n_fermions;
473        for key in self.keys() {
474            internal.insert(key.clone(), internal[key].clone() * other_cc.clone());
475        }
476        MixedHamiltonian {
477            internal_map: internal,
478            n_spins,
479            n_bosons,
480            n_fermions,
481        }
482    }
483}
484
485/// Implements the multiplication function of MixedHamiltonian by MixedHamiltonian.
486///
487impl ops::Mul<MixedHamiltonian> for MixedHamiltonian {
488    type Output = Result<MixedOperator, StruqtureError>;
489    /// Implement `*` for MixedHamiltonian and MixedHamiltonian.
490    ///
491    /// # Arguments
492    ///
493    /// * `other` - The MixedHamiltonian to multiply by.
494    ///
495    /// # Returns
496    ///
497    /// * `Ok(Self)` - The two MixedHamiltonians multiplied.
498    /// * `Err(StruqtureError::MismatchedNumberSubsystems)` - Number of subsystems in system and key do not match.
499    /// * `Err(StruqtureError::NonHermitianOperator)` - Key is naturally hermitian (on-diagonal term), but its corresponding value is not real.
500    fn mul(self, other: MixedHamiltonian) -> Self::Output {
501        let mut op = MixedOperator::with_capacity(
502            self.n_spins,
503            self.n_bosons,
504            self.n_fermions,
505            self.len() * other.len(),
506        );
507        for (bps, vals) in self {
508            for (bpo, valo) in other.iter() {
509                let mixed_products = (bps.clone() * bpo.clone())?;
510                let coefficient = Into::<CalculatorComplex>::into(valo) * vals.clone();
511                for (b, coeff) in mixed_products {
512                    op.add_operator_product(b, coefficient.clone() * coeff)?;
513                }
514            }
515        }
516        Ok(op)
517    }
518}
519
520/// Implements the into_iter function (IntoIterator trait) of MixedHamiltonian.
521///
522impl IntoIterator for MixedHamiltonian {
523    type Item = (HermitianMixedProduct, CalculatorComplex);
524    type IntoIter = indexmap::map::IntoIter<HermitianMixedProduct, CalculatorComplex>;
525    /// Returns the MixedHamiltonian in Iterator form.
526    ///
527    /// # Returns
528    ///
529    /// * `Self::IntoIter` - The MixedHamiltonian in Iterator form.
530    fn into_iter(self) -> Self::IntoIter {
531        self.internal_map.into_iter()
532    }
533}
534
535/// Implements the into_iter function (IntoIterator trait) of reference MixedHamiltonian.
536///
537impl<'a> IntoIterator for &'a MixedHamiltonian {
538    type Item = (&'a HermitianMixedProduct, &'a CalculatorComplex);
539    type IntoIter = Iter<'a, HermitianMixedProduct, CalculatorComplex>;
540
541    /// Returns the reference MixedHamiltonian in Iterator form.
542    ///
543    /// # Returns
544    ///
545    /// * `Self::IntoIter` - The reference MixedHamiltonian in Iterator form.
546    fn into_iter(self) -> Self::IntoIter {
547        self.internal_map.iter()
548    }
549}
550
551/// Implements the from_iter function (FromIterator trait) of MixedHamiltonian.
552///
553impl FromIterator<(HermitianMixedProduct, CalculatorComplex)> for MixedHamiltonian {
554    /// Returns the object in MixedHamiltonian form, from an Iterator form of the object.
555    ///
556    /// # Arguments
557    ///
558    /// * `iter` - The iterator containing the information from which to create the MixedHamiltonian.
559    ///
560    /// # Returns
561    ///
562    /// * `Self::IntoIter` - The iterator in MixedHamiltonian form.
563    ///
564    /// # Panics
565    ///
566    /// * Internal bug in set.
567    /// * Internal bug in add_operator_product.
568    fn from_iter<I: IntoIterator<Item = (HermitianMixedProduct, CalculatorComplex)>>(
569        iter: I,
570    ) -> Self {
571        let mut iterator = iter.into_iter();
572        match iterator.next() {
573            Some(first_element) => {
574                let spins = first_element.0.spins().len();
575                let bosons = first_element.0.bosons().len();
576                let fermions = first_element.0.fermions().len();
577                let mut mh = MixedHamiltonian::new(spins, bosons, fermions);
578                mh.set(first_element.0, first_element.1)
579                    .expect("Internal error in set");
580                for (pair, cc) in iterator {
581                    mh.add_operator_product(pair, cc)
582                        .expect("Internal error in add_operator_product");
583                }
584                mh
585            }
586            None => MixedHamiltonian::new(0, 0, 0),
587        }
588    }
589}
590
591/// Implements the extend function (Extend trait) of MixedHamiltonian.
592///
593impl Extend<(HermitianMixedProduct, CalculatorComplex)> for MixedHamiltonian {
594    /// Extends the MixedHamiltonian by the specified operations (in Iterator form).
595    ///
596    /// # Arguments
597    ///
598    /// * `iter` - The iterator containing the operations by which to extend the MixedHamiltonian.
599    ///
600    /// # Panics
601    ///
602    /// * Internal bug in add_operator_product.
603    fn extend<I: IntoIterator<Item = (HermitianMixedProduct, CalculatorComplex)>>(
604        &mut self,
605        iter: I,
606    ) {
607        for (pp, cc) in iter {
608            self.add_operator_product(pp, cc)
609                .expect("Internal error in add_operator_product");
610        }
611    }
612}
613
614/// Implements the format function (Display trait) of MixedHamiltonian.
615///
616impl fmt::Display for MixedHamiltonian {
617    /// Formats the MixedHamiltonian using the given formatter.
618    ///
619    /// # Arguments
620    ///
621    /// * `f` - The formatter to use.
622    ///
623    /// # Returns
624    ///
625    /// * `std::fmt::Result` - The formatted MixedHamiltonian.
626    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
627        let mut output = "MixedHamiltonian{\n".to_string();
628        for (key, val) in self.iter() {
629            writeln!(output, "{key}: {val},")?;
630        }
631        output.push('}');
632
633        write!(f, "{output}")
634    }
635}
636
637#[cfg(test)]
638mod test {
639    use super::*;
640    use crate::bosons::BosonProduct;
641    use crate::fermions::FermionProduct;
642    use crate::spins::PauliProduct;
643    use crate::STRUQTURE_VERSION;
644    use serde_test::{assert_tokens, Configure, Token};
645
646    // Test the Clone and PartialEq traits of MixedHamiltonian
647    #[test]
648    fn so_from_sos() {
649        let pp: HermitianMixedProduct = HermitianMixedProduct::new(
650            [PauliProduct::new().z(2)],
651            [BosonProduct::new([0], [3]).unwrap()],
652            [FermionProduct::new([0], [2]).unwrap()],
653        )
654        .unwrap();
655        let sos = MixedHamiltonianSerialize {
656            items: vec![(pp.clone(), 0.5.into(), 0.0.into())],
657            n_spins: 1,
658            n_bosons: 1,
659            n_fermions: 1,
660            serialisation_meta: crate::StruqtureSerialisationMeta {
661                type_name: "MixedHamiltonian".to_string(),
662                min_version: (2, 0, 0),
663                version: STRUQTURE_VERSION.to_string(),
664            },
665        };
666        let mut so = MixedHamiltonian::new(1, 1, 1);
667        so.set(pp, CalculatorComplex::from(0.5)).unwrap();
668
669        assert_eq!(MixedHamiltonian::try_from(sos.clone()).unwrap(), so);
670        assert_eq!(MixedHamiltonianSerialize::from(so), sos);
671    }
672    // Test the Clone and PartialEq traits of MixedHamiltonian
673    #[test]
674    fn clone_partial_eq() {
675        let pp: HermitianMixedProduct = HermitianMixedProduct::new(
676            [PauliProduct::new().z(2)],
677            [BosonProduct::new([0], [3]).unwrap()],
678            [FermionProduct::new([0], [2]).unwrap()],
679        )
680        .unwrap();
681        let sos = MixedHamiltonianSerialize {
682            items: vec![(pp, 0.5.into(), 0.0.into())],
683            n_spins: 1,
684            n_bosons: 1,
685            n_fermions: 1,
686            serialisation_meta: crate::StruqtureSerialisationMeta {
687                type_name: "MixedHamiltonian".to_string(),
688                min_version: (2, 0, 0),
689                version: "2.0.0".to_string(),
690            },
691        };
692
693        // Test Clone trait
694        assert_eq!(sos.clone(), sos);
695
696        // Test PartialEq trait
697        let pp_1: HermitianMixedProduct = HermitianMixedProduct::new(
698            [PauliProduct::new().z(2)],
699            [BosonProduct::new([0], [3]).unwrap()],
700            [FermionProduct::new([0], [2]).unwrap()],
701        )
702        .unwrap();
703        let sos_1 = MixedHamiltonianSerialize {
704            items: vec![(pp_1, 0.5.into(), 0.0.into())],
705            n_spins: 1,
706            n_bosons: 1,
707            n_fermions: 1,
708            serialisation_meta: crate::StruqtureSerialisationMeta {
709                type_name: "MixedHamiltonian".to_string(),
710                min_version: (2, 0, 0),
711                version: "2.0.0".to_string(),
712            },
713        };
714        let pp_2: HermitianMixedProduct = HermitianMixedProduct::new(
715            [PauliProduct::new().z(0)],
716            [BosonProduct::new([0], [3]).unwrap()],
717            [FermionProduct::new([0], [2]).unwrap()],
718        )
719        .unwrap();
720        let sos_2 = MixedHamiltonianSerialize {
721            items: vec![(pp_2, 0.5.into(), 0.0.into())],
722            n_spins: 1,
723            n_bosons: 1,
724            n_fermions: 1,
725            serialisation_meta: crate::StruqtureSerialisationMeta {
726                type_name: "MixedHamiltonian".to_string(),
727                min_version: (2, 0, 0),
728                version: "2.0.0".to_string(),
729            },
730        };
731        assert!(sos_1 == sos);
732        assert!(sos == sos_1);
733        assert!(sos_2 != sos);
734        assert!(sos != sos_2);
735    }
736
737    // Test the Debug trait of MixedHamiltonian
738    #[test]
739    fn debug() {
740        let pp: HermitianMixedProduct = HermitianMixedProduct::new(
741            [PauliProduct::new().z(2)],
742            [BosonProduct::new([0], [3]).unwrap()],
743            [FermionProduct::new([0], [2]).unwrap()],
744        )
745        .unwrap();
746        let sos = MixedHamiltonianSerialize {
747            items: vec![(pp, 0.5.into(), 0.0.into())],
748            n_spins: 1,
749            n_bosons: 1,
750            n_fermions: 1,
751            serialisation_meta: crate::StruqtureSerialisationMeta {
752                type_name: "MixedHamiltonian".to_string(),
753                min_version: (2, 0, 0),
754                version: "2.0.0".to_string(),
755            },
756        };
757
758        assert_eq!(
759            format!("{sos:?}"),
760            "MixedHamiltonianSerialize { items: [(HermitianMixedProduct { spins: [PauliProduct { items: [(2, Z)] }], bosons: [BosonProduct { creators: [0], annihilators: [3] }], fermions: [FermionProduct { creators: [0], annihilators: [2] }] }, Float(0.5), Float(0.0))], n_spins: 1, n_bosons: 1, n_fermions: 1, serialisation_meta: StruqtureSerialisationMeta { type_name: \"MixedHamiltonian\", min_version: (2, 0, 0), version: \"2.0.0\" } }"
761        );
762    }
763
764    /// Test MixedHamiltonian Serialization and Deserialization traits (readable)
765    #[test]
766    fn serde_readable() {
767        let pp: HermitianMixedProduct = HermitianMixedProduct::new(
768            [PauliProduct::new().z(2)],
769            [BosonProduct::new([0], [3]).unwrap()],
770            [FermionProduct::new([0], [2]).unwrap()],
771        )
772        .unwrap();
773        let sos = MixedHamiltonianSerialize {
774            items: vec![(pp, 0.5.into(), 0.0.into())],
775            n_spins: 1,
776            n_bosons: 1,
777            n_fermions: 1,
778            serialisation_meta: crate::StruqtureSerialisationMeta {
779                type_name: "MixedHamiltonian".to_string(),
780                min_version: (2, 0, 0),
781                version: "2.0.0".to_string(),
782            },
783        };
784
785        assert_tokens(
786            &sos.readable(),
787            &[
788                Token::Struct {
789                    name: "MixedHamiltonianSerialize",
790                    len: 5,
791                },
792                Token::Str("items"),
793                Token::Seq { len: Some(1) },
794                Token::Tuple { len: 3 },
795                Token::Str("S2Z:Bc0a3:Fc0a2:"),
796                Token::F64(0.5),
797                Token::F64(0.0),
798                Token::TupleEnd,
799                Token::SeqEnd,
800                Token::Str("n_spins"),
801                Token::U64(1),
802                Token::Str("n_bosons"),
803                Token::U64(1),
804                Token::Str("n_fermions"),
805                Token::U64(1),
806                Token::Str("serialisation_meta"),
807                Token::Struct {
808                    name: "StruqtureSerialisationMeta",
809                    len: 3,
810                },
811                Token::Str("type_name"),
812                Token::Str("MixedHamiltonian"),
813                Token::Str("min_version"),
814                Token::Tuple { len: 3 },
815                Token::U64(2),
816                Token::U64(0),
817                Token::U64(0),
818                Token::TupleEnd,
819                Token::Str("version"),
820                Token::Str("2.0.0"),
821                Token::StructEnd,
822                Token::StructEnd,
823            ],
824        );
825    }
826
827    /// Test MixedHamiltonian Serialization and Deserialization traits (compact)
828    #[test]
829    fn serde_compact() {
830        let pp: HermitianMixedProduct = HermitianMixedProduct::new(
831            [PauliProduct::new().z(2)],
832            [BosonProduct::new([0], [3]).unwrap()],
833            [FermionProduct::new([0], [2]).unwrap()],
834        )
835        .unwrap();
836        let sos = MixedHamiltonianSerialize {
837            items: vec![(pp, 0.5.into(), 0.0.into())],
838            n_spins: 1,
839            n_bosons: 1,
840            n_fermions: 1,
841            serialisation_meta: crate::StruqtureSerialisationMeta {
842                type_name: "MixedHamiltonian".to_string(),
843                min_version: (2, 0, 0),
844                version: "2.0.0".to_string(),
845            },
846        };
847
848        assert_tokens(
849            &sos.compact(),
850            &[
851                Token::Struct {
852                    name: "MixedHamiltonianSerialize",
853                    len: 5,
854                },
855                Token::Str("items"),
856                Token::Seq { len: Some(1) },
857                Token::Tuple { len: 3 },
858                Token::Tuple { len: 3 },
859                Token::Seq { len: Some(1) },
860                Token::Seq { len: Some(1) },
861                Token::Tuple { len: 2 },
862                Token::U64(2),
863                Token::UnitVariant {
864                    name: "SinglePauliOperator",
865                    variant: "Z",
866                },
867                Token::TupleEnd,
868                Token::SeqEnd,
869                Token::SeqEnd,
870                Token::Seq { len: Some(1) },
871                Token::Tuple { len: 2 },
872                Token::Seq { len: Some(1) },
873                Token::U64(0),
874                Token::SeqEnd,
875                Token::Seq { len: Some(1) },
876                Token::U64(3),
877                Token::SeqEnd,
878                Token::TupleEnd,
879                Token::SeqEnd,
880                Token::Seq { len: Some(1) },
881                Token::Tuple { len: 2 },
882                Token::Seq { len: Some(1) },
883                Token::U64(0),
884                Token::SeqEnd,
885                Token::Seq { len: Some(1) },
886                Token::U64(2),
887                Token::SeqEnd,
888                Token::TupleEnd,
889                Token::SeqEnd,
890                Token::TupleEnd,
891                Token::NewtypeVariant {
892                    name: "CalculatorFloat",
893                    variant: "Float",
894                },
895                Token::F64(0.5),
896                Token::NewtypeVariant {
897                    name: "CalculatorFloat",
898                    variant: "Float",
899                },
900                Token::F64(0.0),
901                Token::TupleEnd,
902                Token::SeqEnd,
903                Token::Str("n_spins"),
904                Token::U64(1),
905                Token::Str("n_bosons"),
906                Token::U64(1),
907                Token::Str("n_fermions"),
908                Token::U64(1),
909                Token::Str("serialisation_meta"),
910                Token::Struct {
911                    name: "StruqtureSerialisationMeta",
912                    len: 3,
913                },
914                Token::Str("type_name"),
915                Token::Str("MixedHamiltonian"),
916                Token::Str("min_version"),
917                Token::Tuple { len: 3 },
918                Token::U64(2),
919                Token::U64(0),
920                Token::U64(0),
921                Token::TupleEnd,
922                Token::Str("version"),
923                Token::Str("2.0.0"),
924                Token::StructEnd,
925                Token::StructEnd,
926            ],
927        );
928    }
929}