Skip to content

Commit

Permalink
Create SupportsRealImagAsMethod, real, and imag
Browse files Browse the repository at this point in the history
Now ``sympy.core.numbers`` primitives are adequately supported (excepting general false positives; see #5).

Fixes #4.

Also renames ``SupportsNumeratorDenominatorProperties`` to ``SupportsNumeratorDenominator`` for consistency.
  • Loading branch information
posita committed Nov 15, 2021
1 parent 3600886 commit 4379ff6
Show file tree
Hide file tree
Showing 23 changed files with 487 additions and 243 deletions.
18 changes: 16 additions & 2 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,24 @@ repos:
rev: v4.0.1
hooks:
- id: end-of-file-fixer
exclude: ^docs/numerary-encumbered\.svg$
# See <https://docs.python.org/3/library/re.html#re.X>
exclude: |
(?x)^(
docs/(
numerary-encumbered\.svg|
perf_.*\.txt
)
)$
- id: mixed-line-ending
- id: trailing-whitespace
exclude: ^docs/numerary-encumbered\.svg$
# See <https://docs.python.org/3/library/re.html#re.X>
exclude: |
(?x)^(
docs/(
numerary-encumbered\.svg|
perf_.*\.txt
)
)$
- id: check-added-large-files
- id: check-case-conflict
- id: check-merge-conflict
Expand Down
34 changes: 25 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,7 @@ False
These offer significant performance improvements, especially where protocols define many methods.

``` python
--8<-- "docs/perf_supports_complex.out"
--8<-- "docs/perf_supports_complex.txt"
```

<details>
Expand Down Expand Up @@ -370,14 +370,30 @@ True
False
>>> from numerary.types import SupportsRealImag
>>> real_imag: SupportsRealImag = pants_on_fire # fails to detect the lie
>>> real_imag.real
Traceback (most recent call last):
...
AttributeError: 'One' object has no attribute 'real'

```

In this particular case, ``numerary`` provides us with a defensive mechanism.

``` python
>>> from numerary.types import SupportsRealImagMixedU, real, imag
>>> real_imag_defense: SupportsRealImagMixedU = pants_on_fire
>>> real(real_imag_defense)
1
>>> imag(real_imag)
0

```

#### Protocols loses fidelity during runtime checking

At runtime, protocols match *names*, not *signatures*.
For example, [``SupportsNumeratorDenominatorProperties``](https://posita.github.io/numerary/latest/numerary.types/#numerary.types.SupportsNumeratorDenominatorProperties)’s ``numerator`` and ``denominator`` *properties* will match [``sage.rings.integer.Integer``](https://doc.sagemath.org/html/en/reference/rings_standard/sage/rings/integer.html#sage.rings.integer.Integer)’s similarly named *[functions](https://trac.sagemath.org/ticket/28234)*.
In other words, ``isinstance(sage_integer, SupportsNumeratorDenominatorProperties)`` will return ``True``.
For example, [``SupportsNumeratorDenominator``](https://posita.github.io/numerary/latest/numerary.types/#numerary.types.SupportsNumeratorDenominator)’s ``numerator`` and ``denominator`` *properties* will match [``sage.rings.integer.Integer``](https://doc.sagemath.org/html/en/reference/rings_standard/sage/rings/integer.html#sage.rings.integer.Integer)’s similarly named *[functions](https://trac.sagemath.org/ticket/28234)*.
In other words, ``isinstance(sage_integer, SupportsNumeratorDenominator)`` will return ``True``.
Further, if the short-circuiting approach is used, because ``sage.rings.integer.Integer`` registers itself with the numeric tower, this *may*[^5] not be caught by Mypy.

[^5]:
Expand All @@ -394,10 +410,10 @@ Further, if the short-circuiting approach is used, because ``sage.rings.integer.
... def denominator(self) -> int:
... return self._denominator

>>> from numerary.types import SupportsNumeratorDenominatorProperties
>>> frac: SupportsNumeratorDenominatorProperties = Fraction(29, 3) # no typing error
>>> sage_rational1: SupportsNumeratorDenominatorProperties = SageLikeRational(29, 3) # type: ignore [assignment] # Mypy catches this
>>> isinstance(sage_rational1, SupportsNumeratorDenominatorProperties) # isinstance does not
>>> from numerary.types import SupportsNumeratorDenominator
>>> frac: SupportsNumeratorDenominator = Fraction(29, 3) # no typing error
>>> sage_rational1: SupportsNumeratorDenominator = SageLikeRational(29, 3) # type: ignore [assignment] # Mypy catches this
>>> isinstance(sage_rational1, SupportsNumeratorDenominator) # isinstance does not
True
>>> sage_rational1.numerator
<...method...numerator...>
Expand Down Expand Up @@ -428,11 +444,11 @@ These accommodate rational implementations like Sage’s that are mostly complia

``` python
SupportsNumeratorDenominatorMixedU = Union[
SupportsNumeratorDenominatorProperties,
SupportsNumeratorDenominator,
SupportsNumeratorDenominatorMethods,
]
SupportsNumeratorDenominatorMixedT = (
SupportsNumeratorDenominatorProperties,
SupportsNumeratorDenominator,
SupportsNumeratorDenominatorMethods,
)
```
Expand Down
12 changes: 12 additions & 0 deletions docs/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
all : \
perf_rational_baseline.txt \
perf_rational_big_protocol.txt \
perf_rational_protocol.txt \
perf_supports_complex.txt

perf_%.txt : perf_%.ipy Makefile ../numerary/types.py
ipython --no-banner --quick --LoggingMagics.quiet=True perf_$*.ipy \
| cut >perf_$*.txt -f 2 -d $$'\a'

../numerary-encumbered.svg : Makefile
curl --output $@ 'https://img.shields.io/badge/%F0%9F%98%A3-%F0%9D%9A%97%F0%9D%9A%9E%F0%9D%9A%96%F0%9D%9A%8E%F0%9D%9A%9B%F0%9D%9A%8A%F0%9D%9A%9B%F0%9D%9A%A2--encumbered-yellowgreen'
3 changes: 3 additions & 0 deletions docs/notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@

## [0.1.2](https://github.com/posita/numerary/releases/tag/v0.1.2)

* Splits [``SupportsRealImagAsMethod``][numerary.types.SupportsRealImagAsMethod] out of [``SupportsRealImag``][numerary.types.SupportsRealImag] and provides the [``real``][numerary.types.real] and [``imag``][numerary.types.imag] helper functions for better support of ``sympy.core.numbers`` primitives.
* Renames ``SupportsNumeratorDenominatorProperties`` to [``SupportsNumeratorDenominator``][numerary.types.SupportsNumeratorDenominator] to mirror [``SupportsRealImag``][numerary.types.SupportsRealImag] and reflect that it captures the numeric tower interface.

## [0.1.1](https://github.com/posita/numerary/releases/tag/v0.1.1)

* Removes obsoleted ``…SCT`` aliases.
Expand Down
12 changes: 9 additions & 3 deletions docs/numerary.types.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ from numerary.bt import beartype # will resolve to the identity decorator if be
selection:
members:
- "CachingProtocolMeta"
- "RationalLikeProperties"
- "RationalLike"
- "RationalLikeMethods"
- "SupportsAbs"
- "SupportsComplex"
Expand All @@ -67,16 +67,19 @@ from numerary.bt import beartype # will resolve to the identity decorator if be
- "SupportsRound"
- "SupportsConjugate"
- "SupportsRealImag"
- "SupportsRealImagAsMethod"
- "SupportsTrunc"
- "SupportsFloorCeil"
- "SupportsDivmod"
- "SupportsNumeratorDenominatorProperties"
- "SupportsNumeratorDenominator"
- "SupportsNumeratorDenominatorMethods"
- "SupportsComplexOps"
- "SupportsComplexPow"
- "SupportsRealOps"
- "SupportsIntegralOps"
- "SupportsIntegralPow"
- "real"
- "imag"
- "trunc"
- "floor"
- "ceil"
Expand All @@ -103,10 +106,13 @@ from numerary.bt import beartype # will resolve to the identity decorator if be
- "_SupportsRound"
- "_SupportsConjugate"
- "_SupportsRealImag"
- "_SupportsRealImagAsMethod"
- "SupportsRealImagMixedT"
- "SupportsRealImagMixedU"
- "_SupportsTrunc"
- "_SupportsFloorCeil"
- "_SupportsDivmod"
- "_SupportsNumeratorDenominatorProperties"
- "_SupportsNumeratorDenominator"
- "_SupportsNumeratorDenominatorMethods"
- "SupportsNumeratorDenominatorMixedT"
- "SupportsNumeratorDenominatorMixedU"
Expand Down
6 changes: 0 additions & 6 deletions docs/perf_rational_baseline.out

This file was deleted.

6 changes: 6 additions & 0 deletions docs/perf_rational_baseline.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
%timeit isinstance(builtins.int(1), Rational)
356 ns ± 15.2 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
%timeit isinstance(fractions.Fraction(2), Rational)
350 ns ± 14.9 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
%timeit isinstance(builtins.float(3.0), Rational)
359 ns ± 3.21 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
%timeit isinstance(builtins.int(1), SupportsLotsOfNumberStuff)
132 µs ± 1.15 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
136 µs ± 3.3 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
%timeit isinstance(fractions.Fraction(2), SupportsLotsOfNumberStuff)
139 µs ± 2.01 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
154 µs ± 5.94 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
%timeit isinstance(builtins.float(3.0), SupportsLotsOfNumberStuff)
131 µs ± 1.13 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
149 µs ± 8.33 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
%timeit isinstance(builtins.int(1), SupportsNumeratorDenominator)
12.3 µs ± 95.6 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
13 µs ± 270 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
%timeit isinstance(fractions.Fraction(2), SupportsNumeratorDenominator)
12.4 µs ± 91.6 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
14 µs ± 761 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
%timeit isinstance(builtins.float(3.0), SupportsNumeratorDenominator)
12.5 µs ± 167 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
15.4 µs ± 323 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
21 changes: 11 additions & 10 deletions docs/perf_supports_complex.out → docs/perf_supports_complex.txt
Original file line number Diff line number Diff line change
@@ -1,24 +1,25 @@
%timeit isinstance(builtins.int(1), _SupportsComplexOps)
11.8 µs ± 314 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
11.9 µs ± 279 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
%timeit isinstance(builtins.int(1), SupportsComplexOps)
307 ns ± 2.97 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
312 ns ± 1.56 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

%timeit isinstance(builtins.float(2.0), _SupportsComplexOps)
11.6 µs ± 104 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
13.1 µs ± 385 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
%timeit isinstance(builtins.float(2.0), SupportsComplexOps)
312 ns ± 6.29 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
380 ns ± 23 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

%timeit isinstance(decimal.Decimal(3), _SupportsComplexOps)
11.7 µs ± 68.1 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
13.3 µs ± 897 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
%timeit isinstance(decimal.Decimal(3), SupportsComplexOps)
306 ns ± 3.7 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
322 ns ± 12.4 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

%timeit isinstance(fractions.Fraction(4), _SupportsComplexOps)
11.7 µs ± 100 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
11.9 µs ± 178 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
%timeit isinstance(fractions.Fraction(4), SupportsComplexOps)
308 ns ± 4.24 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
314 ns ± 1.54 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

%timeit isinstance(sympy.core.numbers.Integer(5), _SupportsComplexOps)
11.9 µs ± 251 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
12 µs ± 110 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
%timeit isinstance(sympy.core.numbers.Integer(5), SupportsComplexOps)
306 ns ± 1.5 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
315 ns ± 4.39 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

6 changes: 3 additions & 3 deletions docs/whytho.md
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,7 @@ Let’s see how they perform.
First, let’s get a baseline.

``` python
--8<-- "docs/perf_rational_baseline.out"
--8<-- "docs/perf_rational_baseline.txt"
```

<details>
Expand All @@ -233,7 +233,7 @@ First, let’s get a baseline.
Now let’s compare that with our two-property protocol.

``` python
--8<-- "docs/perf_rational_protocol.out"
--8<-- "docs/perf_rational_protocol.txt"
```

<details>
Expand All @@ -249,7 +249,7 @@ And that’s just with a two-property protocol.
How much worse would it be if we had enumerated all the dunder methods? 😰

``` python
--8<-- "docs/perf_rational_big_protocol.out"
--8<-- "docs/perf_rational_big_protocol.txt"
```

<details>
Expand Down
Loading

0 comments on commit 4379ff6

Please sign in to comment.