Skip to content

Commit

Permalink
Merge pull request #878 from qutech/fix/hatch_test
Browse files Browse the repository at this point in the history
Fix tests and make hatch test usable
  • Loading branch information
terrorfisch authored Jan 3, 2025
2 parents fb33068 + 860b331 commit 6243493
Show file tree
Hide file tree
Showing 7 changed files with 75 additions and 12 deletions.
19 changes: 15 additions & 4 deletions .github/workflows/pythontest.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -55,18 +55,29 @@ jobs:
run: |
coverage run -m pytest --junit-xml pytest.xml
- name: Generate valid name
run: |
numpy_version="${{ matrix.numpy-version }}"
if [[ $numpy_version == *"<2"* ]]; then
numpy_version="1"
else
numpy_version="2"
fi
MATRIX_NAME="python-${{ matrix.python-version }}-numpy-"$numpy_version
echo "MATRIX_NAME=$MATRIX_NAME" >> $GITHUB_ENV
- name: Upload coverage data to coveralls.io
run: coveralls --service=github
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
COVERALLS_FLAG_NAME: python-${{ matrix.python-version }}-${{ matrix.time-type }}
COVERALLS_FLAG_NAME: ${{ env.MATRIX_NAME }}
COVERALLS_PARALLEL: true

- name: Upload Test Results
if: always()
uses: actions/upload-artifact@v2
uses: actions/upload-artifact@v4
with:
name: Unit Test Results ( ${{ matrix.python-version }}-${{ matrix.time-type }} )
name: Unit Test Results ( ${{ env.MATRIX_NAME }} )
path: |
pytest.xml
Expand All @@ -88,7 +99,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Upload
uses: actions/upload-artifact@v2
uses: actions/upload-artifact@v4
with:
name: Event File
path: ${{ github.event_path }}
6 changes: 6 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,12 @@ include = [
"/qupulse",
]

[tool.hatch.envs.default]
features = ["default"]

[tool.hatch.envs.hatch-test]
template = "default"

[tool.hatch.envs.docs]
installer = "uv"
dependencies = [
Expand Down
41 changes: 38 additions & 3 deletions qupulse/expressions/sympy.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@
This module defines the class Expression to represent mathematical expression as well as
corresponding exception classes.
"""
import numbers
import operator
from typing import Any, Dict, Union, Sequence, Callable, TypeVar, Type, Mapping
from typing import Any, Dict, Union, Sequence, Callable, TypeVar, Type, Mapping, Optional
from numbers import Number
import warnings
import functools
Expand Down Expand Up @@ -373,12 +374,28 @@ def __le__(self, other: Union['ExpressionScalar', Number, sympy.Expr]) -> Union[
return None if isinstance(result, sympy.Rel) else bool(result)

def __eq__(self, other: Union['ExpressionScalar', Number, sympy.Expr]) -> bool:
"""Enable comparisons with Numbers"""
# the consistency of __hash__ and __eq__ relies on the consistency of the numeric types' behavior.
# The types deal with equal floats, integers and rationals for us.

num_val = self._try_to_numeric()
if num_val is not None:
return other == num_val
elif isinstance(other, ALLOWED_NUMERIC_SCALAR_TYPES):
# self is non-numeric but other is
# this is a short-cut to avoid an unnecessary sympify call
return False

rhs = self._sympify(other)
lhs = self._sympified_expression

# sympy's __eq__ checks for structural equality to be consistent regarding __hash__ so we do that too
# see https://github.com/sympy/sympy/issues/18054#issuecomment-566198899
return self._sympified_expression == self._sympify(other)
return lhs == rhs

def __hash__(self) -> int:
num_val = self._try_to_numeric()
if num_val is not None:
return hash(num_val)
return hash(self._sympified_expression)

def __add__(self, other: Union['ExpressionScalar', Number, sympy.Expr]) -> 'ExpressionScalar':
Expand Down Expand Up @@ -420,6 +437,24 @@ def __pos__(self):
def _sympy_(self):
return self._sympified_expression

def _try_to_numeric(self) -> Optional[numbers.Number]:
"""Returns a numeric representation if the expression has no free variables. The difference to __float__ is
the proper treatment of integers and rationals."""
if self._variables:
return None
if isinstance(self._original_expression, ALLOWED_NUMERIC_SCALAR_TYPES):
return self._original_expression
expr = self._sympified_expression
if isinstance(expr, bool):
# sympify can return bool
return expr
if expr.is_Float:
return float(expr)
if expr.is_Integer:
return int(expr)
else:
return TimeType.from_sympy(expr)

@property
def original_expression(self) -> Union[str, Number]:
if self._original_expression is None:
Expand Down
3 changes: 3 additions & 0 deletions qupulse/utils/sympy.py
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,9 @@ def eval(cls, arg) -> Optional[sympy.Integer]:
if hasattr(arg, '__len__'):
return sympy.Integer(len(arg))

def _lambdacode(self, printer) -> str:
return f'len({printer._print(self.args[0])})'

is_Integer = True
Len.__name__ = 'len'

Expand Down
10 changes: 9 additions & 1 deletion qupulse/utils/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,14 @@ def from_fraction(cls, numerator: int, denominator: int) -> 'TimeType':
"""
return cls(numerator, denominator)

@classmethod
def from_sympy(cls, num: sympy.Expr):
if num.is_Float:
return cls.from_float(float(num))
else:
p, q = num.as_numer_denom()
return cls.from_fraction(int(p), int(q))

def __repr__(self):
return f'TimeType({self._value.numerator}, {self._value.denominator})'

Expand All @@ -306,7 +314,7 @@ def __float__(self):
float: TimeType.from_float,
TimeType._InternalType: TimeType,
fractions.Fraction: TimeType,
sympy.Rational: lambda q: TimeType.from_fraction(q.p, q.q),
sympy.Rational: lambda q: TimeType.from_fraction(int(q.p), int(q.q)),
TimeType: lambda x: x
}

Expand Down
6 changes: 3 additions & 3 deletions tests/_program/transformation_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ def test_call(self):

data['ignored'] = np.arange(116., 120.)

transformed = trafo(np.full(4, np.NaN), data)
transformed = trafo(np.full(4, np.nan), data)

expected = {'transformed_a': data['a'] - data['b'],
'transformed_b': np.sum(raw_data, axis=0),
Expand All @@ -140,15 +140,15 @@ def test_call(self):
data.pop('c')
with self.assertRaisesRegex(KeyError, 'Invalid input channels'):

trafo(np.full(4, np.NaN), data)
trafo(np.full(4, np.nan), data)

in_chs = ('a', 'b', 'c')
out_chs = ('a', 'b', 'c')
matrix = np.eye(3)
trafo = LinearTransformation(matrix, in_chs, out_chs)

data_in = {'ignored': np.arange(116., 120.)}
transformed = trafo(np.full(4, np.NaN), data_in)
transformed = trafo(np.full(4, np.nan), data_in)
np.testing.assert_equal(transformed, data_in)
self.assertIs(data_in['ignored'], transformed['ignored'])

Expand Down
2 changes: 1 addition & 1 deletion tests/expressions/expression_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -416,7 +416,7 @@ def test_sympy_math(self):

def test_is_nan(self):
self.assertTrue(ExpressionScalar('nan').is_nan())
self.assertTrue(ExpressionScalar('0./0.').is_nan())
self.assertTrue((ExpressionScalar(float('inf')) / ExpressionScalar(float('inf'))).is_nan())

self.assertFalse(ExpressionScalar(456).is_nan())

Expand Down

0 comments on commit 6243493

Please sign in to comment.