qnet.algebra.core.abstract_algebra module

Base classes for all Expressions and Operations.

The abstract algebra package provides the foundation for symbolic algebra of quantum objects or circuits. All symbolic objects are an instance of Expression. Algebraic combinations of atomic expressions are instances of Operation. In this way, any symbolic expression is a tree of operations, with children of each node defined through the Operation.operands attribute, and the leaves being atomic expressions.

See Expressions and Operations for design details and usage.

Summary

Classes:

Expression Base class for all QNET Expressions
Operation Base class for “operations”

Functions:

substitute Substitute symbols or (sub-)expressions with the given replacements and re-evalute the result

__all__: Expression, Operation, substitute

Reference

class qnet.algebra.core.abstract_algebra.Expression(*args, **kwargs)[source]

Bases: object

Base class for all QNET Expressions

Expressions should generally be instantiated using the create() class method, which takes into account the algebraic properties of the Expression and and applies simplifications. It also uses memoization to cache all known (sub-)expression. This is possible because expressions are intended to be immutable. Any changes to an expression should be made through e.g. substitute() or apply_rule(), which returns a new modified expression.

Every expression has a well-defined list of positional and keyword arguments that uniquely determine the expression and that may be accessed through the args and kwargs property. That is,

expr.__class__(*expr.args, **expr.kwargs)

will return and object identical to expr.

Class Attributes:
 
  • instance_caching (bool) – Flag to indicate whether the create() class method should cache the instantiation of instances. If True, repeated calls to create() with the same arguments return instantly, instead of re-evaluating all simplifications and rules.
  • simplifications (list) – List of callable simplifications that create() will use to process its positional and keyword arguments. Each callable must take three parameters (the class, the list args of positional arguments given to create() and a dictionary kwargs of keyword arguments given to create()) and return either a tuple of new args and kwargs (which are then handed to the next callable), or an Expression (which is directly returned as the result of the call to create()). The built-in available simplification callables are in algebraic_properties
simplifications = []
instance_caching = True
classmethod create(*args, **kwargs)[source]

Instantiate while applying automatic simplifications

Instead of directly instantiating cls, it is recommended to use create(), which applies simplifications to the args and keyword arguments according to the simplifications class attribute, and returns an appropriate object (which may or may not be an instance of the original cls).

Two simplifications of particular importance are match_replace() and match_replace_binary() which apply rule-based simplifications.

The temporary_rules() context manager may be used to allow temporary modification of the automatic simplifications that create() uses, in particular the rules for match_replace() and match_replace_binary(). Inside the managed context, the simplifications class attribute may be modified and rules can be managed with add_rule() and del_rules().

classmethod add_rule(name, pattern, replacement, attr=None)[source]

Add an algebraic rule for create() to the class

Parameters:
  • name (str) – Name of the rule. This is used for debug logging to allow an analysis of which rules where applied when creating an expression. The name can be arbitrary, but it must be unique. Built-in rules have names 'Rxxx' where x is a digit
  • pattern (Pattern) – A pattern constructed by pattern_head() to match a ProtoExpr
  • replacement (callable) – callable that takes the wildcard names defined in pattern as keyword arguments and returns an evaluated expression.
  • attr (None or str) – Name of the class attribute to which to add the rule. If None, one of '_rules', '_binary_rules' is automatically chosen
Raises:
  • TypeError – if name is not a str or pattern is not a Pattern instance
  • ValueError – if pattern is not set up to match a ProtoExpr; if there there is already a rule with the same name; if replacement is not a callable or does not take all the wildcard names in pattern as arguments
  • AttributeError – If invalid attr

Note

The “automatic” rules added by this method are applied before expressions are instantiated (against a corresponding ProtoExpr). In contrast, apply_rules()/apply_rule() are applied to fully instantiated objects.

The temporary_rules() context manager may be used to create a context in which rules may be defined locally.

classmethod show_rules(*names, attr=None)[source]

