-
Notifications
You must be signed in to change notification settings - Fork 31
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #750 from qutech/issues/572_expression_cleanup
Cleanup expression usage
- Loading branch information
Showing
15 changed files
with
806 additions
and
538 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
Promote ``qupulse.expression`` to a subpackage and create ``qupulse.expression.protocol`` with protocol classes that define the expression interface that is supposed to be used by qupulse. | ||
The ```sympy`` based implementation is moved to ``qupulse.expressions.sympy`` and imported in ``qupulse.expressions``. | ||
|
||
The intended use is to be able to use less powerful but faster implementations of the ``Expression`` protocol where appropriate. | ||
In this first iteration, qupulse still relies on internals of the ``sympy`` based implementation in many places which is to be removed in the future. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
"""This subpackage contains qupulse's expression logic. The submodule :py:mod:`.expressions.protocol` defines the :py:class:`typing.Protocol` | ||
that expression functionality providers must implement. This allows to substitute the powerful and expressive but slow | ||
default implementation with a faster less expressive backend. | ||
The default implementation is in :py:mod:`.expressions.sympy`. | ||
There is are wrapper classes for finding non-protocol uses of expression in :py:mod:`.expressions.wrapper`. Define | ||
``QUPULSE_EXPRESSION_WRAPPER`` environment variable when running python to wrap all expression usages. | ||
""" | ||
|
||
from typing import Type, TypeVar | ||
from numbers import Real | ||
import os | ||
|
||
import numpy as np | ||
import sympy as sp | ||
|
||
from . import sympy, protocol, wrapper | ||
|
||
|
||
__all__ = ["Expression", "ExpressionVector", "ExpressionScalar", | ||
"NonNumericEvaluation", "ExpressionVariableMissingException"] | ||
|
||
|
||
Expression: Type[protocol.Expression] = sympy.Expression | ||
ExpressionScalar: Type[protocol.ExpressionScalar] = sympy.ExpressionScalar | ||
ExpressionVector: Type[protocol.ExpressionVector] = sympy.ExpressionVector | ||
|
||
|
||
if os.environ.get('QUPULSE_EXPRESSION_WRAPPER', None): # pragma: no cover | ||
Expression, ExpressionScalar, ExpressionVector = wrapper.make_wrappers(sympy.Expression, | ||
sympy.ExpressionScalar, | ||
sympy.ExpressionVector) | ||
|
||
|
||
ExpressionLike = TypeVar('ExpressionLike', str, Real, sp.Expr, ExpressionScalar) | ||
|
||
|
||
class ExpressionVariableMissingException(Exception): | ||
"""An exception indicating that a variable value was not provided during expression evaluation. | ||
See also: | ||
qupulse.expressions.Expression | ||
""" | ||
|
||
def __init__(self, variable: str, expression: Expression) -> None: | ||
super().__init__() | ||
self.variable = variable | ||
self.expression = expression | ||
|
||
def __str__(self) -> str: | ||
return f"Could not evaluate <{self.expression}>: A value for variable <{self.variable}> is missing!" | ||
|
||
|
||
class NonNumericEvaluation(Exception): | ||
"""An exception that is raised if the result of evaluate_numeric is not a number. | ||
See also: | ||
qupulse.expressions.Expression.evaluate_numeric | ||
""" | ||
|
||
def __init__(self, expression: Expression, non_numeric_result, call_arguments): | ||
self.expression = expression | ||
self.non_numeric_result = non_numeric_result | ||
self.call_arguments = call_arguments | ||
|
||
def __str__(self) -> str: | ||
if isinstance(self.non_numeric_result, np.ndarray): | ||
dtype = self.non_numeric_result.dtype | ||
|
||
if dtype == np.dtype('O'): | ||
dtypes = set(map(type, self.non_numeric_result.flat)) | ||
return f"The result of evaluate_numeric is an array with the types {dtypes} which is not purely numeric" | ||
else: | ||
dtype = type(self.non_numeric_result) | ||
return f"The result of evaluate_numeric is of type {dtype} which is not a number" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
"""This module contains the interface / protocol descriptions of ``Expression``, ``ExpressionScalar`` and | ||
``ExpressionVector``.""" | ||
|
||
from typing import Mapping, Union, Sequence, Hashable, Any, Protocol | ||
from numbers import Real | ||
|
||
import numpy as np | ||
|
||
|
||
class Ordered(Protocol): | ||
def __lt__(self, other): | ||
pass | ||
|
||
def __le__(self, other): | ||
pass | ||
|
||
def __gt__(self, other): | ||
pass | ||
|
||
def __ge__(self, other): | ||
pass | ||
|
||
|
||
class Scalar(Protocol): | ||
def __add__(self, other): | ||
pass | ||
|
||
def __sub__(self, other): | ||
pass | ||
|
||
def __mul__(self, other): | ||
pass | ||
|
||
def __truediv__(self, other): | ||
pass | ||
|
||
def __floordiv__(self, other): | ||
pass | ||
|
||
def __ceil__(self): | ||
pass | ||
|
||
def __floor__(self): | ||
pass | ||
|
||
def __float__(self): | ||
pass | ||
|
||
def __int__(self): | ||
pass | ||
|
||
def __abs__(self): | ||
pass | ||
|
||
|
||
class Expression(Hashable, Protocol): | ||
"""This protocol defines how Expressions are allowed to be used in qupulse.""" | ||
|
||
def evaluate_in_scope(self, scope: Mapping) -> Union[Real, np.ndarray]: | ||
"""Evaluate the expression by taking the variables from the given scope (typically of type Scope, but it can be | ||
any mapping.) | ||
Args: | ||
scope: | ||
Returns: | ||
""" | ||
|
||
def evaluate_symbolic(self, substitutions: Mapping[str, Any]) -> 'Expression': | ||
"""Substitute a part of the expression for another""" | ||
|
||
def evaluate_time_dependent(self, scope: Mapping) -> Union['Expression', Real, np.ndarray]: | ||
"""Evaluate to a time dependent expression or a constant.""" | ||
|
||
@property | ||
def variables(self) -> Sequence[str]: | ||
""" Get all free variables in the expression. | ||
Returns: | ||
A collection of all free variables occurring in the expression. | ||
""" | ||
raise NotImplementedError() | ||
|
||
@classmethod | ||
def make(cls, | ||
expression_or_dict, | ||
numpy_evaluation=None) -> 'Expression': | ||
"""Backward compatible expression generation to allow creation from dict.""" | ||
raise NotImplementedError() | ||
|
||
@property | ||
def underlying_expression(self) -> Any: | ||
"""Return some internal unspecified representation""" | ||
raise NotImplementedError() | ||
|
||
def get_serialization_data(self): | ||
raise NotImplementedError() | ||
|
||
|
||
class ExpressionScalar(Expression, Scalar, Ordered, Protocol): | ||
pass | ||
|
||
|
||
class ExpressionVector(Expression, Protocol): | ||
pass |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.