Source code for pennylane.labs.trotter_error.product_formulas.product_formula
# Copyright 2025 Xanadu Quantum Technologies Inc.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
# http://www.apache.org/licenses/LICENSE-2.0
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""The ProductFormula class"""
from __future__ import annotations
import copy
from collections.abc import Hashable, Sequence
from typing import Any
import numpy as np
from scipy.linalg import expm, fractional_matrix_power
[docs]
class ProductFormula:
r"""Class for representing product formulas.
For a set of Hermitian operators :math:`H_1,\dots,H_n`
a product formula is any function in the form :math:`U(t) = \prod_{k=1}^n e^{it\alpha_k H_k}` with
:math:`\alpha_k \in \mathbb{R}`.
Args:
terms (Sequence[Hashable] | Sequence[``ProductFormula``]): Either a list of labels for the
Hermitian operators or a list of ``ProductFormula`` objcts. When a list of labels is given,
the product formula returned is the product of exponentials of the lables. When a list of product
formulas is given the product formula returned is the product of the given product formulas.
coeffs: (Sequence[float]): A list of coefficients corresponding to the given terms. This argument
is not needed when the terms are ``ProductFormula`` objects.
exponent (float): Raises the product formula to the power of ``exponent``. Defaults to 1.0.
label (str): Optional parameter used for pretty printing.
**Example**
This example uses :class:`~pennylane.labs.troter_error.ProductFormula` to build the fourth order
Troter-Suzuki formula on three fragments. First we build the second order formula.
>>> from pennylane.labs.trotter_error import ProductFormula
>>>
>>> frag_labels = ["A", "B", "C", "B", "A"]
>>> frag_coeffs = [1/2, 1/2, 1, 1/2, 1/2]
>>> second_order = ProductFormula(frag_labels, frag_coeffs)
Now we build the fourth order formula out of the second order formula using arithmetic operations.
>>> u = 1 / (4 - 4**(1/3))
>>> v = 1 - 4*u
>>>
>>> fourth_order = second_order(u)**2 @ second_order(v) @ second_order(u)**2
"""
def __init__(
self,
terms: Sequence[Hashable] | Sequence[ProductFormula],
coeffs: Sequence[float] = None,
exponent: float = 1.0,
label: str = None,
):
if any(not isinstance(term, type(terms[0])) for term in terms):
raise TypeError("All terms must have the same type.")
if not isinstance(terms[0], (Hashable, ProductFormula)):
raise TypeError(
f"Terms must either be Hashable type or of type ProductFormula. Got {type(terms[0])} instead."
)
if isinstance(terms[0], ProductFormula):
self.recursive = True
elif isinstance(terms[0], Hashable):
if coeffs is None:
raise ValueError("List of coefficients must be given.")
if len(coeffs) != len(terms):
raise ValueError("Number of coefficients must equal number of terms.")
self.recursive = False
self.fragments = set()
for term in terms:
if isinstance(term, ProductFormula):
self.fragments = set.union(self.fragments, term.fragments)
else:
self.fragments.add(term)
self.terms = terms
self.coeffs = coeffs if coeffs else [1] * len(self.terms)
self.exponent = exponent
self.label = label
self._ordered_terms = {}
position = 0
for term in self.terms:
if term not in self._ordered_terms:
self._ordered_terms[term] = position
position += 1
def __call__(self, t: float):
ret = copy.copy(self)
ret.label = f"{self.label}({t})"
if ret.recursive:
ret.terms = [term(t) for term in ret.terms]
ret._ordered_terms = {
term(t): position for term, position in ret._ordered_terms.items()
}
else:
ret.coeffs = [t * coeff for coeff in ret.coeffs]
return ret
def __eq__(self, other: ProductFormula) -> bool:
if self.recursive != other.recursive:
return False
if self.terms != other.terms:
return False
if self.coeffs != other.coeffs:
return False
return self.exponent == other.exponent
def __hash__(self) -> int:
terms = tuple(self.terms)
coeffs = tuple(self.coeffs)
return hash((terms, coeffs, self.exponent))
def __matmul__(self, other: ProductFormula) -> ProductFormula:
return ProductFormula([self, other], label=f"{self.label}@{other.label}")
def __pow__(self, z: float) -> ProductFormula:
ret = copy.copy(self)
ret.exponent = z * self.exponent
return ret
def __repr__(self) -> str:
if self.label:
return f"{self.label}**{self.exponent}" if self.exponent != 1 else f"{self.label}"
if self.recursive:
return "@".join(term.__repr__() for term in self.terms)
return "@".join([f"Exp({coeff}*H_{term})" for coeff, term in zip(self.coeffs, self.terms)])
[docs]
def to_matrix(self, fragments: dict[Hashable, np.ndarray]) -> np.ndarray:
"""Returns a numpy representation of the product formula.
Args:
fragments (Dict[Hashable, Fragment]): The matrix representations of the fragment labels.
**Example**
>>> import numpy as np
>>> from pennylane.labs.trotter_error import ProductFormula
>>>
>>> frag_labels = ["A", "B", "C", "B", "A"]
>>> frag_coeffs = [1/2, 1/2, 1, 1/2, 1/2]
>>> second_order = ProductFormula(frag_labels, frag_coeffs)
>>>
>>> np.random.seed(42)
>>> fragments = {
>>> "A": np.random.random(size=(3, 3)),
>>> "B": np.random.random(size=(3, 3)),
>>> "C": np.random.random(size=(3, 3)),
>>> }
>>>
>>> second_order.to_matrix(fragments)
[[20.53683969 24.33566914 25.4931284 ]
[12.50207018 15.44505726 15.01069493]
[13.52951601 17.64888648 18.04980336]]
"""
accumulator = _MultiplicativeIdentity()
for term, coeff in zip(self.terms, self.coeffs):
if isinstance(term, ProductFormula):
accumulator @= term.to_matrix(fragments)
else:
accumulator @= expm(coeff * fragments[term])
return fractional_matrix_power(accumulator, self.exponent)
@property
def ordered_fragments(self) -> dict[Hashable, int]:
"""Return the fragment ordering used by the product formula.
**Example**
>>> from pennylane.labs.trotter_error import ProductFormula
>>>
>>> pf1 = ProductFormula(["A", "B", "C"], [1, 1, 1])
>>> pf2 = ProductFormula(["X", "Y", "Z"], [1, 1, 1])
>>>
>>> pf = pf1 @ pf2
>>>
>>> pf.ordered_fragments
{'A': 0, 'B': 1, 'C': 2, 'X': 3, 'Y': 4, 'Z': 5}
"""
if not self.recursive:
return self.ordered_terms
ordered_fragments = {}
position = 0
for term in self.terms:
for fragment in term.ordered_fragments:
if fragment in ordered_fragments:
continue
ordered_fragments[fragment] = position
position += 1
return ordered_fragments
@property
def ordered_terms(self) -> dict[Hashable, int]:
"""Return the term ordering used by the product formula.
**Example**
>>> from pennylane.labs.trotter_error import ProductFormula, bch_expansion
>>>
>>> frag_labels = ["A", "B", "C", "B", "A"]
>>> frag_coeffs = [1/2, 1/2, 1, 1/2, 1/2]
>>> second_order = ProductFormula(frag_labels, frag_coeffs, label="U")
>>>
>>> u = 1 / (4 - 4**(1/3))
>>> v = 1 - 4*u
>>>
>>> fourth_order = second_order(u)**2 @ second_order(v) @ second_order(u)**2
>>> fourth_order.ordered_terms
{U(0.4144907717943757)@U(-0.6579630871775028): 0, U(0.4144907717943757)**2.0: 1}
"""
ordered_terms = {}
position = 0
for term in self.terms:
if term not in ordered_terms:
ordered_terms[term] = position
position += 1
return ordered_terms
class _MultiplicativeIdentity:
"""A generic multiplicative identity that can be multiplied with any Python object."""
def __matmul__(self, other: Any):
return other
def __rmatmul__(self, other: Any):
return other
_modules/pennylane/labs/trotter_error/product_formulas/product_formula
Download Python script
Download Notebook
View on GitHub