Print algebraic rules used by create

Print a summary of the algebraic rules with the given names, or all rules if not names a given.

Parameters:
  • names (str) – Names of rules to show
  • attr (None or str) – Name of the class attribute from which to get the rules. Cf. add_rule().
Raises:

AttributeError – If invalid attr

classmethod del_rules(*names, attr=None)[source]

Delete algebraic rules used by create()

Remove the rules with the given names, or all rules if no names are given

Parameters:
  • names (str) – Names of rules to delete
  • attr (None or str) – Name of the class attribute from which to delete the rules. Cf. add_rule().
Raises:
classmethod rules(attr=None)[source]

Iterable of rule names used by create()

Parameters:attr (None or str) – Name of the class attribute to which to get the names. If None, one of '_rules', '_binary_rules' is automatically chosen
args

The tuple of positional arguments for the instantiation of the Expression

kwargs

The dictionary of keyword-only arguments for the instantiation of the Expression

minimal_kwargs

A “minimal” dictionary of keyword-only arguments, i.e. a subset of kwargs that may exclude default options

substitute(var_map)[source]

Substitute sub-expressions

Parameters:var_map (dict) – Dictionary with entries of the form {expr: substitution}
doit(classes=None, recursive=True, **kwargs)[source]

Rewrite (sub-)expressions in a more explicit form

Return a modified expression that is more explicit than the original expression. The definition of “more explicit” is decided by the relevant subclass, e.g. a Commutator is written out according to its definition.

Parameters:
  • classes (None or list) – an optional list of classes. If given, only (sub-)expressions that an instance of one of the classes in the list will be rewritten.
  • recursive (bool) – If True, also rewrite any sub-expressions of any rewritten expression. Note that doit() always recurses into sub-expressions of expressions not affected by it.
  • kwargs – Any remaining keyword arguments may be used by the doit() method of a particular expression.

Example

Consider the following expression:

>>> from sympy import IndexedBase
>>> i = IdxSym('i'); N = symbols('N')
>>> Asym, Csym = symbols('A, C', cls=IndexedBase)
>>> A = lambda i: OperatorSymbol(StrLabel(Asym[i]), hs=0)
>>> B = OperatorSymbol('B', hs=0)
>>> C = lambda i: OperatorSymbol(StrLabel(Csym[i]), hs=0)
>>> def show(expr):
...     print(unicode(expr, show_hs_label=False))
>>> expr = Sum(i, 1, 3)(Commutator(A(i), B) + C(i)) / N
>>> show(expr)
1/N (∑_{i=1}^{3} (Ĉ_i + [Â_i, B̂]))

Calling doit() without parameters rewrites both the indexed sum and the commutator:

>>> show(expr.doit())
1/N (Ĉ₁ + Ĉ₂ + Ĉ₃ + Â₁ B̂ + Â₂ B̂ + Â₃ B̂ - B̂ Â₁ - B̂ Â₂ - B̂ Â₃)

A non-recursive call only expands the sum, as it does not recurse into the expanded summands:

>>> show(expr.doit(recursive=False))
1/N (Ĉ₁ + Ĉ₂ + Ĉ₃ + [Â₁, B̂] + [Â₂, B̂] + [Â₃, B̂])

We can selectively expand only the sum or only the commutator:

>>> show(expr.doit(classes=[IndexedSum]))
1/N (Ĉ₁ + Ĉ₂ + Ĉ₃ + [Â₁, B̂] + [Â₂, B̂] + [Â₃, B̂])

>>> show(expr.doit(classes=[Commutator]))
1/N (∑_{i=1}^{3} (Ĉ_i - B̂ Â_i + Â_i B̂))

Also we can pass a keyword argument that expands the sum only to the 2nd term, as documented in Commutator.doit()

>>> show(expr.doit(classes=[IndexedSum], max_terms=2))
1/N (Ĉ₁ + Ĉ₂ + [Â₁, B̂] + [Â₂, B̂])
apply(func, *args, **kwargs)[source]

