struqture/mixed_systems/
mixed_plus_minus_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::{MixedIndex, MixedProduct};
14use crate::bosons::BosonProduct;
15use crate::fermions::FermionProduct;
16use crate::spins::{PauliProduct, PlusMinusProduct};
17use crate::{ModeIndex, StruqtureError, SymmetricIndex};
18use itertools::Itertools;
19use num_complex::Complex64;
20use serde::{
21    de::{Error, SeqAccess, Visitor},
22    ser::SerializeTuple,
23    Deserialize, Deserializer, Serialize, Serializer,
24};
25use std::str::FromStr;
26use tinyvec::TinyVec;
27
28/// A mixed product of pauli products, boson products and fermion products.
29///
30/// A [crate::spins::PlusMinusProduct] 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/// It is used as an index for non-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::PlusMinusProduct;
43/// use struqture::bosons::BosonProduct;
44/// use struqture::fermions::FermionProduct;
45/// use struqture::mixed_systems::MixedPlusMinusProduct;
46///
47/// let m_product = MixedPlusMinusProduct::new([PlusMinusProduct::new().z(0)], [BosonProduct::new([0], [1]).unwrap()], [FermionProduct::new([0], [0]).unwrap()]);
48/// println!("{}", m_product);
49///
50/// ```
51#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Default)]
52pub struct MixedPlusMinusProduct {
53    /// List of spin sub-indices
54    pub(crate) spins: TinyVec<[PlusMinusProduct; 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 MixedPlusMinusProduct {
63    fn schema_name() -> std::borrow::Cow<'static, str> {
64        "MixedPlusMinusProduct".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  +, - and Z are preceeded and creators (c) and annihilators (a) are followed by the modes they are acting on. E.g. :S0+1+:Bc0a1:Fc0a2:."
71        })
72    }
73}
74
75impl crate::SerializationSupport for MixedPlusMinusProduct {
76    fn struqture_type() -> crate::StruqtureType {
77        crate::StruqtureType::MixedPlusMinusProduct
78    }
79}
80
81impl Serialize for MixedPlusMinusProduct {
82    /// Serialization function for MixedPlusMinusProduct according to string type.
83    ///
84    /// # Arguments
85    ///
86    /// * `self` - MixedPlusMinusProduct to be serialized.
87    /// * `serializer` - Serializer used for serialization.
88    ///
89    /// # Returns
90    ///
91    /// `S::Ok` - Serialized instance of MixedPlusMinusProduct.
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 MixedPlusMinusProduct {
113    /// Deserialization function for MixedPlusMinusProduct.
114    ///
115    /// # Arguments
116    ///
117    /// * `self` - Serialized instance of MixedPlusMinusProduct to be deserialized.
118    /// * `deserializer` - Deserializer used for deserialization.
119    ///
120    /// # Returns
121    ///
122    /// `DecoherenceProduct` - Deserialized instance of MixedPlusMinusProduct.
123    /// `D::Error` - Error in the deserialization process.
124    fn deserialize<D>(deserializer: D) -> Result<MixedPlusMinusProduct, 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 = MixedPlusMinusProduct;
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<MixedPlusMinusProduct, E>
139                where
140                    E: serde::de::Error,
141                {
142                    MixedPlusMinusProduct::from_str(v).map_err(|err| E::custom(format!("{err:?}")))
143                }
144
145                fn visit_borrowed_str<E>(self, v: &'de str) -> Result<MixedPlusMinusProduct, E>
146                where
147                    E: serde::de::Error,
148                {
149                    MixedPlusMinusProduct::from_str(v).map_err(|err| E::custom(format!("{err:?}")))
150                }
151            }
152
153            deserializer.deserialize_str(TemporaryVisitor)
154        } else {
155            struct MixedProductVisitor;
156            impl<'de> serde::de::Visitor<'de> for MixedProductVisitor {
157                type Value = MixedPlusMinusProduct;
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<[PlusMinusProduct; 2]> = match access.next_element()? {
170                        Some(x) => x,
171                        None => {
172                            return Err(M::Error::custom("Missing spin 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("Missing boson sequence".to_string()));
179                        }
180                    };
181                    let fermions: TinyVec<[FermionProduct; 2]> = match access.next_element()? {
182                        Some(x) => x,
183                        None => {
184                            return Err(M::Error::custom("Missing fermion sequence".to_string()));
185                        }
186                    };
187
188                    Ok(MixedPlusMinusProduct {
189                        spins,
190                        bosons,
191                        fermions,
192                    })
193                }
194            }
195            let pp_visitor = MixedProductVisitor;
196
197            deserializer.deserialize_tuple(3, pp_visitor)
198        }
199    }
200}
201
202impl MixedPlusMinusProduct {
203    /// Creates a new MixedPlusMinusProduct.
204    ///
205    /// # Arguments
206    ///
207    /// * `spins` - Products of pauli operators acting on qubits.
208    /// * `bosons` - Products of bosonic creation and annihilation operators.
209    /// * `fermions` - Products of fermionic creation and annihilation operators.
210    ///
211    /// # Returns
212    ///
213    /// * Ok(`Self`) - a new MixedPlusMinusProduct with the input of spins and bosons.
214    pub fn new(
215        spins: impl IntoIterator<Item = PlusMinusProduct>,
216        bosons: impl IntoIterator<Item = BosonProduct>,
217        fermions: impl IntoIterator<Item = FermionProduct>,
218    ) -> Self {
219        Self {
220            spins: spins.into_iter().collect(),
221            bosons: bosons.into_iter().collect(),
222            fermions: fermions.into_iter().collect(),
223        }
224    }
225
226    /// Gets the spin Products of Self.
227    ///
228    /// # Returns
229    ///
230    /// * `Iter<PlusMinusProduct>` - The spin Products in Self.
231    pub fn spins(&self) -> std::slice::Iter<'_, PlusMinusProduct> {
232        self.spins.iter()
233    }
234
235    /// Gets the boson Products of Self.
236    ///
237    /// # Returns
238    ///
239    /// * `Iter<BosonProduct>` - The boson Products in Self.
240    pub fn bosons(&self) -> std::slice::Iter<'_, BosonProduct> {
241        self.bosons.iter()
242    }
243
244    /// Gets the fermion Products of Self.
245    ///
246    /// # Returns
247    ///
248    /// * `Iter<FermionProduct>` - The fermion Products in Self.
249    pub fn fermions(&self) -> std::slice::Iter<'_, FermionProduct> {
250        self.fermions.iter()
251    }
252
253    /// Returns the current number of spins each subsystem acts upon.
254    ///
255    /// # Returns
256    ///
257    /// * `Vec<usize>` - Number of spins in each spin sub-system.
258    pub fn current_number_spins(&self) -> Vec<usize> {
259        self.spins().map(|s| s.current_number_spins()).collect()
260    }
261
262    /// Returns the current number of bosonic modes each subsystem acts upon.
263    ///
264    /// # Returns
265    ///
266    /// * `Vec<usize>` - Number of bosons in each boson sub-system.
267    pub fn current_number_bosonic_modes(&self) -> Vec<usize> {
268        self.bosons().map(|b| b.current_number_modes()).collect()
269    }
270
271    /// Returns the current number of fermionic modes each subsystem acts upon.
272    ///
273    /// # Returns
274    ///
275    /// * `Vec<usize>` - Number of fermions in each fermion sub-system.
276    pub fn current_number_fermionic_modes(&self) -> Vec<usize> {
277        self.fermions().map(|f| f.current_number_modes()).collect()
278    }
279
280    /// Export to struqture_1 format.
281    #[cfg(feature = "struqture_1_export")]
282    pub fn to_struqture_1(
283        &self,
284    ) -> Result<struqture_1::mixed_systems::MixedPlusMinusProduct, StruqtureError> {
285        let self_string = self.to_string();
286        let struqture_1_product = struqture_1::mixed_systems::MixedPlusMinusProduct::from_str(
287            &self_string,
288        )
289        .map_err(|err| StruqtureError::GenericError {
290            msg: format!("{err}"),
291        })?;
292        Ok(struqture_1_product)
293    }
294
295    /// Export to struqture_1 format.
296    #[cfg(feature = "struqture_1_import")]
297    pub fn from_struqture_1(
298        value: &struqture_1::mixed_systems::MixedPlusMinusProduct,
299    ) -> Result<Self, StruqtureError> {
300        let value_string = value.to_string();
301        let pauli_product = Self::from_str(&value_string)?;
302        Ok(pauli_product)
303    }
304}
305
306impl From<MixedProduct> for Vec<(MixedPlusMinusProduct, Complex64)> {
307    /// Converts a MixedProduct into a vector of tuples of (MixedPlusMinusProduct, Complex64).
308    ///
309    /// # Arguments
310    ///
311    /// * `value` - The MixedProduct to convert.
312    ///
313    /// # Returns
314    ///
315    /// * `Self` - The MixedProduct converted into a vector of tuples of (MixedPlusMinusProduct, Complex64).
316    fn from(value: MixedProduct) -> Self {
317        let mut return_vec: Vec<(MixedPlusMinusProduct, Complex64)> = Vec::new();
318        let mut spins_vec: Vec<Vec<(PlusMinusProduct, Complex64)>> = Vec::new();
319        for mixed_product in value.spins() {
320            let conversion = Vec::<(PlusMinusProduct, Complex64)>::from(mixed_product.clone());
321            spins_vec.push(conversion);
322        }
323
324        // converted: list of entries with n subsystem PP (in vec) and prefactor
325        let mut converted: Vec<(Vec<PlusMinusProduct>, Complex64)> = Vec::new();
326        for (mp, prefactor) in spins_vec[0].clone() {
327            converted.push((vec![mp], prefactor))
328        }
329        for element in spins_vec.iter().skip(1) {
330            let mut new_converted = Vec::new();
331            for ((left, prefactor), (right, right_factor)) in
332                converted.iter().cartesian_product(element)
333            {
334                let mut new_vec = left.clone();
335                new_vec.push(right.clone());
336                new_converted.push((new_vec, prefactor * right_factor))
337            }
338            converted = new_converted;
339        }
340
341        for (vec_mp, cc) in converted {
342            return_vec.push((
343                MixedPlusMinusProduct::new(
344                    vec_mp,
345                    value.bosons().cloned(),
346                    value.fermions().cloned(),
347                ),
348                cc,
349            ));
350        }
351        return_vec
352    }
353}
354
355impl TryFrom<MixedPlusMinusProduct> for Vec<(MixedProduct, Complex64)> {
356    type Error = StruqtureError;
357    /// Converts a MixedPlusMinusProduct into a vector of tuples of (MixedProduct, Complex64).
358    ///
359    /// # Arguments
360    ///
361    /// * `value` - The MixedPlusMinusProduct to convert.
362    ///
363    /// # Returns
364    ///
365    /// * `Self` - The MixedPlusMinusProduct converted into a vector of tuples of (MixedProduct, Complex64).
366    fn try_from(value: MixedPlusMinusProduct) -> Result<Self, Self::Error> {
367        let mut return_vec: Vec<(MixedProduct, Complex64)> = Vec::new();
368        let mut spins_vec: Vec<Vec<(PauliProduct, Complex64)>> = Vec::new();
369        for mixed_product in value.spins() {
370            let conversion = Vec::<(PauliProduct, Complex64)>::from(mixed_product.clone());
371            spins_vec.push(conversion);
372        }
373
374        // converted: list of entries with n subsystem PP (in vec) and prefactor
375        let mut converted: Vec<(Vec<PauliProduct>, Complex64)> = Vec::new();
376        for (mp, prefactor) in spins_vec[0].clone() {
377            converted.push((vec![mp], prefactor))
378        }
379        for element in spins_vec.iter().skip(1) {
380            let mut new_converted = Vec::new();
381            for ((left, prefactor), (right, right_factor)) in
382                converted.iter().cartesian_product(element)
383            {
384                let mut new_vec = left.clone();
385                new_vec.push(right.clone());
386                new_converted.push((new_vec, prefactor * right_factor))
387            }
388            converted = new_converted;
389        }
390
391        for (vec_mp, cc) in converted {
392            return_vec.push((
393                MixedProduct::new(vec_mp, value.bosons().cloned(), value.fermions().cloned())?,
394                cc,
395            ));
396        }
397        Ok(return_vec)
398    }
399}
400
401impl FromStr for MixedPlusMinusProduct {
402    type Err = StruqtureError;
403    /// Constructs a MixedPlusMinusProduct from a string.
404    ///
405    /// # Arguments
406    ///
407    /// * `s` - The string to convert.
408    ///
409    /// # Returns
410    ///
411    /// * `Ok(Self)` - The successfully converted MixedPlusMinusProduct.
412    /// * `Err(StruqtureError::ParsingError)` - Encountered subsystem that is neither spin, nor boson, nor fermion.
413    fn from_str(s: &str) -> Result<Self, Self::Err> {
414        let mut spins: TinyVec<[PlusMinusProduct; 2]> =
415            TinyVec::<[PlusMinusProduct; 2]>::with_capacity(2);
416        let mut bosons: TinyVec<[BosonProduct; 2]> = TinyVec::<[BosonProduct; 2]>::with_capacity(2);
417        let mut fermions: TinyVec<[FermionProduct; 2]> =
418            TinyVec::<[FermionProduct; 2]>::with_capacity(2);
419        let subsystems = s.split(':').filter(|s| !s.is_empty());
420        for subsystem in subsystems {
421            if let Some(rest) = subsystem.strip_prefix('S') {
422                spins.push(PlusMinusProduct::from_str(rest)?);
423            } else if let Some(rest) = subsystem.strip_prefix('B') {
424                bosons.push(BosonProduct::from_str(rest)?);
425            } else if let Some(rest) = subsystem.strip_prefix('F') {
426                fermions.push(FermionProduct::from_str(rest)?);
427            } else {
428                return Err(StruqtureError::ParsingError {
429                    target_type: "MixedPlusMinusProduct".to_string(),
430                    msg: format!(
431                        "Encountered subsystem that is neither spin, nor boson, nor fermion: {subsystem}"
432                    ),
433                });
434            }
435        }
436
437        Ok(Self {
438            spins,
439            bosons,
440            fermions,
441        })
442    }
443}
444
445impl SymmetricIndex for MixedPlusMinusProduct {
446    // From trait
447    fn hermitian_conjugate(&self) -> (Self, f64) {
448        let mut coefficient = 1.0;
449
450        let mut new_spins = self.spins.clone();
451        for spin in new_spins.iter_mut() {
452            let (conj_spin, coeff) = spin.hermitian_conjugate();
453            *spin = conj_spin;
454            coefficient *= coeff;
455        }
456        let mut new_bosons = self.bosons.clone();
457        for boson in new_bosons.iter_mut() {
458            let (conj_boson, coeff) = boson.hermitian_conjugate();
459            *boson = conj_boson;
460            coefficient *= coeff;
461        }
462        let mut new_fermions = self.fermions.clone();
463        for fermion in new_fermions.iter_mut() {
464            let (conj_fermion, coeff) = fermion.hermitian_conjugate();
465            *fermion = conj_fermion;
466            coefficient *= coeff;
467        }
468        (
469            Self {
470                spins: new_spins,
471                bosons: new_bosons,
472                fermions: new_fermions,
473            },
474            coefficient,
475        )
476    }
477
478    // From trait
479    fn is_natural_hermitian(&self) -> bool {
480        self.bosons.iter().all(|b| b.is_natural_hermitian())
481            && self.fermions.iter().all(|f| f.is_natural_hermitian())
482    }
483}
484
485/// Implements the format function (Display trait) of MixedPlusMinusProduct.
486///
487impl std::fmt::Display for MixedPlusMinusProduct {
488    /// Formats the MixedPlusMinusProduct using the given formatter.
489    ///
490    /// # Arguments
491    ///
492    /// * `f` - The formatter to use.
493    ///
494    /// # Returns
495    ///
496    /// * `std::fmt::Result` - The formatted MixedPlusMinusProduct.
497    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
498        let mut string: String = String::new();
499        for spin in self.spins() {
500            string.push_str(format!("S{spin}:").as_str());
501        }
502        for boson in self.bosons() {
503            string.push_str(format!("B{boson}:").as_str());
504        }
505        for fermion in self.fermions() {
506            string.push_str(format!("F{fermion}:").as_str());
507        }
508        write!(f, "{string}")
509    }
510}
511
512#[cfg(test)]
513mod test {
514    use super::*;
515    use itertools::Itertools;
516    use test_case::test_case;
517
518    #[test_case("", &[], &[]; "empty")]
519    #[test_case(":S0+1+:", &[], &[PlusMinusProduct::from_str("0+1+").unwrap()]; "single spin systems")]
520    #[test_case(":S0+1+:S0Z:", &[], &[PlusMinusProduct::from_str("0+1+").unwrap(), PlusMinusProduct::from_str("0Z").unwrap()]; "two spin systems")]
521    #[test_case(":S0+1+:Bc0a1:", &[BosonProduct::from_str("c0a1").unwrap()], &[PlusMinusProduct::from_str("0+1+").unwrap()]; "spin-boson systems")]
522    fn from_string(stringformat: &str, bosons: &[BosonProduct], spins: &[PlusMinusProduct]) {
523        let test_new = <MixedPlusMinusProduct as std::str::FromStr>::from_str(stringformat);
524        assert!(test_new.is_ok());
525        let res = test_new.unwrap();
526        let empty_bosons: Vec<BosonProduct> = bosons.to_vec();
527        let res_bosons: Vec<BosonProduct> = res.bosons.iter().cloned().collect_vec();
528        assert_eq!(res_bosons, empty_bosons);
529        let empty_spins: Vec<PlusMinusProduct> = spins.to_vec();
530        let res_spins: Vec<PlusMinusProduct> = res.spins.iter().cloned().collect_vec();
531        assert_eq!(res_spins, empty_spins);
532    }
533}