Apply func to expression.

Equivalent to func(self, *args, **kwargs). This method exists for easy chaining:

>>> A, B, C, D = (
...     OperatorSymbol(s, hs=1) for s in ('A', 'B', 'C', 'D'))
>>> expr = (
...     Commutator(A * B, C * D)
...     .apply(lambda expr: expr**2)
...     .apply(expand_commutators_leibniz, expand_expr=False)
...     .substitute({A: IdentityOperator}))
apply_rules(rules, recursive=True)[source]

Rebuild the expression while applying a list of rules

The rules are applied against the instantiated expression, and any sub-expressions if recursive is True. Rule application is best though of as a pattern-based substitution. This is different from the automatic rules that create() uses (see add_rule()), which are applied before expressions are instantiated.

Parameters:
  • rules (list or OrderedDict) – List of rules or dictionary mapping names to rules, where each rule is a tuple (Pattern, replacement callable), cf. apply_rule()
  • recursive (bool) – If true (default), apply rules to all arguments and keyword arguments of the expression. Otherwise, only the expression itself will be re-instantiated.

If rules is a dictionary, the keys (rules names) are used only for debug logging, to allow an analysis of which rules lead to the final form of an expression.

apply_rule(pattern, replacement, recursive=True)[source]

Apply a single rules to the expression

This is equivalent to apply_rules() with rules=[(pattern, replacement)]

Parameters:
  • pattern (Pattern) – A pattern containing one or more wildcards
  • replacement (callable) – A callable that takes the wildcard names in pattern as keyword arguments, and returns a replacement for any expression that pattern matches.

Example

Consider the following Heisenberg Hamiltonian:

>>> tls = SpinSpace(label='s', spin='1/2')
>>> i, j, n = symbols('i, j, n', cls=IdxSym)
>>> J = symbols('J', cls=sympy.IndexedBase)
>>> def Sig(i):
...     return OperatorSymbol(
...         StrLabel(sympy.Indexed('sigma', i)), hs=tls)
>>> H = - Sum(i, tls)(Sum(j, tls)(
...     J[i, j] * Sig(i) * Sig(j)))
>>> unicode(H)
'- (∑_{i,j ∈ ℌₛ} J_ij σ̂_i^(s) σ̂_j^(s))'

We can transform this into a classical Hamiltonian by replacing the operators with scalars:

>>> H_classical = H.apply_rule(
...     pattern(OperatorSymbol, wc('label', head=StrLabel)),
...     lambda label: label.expr * IdentityOperator)
>>> unicode(H_classical)
'- (∑_{i,j ∈ ℌₛ} J_ij σ_i σ_j)'
rebuild()[source]

Recursively re-instantiate the expression

This is generally used within a managed context such as extra_rules(), extra_binary_rules(), or no_rules().

free_symbols

Set of free SymPy symbols contained within the expression.

bound_symbols

Set of bound SymPy symbols in the expression

all_symbols

Combination of free_symbols and bound_symbols

__ne__(other)[source]

If it is well-defined (i.e. boolean), simply return the negation of self.__eq__(other) Otherwise return NotImplemented.

qnet.algebra.core.abstract_algebra.substitute(expr, var_map)[source]

Substitute symbols or (sub-)expressions with the given replacements and re-evalute the result

Parameters:
  • expr – The expression in which to perform the substitution
  • var_map (dict) – The substitution dictionary.
class qnet.algebra.core.abstract_algebra.Operation(*operands, **kwargs)[source]

Bases: qnet.algebra.core.abstract_algebra.Expression

Base class for “operations”

Operations are Expressions that act algebraically on other expressions (their “operands”).

Operations differ from more general Expressions by the convention that the arguments of the Operator are exactly the operands (which must be members of the algebra!) Any other parameters (non-operands) that may be required must be given as keyword-arguments.

operands

Tuple of operands of the operation

args

Alias for operands