daatypes 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- daatypes-0.1.0/.gitignore +218 -0
- daatypes-0.1.0/LICENSE +21 -0
- daatypes-0.1.0/PKG-INFO +22 -0
- daatypes-0.1.0/idfk.py +415 -0
- daatypes-0.1.0/pyproject.toml +38 -0
- daatypes-0.1.0/readme.md +1 -0
- daatypes-0.1.0/src/daatypes/__init__.py +75 -0
- daatypes-0.1.0/src/daatypes/ball.py +22 -0
- daatypes-0.1.0/src/daatypes/cf.py +79 -0
- daatypes-0.1.0/src/daatypes/comp.py +49 -0
- daatypes-0.1.0/src/daatypes/fixed.py +243 -0
- daatypes-0.1.0/src/daatypes/float.py +232 -0
- daatypes-0.1.0/src/daatypes/int.py +4 -0
- daatypes-0.1.0/src/daatypes/interval.py +5 -0
- daatypes-0.1.0/src/daatypes/monzo.py +286 -0
- daatypes-0.1.0/src/daatypes/octo.py +29 -0
- daatypes-0.1.0/src/daatypes/polar.py +10 -0
- daatypes-0.1.0/src/daatypes/qint.py +4 -0
- daatypes-0.1.0/src/daatypes/quat.py +60 -0
- daatypes-0.1.0/src/daatypes/real.py +16 -0
- daatypes-0.1.0/src/daatypes/uint.py +4 -0
- daatypes-0.1.0/src/daatypes/uqint.py +4 -0
- daatypes-0.1.0/src/daatypes/vector.py +33 -0
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
# Byte-compiled / optimized / DLL files
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[codz]
|
|
4
|
+
*$py.class
|
|
5
|
+
|
|
6
|
+
# C extensions
|
|
7
|
+
*.so
|
|
8
|
+
|
|
9
|
+
# Distribution / packaging
|
|
10
|
+
.Python
|
|
11
|
+
build/
|
|
12
|
+
develop-eggs/
|
|
13
|
+
dist/
|
|
14
|
+
downloads/
|
|
15
|
+
eggs/
|
|
16
|
+
.eggs/
|
|
17
|
+
lib/
|
|
18
|
+
lib64/
|
|
19
|
+
parts/
|
|
20
|
+
sdist/
|
|
21
|
+
var/
|
|
22
|
+
wheels/
|
|
23
|
+
share/python-wheels/
|
|
24
|
+
*.egg-info/
|
|
25
|
+
.installed.cfg
|
|
26
|
+
*.egg
|
|
27
|
+
MANIFEST
|
|
28
|
+
|
|
29
|
+
# PyInstaller
|
|
30
|
+
# Usually these files are written by a python script from a template
|
|
31
|
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
|
32
|
+
*.manifest
|
|
33
|
+
*.spec
|
|
34
|
+
|
|
35
|
+
# Installer logs
|
|
36
|
+
pip-log.txt
|
|
37
|
+
pip-delete-this-directory.txt
|
|
38
|
+
|
|
39
|
+
# Unit test / coverage reports
|
|
40
|
+
htmlcov/
|
|
41
|
+
.tox/
|
|
42
|
+
.nox/
|
|
43
|
+
.coverage
|
|
44
|
+
.coverage.*
|
|
45
|
+
.cache
|
|
46
|
+
nosetests.xml
|
|
47
|
+
coverage.xml
|
|
48
|
+
*.cover
|
|
49
|
+
*.py.cover
|
|
50
|
+
.hypothesis/
|
|
51
|
+
.pytest_cache/
|
|
52
|
+
cover/
|
|
53
|
+
|
|
54
|
+
# Translations
|
|
55
|
+
*.mo
|
|
56
|
+
*.pot
|
|
57
|
+
|
|
58
|
+
# Django stuff:
|
|
59
|
+
*.log
|
|
60
|
+
local_settings.py
|
|
61
|
+
db.sqlite3
|
|
62
|
+
db.sqlite3-journal
|
|
63
|
+
|
|
64
|
+
# Flask stuff:
|
|
65
|
+
instance/
|
|
66
|
+
.webassets-cache
|
|
67
|
+
|
|
68
|
+
# Scrapy stuff:
|
|
69
|
+
.scrapy
|
|
70
|
+
|
|
71
|
+
# Sphinx documentation
|
|
72
|
+
docs/_build/
|
|
73
|
+
|
|
74
|
+
# PyBuilder
|
|
75
|
+
.pybuilder/
|
|
76
|
+
target/
|
|
77
|
+
|
|
78
|
+
# Jupyter Notebook
|
|
79
|
+
.ipynb_checkpoints
|
|
80
|
+
|
|
81
|
+
# IPython
|
|
82
|
+
profile_default/
|
|
83
|
+
ipython_config.py
|
|
84
|
+
|
|
85
|
+
# pyenv
|
|
86
|
+
# For a library or package, you might want to ignore these files since the code is
|
|
87
|
+
# intended to run in multiple environments; otherwise, check them in:
|
|
88
|
+
# .python-version
|
|
89
|
+
|
|
90
|
+
# pipenv
|
|
91
|
+
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
|
92
|
+
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
|
93
|
+
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
|
94
|
+
# install all needed dependencies.
|
|
95
|
+
# Pipfile.lock
|
|
96
|
+
|
|
97
|
+
# UV
|
|
98
|
+
# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
|
|
99
|
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
100
|
+
# commonly ignored for libraries.
|
|
101
|
+
# uv.lock
|
|
102
|
+
|
|
103
|
+
# poetry
|
|
104
|
+
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
|
105
|
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
106
|
+
# commonly ignored for libraries.
|
|
107
|
+
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
|
108
|
+
# poetry.lock
|
|
109
|
+
# poetry.toml
|
|
110
|
+
|
|
111
|
+
# pdm
|
|
112
|
+
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
|
113
|
+
# pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python.
|
|
114
|
+
# https://pdm-project.org/en/latest/usage/project/#working-with-version-control
|
|
115
|
+
# pdm.lock
|
|
116
|
+
# pdm.toml
|
|
117
|
+
.pdm-python
|
|
118
|
+
.pdm-build/
|
|
119
|
+
|
|
120
|
+
# pixi
|
|
121
|
+
# Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control.
|
|
122
|
+
# pixi.lock
|
|
123
|
+
# Pixi creates a virtual environment in the .pixi directory, just like venv module creates one
|
|
124
|
+
# in the .venv directory. It is recommended not to include this directory in version control.
|
|
125
|
+
.pixi
|
|
126
|
+
|
|
127
|
+
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
|
128
|
+
__pypackages__/
|
|
129
|
+
|
|
130
|
+
# Celery stuff
|
|
131
|
+
celerybeat-schedule
|
|
132
|
+
celerybeat.pid
|
|
133
|
+
|
|
134
|
+
# Redis
|
|
135
|
+
*.rdb
|
|
136
|
+
*.aof
|
|
137
|
+
*.pid
|
|
138
|
+
|
|
139
|
+
# RabbitMQ
|
|
140
|
+
mnesia/
|
|
141
|
+
rabbitmq/
|
|
142
|
+
rabbitmq-data/
|
|
143
|
+
|
|
144
|
+
# ActiveMQ
|
|
145
|
+
activemq-data/
|
|
146
|
+
|
|
147
|
+
# SageMath parsed files
|
|
148
|
+
*.sage.py
|
|
149
|
+
|
|
150
|
+
# Environments
|
|
151
|
+
.env
|
|
152
|
+
.envrc
|
|
153
|
+
.venv
|
|
154
|
+
env/
|
|
155
|
+
venv/
|
|
156
|
+
ENV/
|
|
157
|
+
env.bak/
|
|
158
|
+
venv.bak/
|
|
159
|
+
|
|
160
|
+
# Spyder project settings
|
|
161
|
+
.spyderproject
|
|
162
|
+
.spyproject
|
|
163
|
+
|
|
164
|
+
# Rope project settings
|
|
165
|
+
.ropeproject
|
|
166
|
+
|
|
167
|
+
# mkdocs documentation
|
|
168
|
+
/site
|
|
169
|
+
|
|
170
|
+
# mypy
|
|
171
|
+
.mypy_cache/
|
|
172
|
+
.dmypy.json
|
|
173
|
+
dmypy.json
|
|
174
|
+
|
|
175
|
+
# Pyre type checker
|
|
176
|
+
.pyre/
|
|
177
|
+
|
|
178
|
+
# pytype static type analyzer
|
|
179
|
+
.pytype/
|
|
180
|
+
|
|
181
|
+
# Cython debug symbols
|
|
182
|
+
cython_debug/
|
|
183
|
+
|
|
184
|
+
# PyCharm
|
|
185
|
+
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
|
186
|
+
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
|
187
|
+
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
|
188
|
+
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
|
189
|
+
# .idea/
|
|
190
|
+
|
|
191
|
+
# Abstra
|
|
192
|
+
# Abstra is an AI-powered process automation framework.
|
|
193
|
+
# Ignore directories containing user credentials, local state, and settings.
|
|
194
|
+
# Learn more at https://abstra.io/docs
|
|
195
|
+
.abstra/
|
|
196
|
+
|
|
197
|
+
# Visual Studio Code
|
|
198
|
+
# Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore
|
|
199
|
+
# that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
|
|
200
|
+
# and can be added to the global gitignore or merged into this file. However, if you prefer,
|
|
201
|
+
# you could uncomment the following to ignore the entire vscode folder
|
|
202
|
+
# .vscode/
|
|
203
|
+
# Temporary file for partial code execution
|
|
204
|
+
tempCodeRunnerFile.py
|
|
205
|
+
|
|
206
|
+
# Ruff stuff:
|
|
207
|
+
.ruff_cache/
|
|
208
|
+
|
|
209
|
+
# PyPI configuration file
|
|
210
|
+
.pypirc
|
|
211
|
+
|
|
212
|
+
# Marimo
|
|
213
|
+
marimo/_static/
|
|
214
|
+
marimo/_lsp/
|
|
215
|
+
__marimo__/
|
|
216
|
+
|
|
217
|
+
# Streamlit
|
|
218
|
+
.streamlit/secrets.toml
|
daatypes-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Lalremruata Chongmang
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
daatypes-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: daatypes
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: i simulate datatypes that i find useful
|
|
5
|
+
Project-URL: Homepage, https://github.com/deftasparagusanaconda/daatypes
|
|
6
|
+
Project-URL: Repository, https://github.com/deftasparagusanaconda/daatypes
|
|
7
|
+
Project-URL: Issues, https://github.com/deftasparagusanaconda/daatypes/issues
|
|
8
|
+
Author: deftasparagusanaconda
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: math,numerical
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Intended Audience :: Science/Research
|
|
15
|
+
Classifier: License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
18
|
+
Classifier: Topic :: Scientific/Engineering :: Mathematics
|
|
19
|
+
Requires-Python: >=3.10
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
|
|
22
|
+
a collection of datatypes (or, rather, simulations thereof) that i find useful
|
daatypes-0.1.0/idfk.py
ADDED
|
@@ -0,0 +1,415 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
import math, collections, numbers
|
|
3
|
+
from daacorations import pretty_repr
|
|
4
|
+
from typing import Any, Literal
|
|
5
|
+
from numbers import Integral, Rational
|
|
6
|
+
from functools import cached_property
|
|
7
|
+
from itertools import pairwise, cycle
|
|
8
|
+
from fractions import Fraction
|
|
9
|
+
from abc import abstractmethod
|
|
10
|
+
from frozendefaultdict import frozendefaultdict
|
|
11
|
+
from collections import defaultdict
|
|
12
|
+
from collections.abc import Hashable, MutableMapping
|
|
13
|
+
from frozendict import frozendict
|
|
14
|
+
from frozendefaultdict import HashableMapping
|
|
15
|
+
|
|
16
|
+
# numbers ----------------------------------------------------------------------
|
|
17
|
+
|
|
18
|
+
class Natural(numbers.Integral):
|
|
19
|
+
'a non-negative integer'
|
|
20
|
+
|
|
21
|
+
def __init__(self, value: Integral):
|
|
22
|
+
if value < 0 or not isinstance(value, Integral):
|
|
23
|
+
raise ValueError('must be a non-negative integer')
|
|
24
|
+
self.value: Integral = value
|
|
25
|
+
|
|
26
|
+
def __add__(self, other) -> Natural:
|
|
27
|
+
return type(self)(self.value + other.value)
|
|
28
|
+
def __sub__(self, other) -> Natural:
|
|
29
|
+
if other.value > self.value:
|
|
30
|
+
raise OutOfDomainError('')
|
|
31
|
+
return type(self)(self.value - other.value)
|
|
32
|
+
def __mul__(self, other) -> Natural:
|
|
33
|
+
return type(self)(self.value * other.value)
|
|
34
|
+
def __truediv__(self, other) -> Natural | Rational:
|
|
35
|
+
if self.value % other.value == 0:
|
|
36
|
+
return type(self)(self.value // other.value)
|
|
37
|
+
else:
|
|
38
|
+
return Fraction(self.value, other.value)
|
|
39
|
+
def __pow__(self, other) -> Natural:
|
|
40
|
+
if self.value == 0 and other.value == 0:
|
|
41
|
+
raise ArithmeticError('0 ** 0 is not defined')
|
|
42
|
+
return Natural(self.value ** other.value)
|
|
43
|
+
def __radd__(self, other) -> Natural:
|
|
44
|
+
return Natural(self.value + other.value)
|
|
45
|
+
def __rsub__(self, other) -> Natural:
|
|
46
|
+
return Natural(self.value + other.value)
|
|
47
|
+
def __rmul__(self, other) -> Natural:
|
|
48
|
+
return Natural(self.value + other.value)
|
|
49
|
+
def __rdiv__(self, other) -> Natural:
|
|
50
|
+
return Natural(self.value + other.value)
|
|
51
|
+
def __rpow__(self, other) -> Natural:
|
|
52
|
+
return Natural(self.value + other.value)
|
|
53
|
+
|
|
54
|
+
__repr__ = pretty_repr
|
|
55
|
+
|
|
56
|
+
class Cardinal:
|
|
57
|
+
'a number that represents the size/cardinality of a set'
|
|
58
|
+
|
|
59
|
+
ALEPH_NULL = object()
|
|
60
|
+
|
|
61
|
+
def __init__(self, value: Natural | Literal[ALEPH_NULL]):
|
|
62
|
+
self.value: Natural | Literal[ALEPH_NULL] = value
|
|
63
|
+
|
|
64
|
+
__repr__ = pretty_repr
|
|
65
|
+
|
|
66
|
+
def __str__(self) -> str:
|
|
67
|
+
return str(self.value)
|
|
68
|
+
|
|
69
|
+
# collections ------------------------------------------------------------------
|
|
70
|
+
|
|
71
|
+
class Sized:
|
|
72
|
+
'like collections.abc.Sized but properly allowing cardinal number sizes instead of just non-negative integer sizes. thus, infinite collections of different cardinalities are possible'
|
|
73
|
+
@abstractmethod
|
|
74
|
+
def __len__(self) -> Cardinal:
|
|
75
|
+
...
|
|
76
|
+
|
|
77
|
+
__repr__ = pretty_repr
|
|
78
|
+
|
|
79
|
+
class IterableContainer(collections.abc.Iterable, collections.abc.Container):
|
|
80
|
+
'an iterable container with rich mixins: count, filter, __contains__. can be applied to Collection, Sequence, Set, and any other ABCs that derive both Iterable and Container.'
|
|
81
|
+
|
|
82
|
+
def count(self, stuff: Set[Any], complement: bool = False) -> Natural:
|
|
83
|
+
'"how many things of stuff does this sequence have?"'
|
|
84
|
+
return sum(thing not in stuff for thing in self) if complement else sum(thing in stuff for thing in self)
|
|
85
|
+
|
|
86
|
+
def filter(self, stuff: Set[Any], complement: bool = False) -> Sequence[Any]:
|
|
87
|
+
'"things of this sequence which are in stuff"'
|
|
88
|
+
return type(self)((thing for thing in self if thing not in stuff) if complement else (thing for thing in self if thing in stuff))
|
|
89
|
+
|
|
90
|
+
def __contains__(self, thing) -> bool:
|
|
91
|
+
return any(thing2 in thing for thing2 in self)
|
|
92
|
+
|
|
93
|
+
__repr__ = pretty_repr
|
|
94
|
+
|
|
95
|
+
class Collection(Sized, IterableContainer):
|
|
96
|
+
'like collections.abc.Collection but allows infinite len and has rich mixins (from IterableContainer)'
|
|
97
|
+
...
|
|
98
|
+
|
|
99
|
+
class Set(Collection):
|
|
100
|
+
"""an interface for datatypes that represent a set: a collection of unique elements"""
|
|
101
|
+
|
|
102
|
+
class Mapping(Collection):
|
|
103
|
+
...
|
|
104
|
+
|
|
105
|
+
class Function(Collection):
|
|
106
|
+
"""an object that maps elements from the domain set to the codomain set.
|
|
107
|
+
|
|
108
|
+
__len__ returns how many pairs are defined on the function."""
|
|
109
|
+
|
|
110
|
+
def __init__(self, domain: Set[Any], codomain: Set[Any], pairs: Mapping[Any, Any]):
|
|
111
|
+
self.domain: Set[Any] = domain
|
|
112
|
+
self.codomain: Set[Any] = codomain
|
|
113
|
+
self.pairs: Mapping[Any, Any] = pairs
|
|
114
|
+
|
|
115
|
+
@property
|
|
116
|
+
def is_total(self):
|
|
117
|
+
return self.domain == self.pairs.keys()
|
|
118
|
+
|
|
119
|
+
class Sequence(Collection):
|
|
120
|
+
...
|
|
121
|
+
|
|
122
|
+
class _CyclicView:
|
|
123
|
+
def __init__(self, sequence: Sequence):
|
|
124
|
+
self.sequence = Sequence
|
|
125
|
+
|
|
126
|
+
def __getitem__(self, index: int | slice) -> Any:
|
|
127
|
+
match index:
|
|
128
|
+
case int(): return self.sequence[index % len(self)]
|
|
129
|
+
case slice(): return type(self.sequence)(self[i % len(self)] for i in range(*index))
|
|
130
|
+
case _: raise IndexError('index must be int or slice')
|
|
131
|
+
|
|
132
|
+
def __len__(self) -> int:
|
|
133
|
+
return len(self.sequence)
|
|
134
|
+
|
|
135
|
+
__repr__ = pretty_repr
|
|
136
|
+
|
|
137
|
+
# there are two kinds of sequences i recognize: if the domain is dense, a .cover cannot be defined. if the domain is not dense, a .cover can be defined, and you can traverse more easily.
|
|
138
|
+
#
|
|
139
|
+
# a sequence is a triple: (domain, function, codomain), where:
|
|
140
|
+
# domain is a . if it is not dense, a cover can be defined, and the sequence can be traversed easily
|
|
141
|
+
|
|
142
|
+
class Wheel(collections.abc.Iterator):
|
|
143
|
+
"""a wheel for generating primes. its iterator returns the residues of the given wheel size. you get diminishing returns as you go up in size:
|
|
144
|
+
|
|
145
|
+
>>> primes = [2,3,5,7,11,13,17,19,23]
|
|
146
|
+
>>> for i in range(len(primes)):
|
|
147
|
+
>>> size = math.prod(primes[:i])
|
|
148
|
+
>>> wheel_count = Wheel(size).cycle_size
|
|
149
|
+
>>> naïve_count = size
|
|
150
|
+
>>> efficiency = wheel_count / naïve_count
|
|
151
|
+
>>> print(size, f'{wheel_count}/{naïve_count}={efficiency:.2%}')
|
|
152
|
+
>>>
|
|
153
|
+
1 1/1=100.00%
|
|
154
|
+
2 1/2=50.00%
|
|
155
|
+
6 2/6=33.33%
|
|
156
|
+
30 8/30=26.67%
|
|
157
|
+
210 48/210=22.86%
|
|
158
|
+
2310 480/2310=20.78%
|
|
159
|
+
30030 5760/30030=19.18%
|
|
160
|
+
510510 92160/510510=18.05%
|
|
161
|
+
9699690 1658880/9699690=17.10%
|
|
162
|
+
223092870 36495360/223092870=16.36%
|
|
163
|
+
"""
|
|
164
|
+
|
|
165
|
+
@staticmethod
|
|
166
|
+
def _generate_steps(size: int) -> Sequence[int]:
|
|
167
|
+
candidates = tuple(filter(lambda candidate: math.gcd(candidate, size) == 1, range(size)))
|
|
168
|
+
return [b - a for a, b in pairwise(candidates)] + [(candidates[0] - candidates[-1]) % size]
|
|
169
|
+
|
|
170
|
+
def __init__(self, factors: collections.abc.Iterable[int]):
|
|
171
|
+
'if you pass non-primes as factors, you wont be able to track them at the start of your generator. thats on you, idiot. sorry, harsh words, sorry but if you do that youre kinda dumb.. a bit'
|
|
172
|
+
size = math.lcm(*factors)
|
|
173
|
+
|
|
174
|
+
if math.prod(factors) != size:
|
|
175
|
+
raise ValueError('factors must be coprime!')
|
|
176
|
+
|
|
177
|
+
self.size: int = size
|
|
178
|
+
self.candidate: int = 1
|
|
179
|
+
|
|
180
|
+
steps: Sequence[int] = Wheel._generate_steps(size)
|
|
181
|
+
self.cycle: cycle[int] = cycle(steps)
|
|
182
|
+
self.cycle_size: int = len(steps)
|
|
183
|
+
|
|
184
|
+
# initialize candidate properly
|
|
185
|
+
#for factor in factors: # indexitis
|
|
186
|
+
# next(self)
|
|
187
|
+
|
|
188
|
+
def __iter__(self):
|
|
189
|
+
return self
|
|
190
|
+
|
|
191
|
+
def __next__(self) -> int:
|
|
192
|
+
self.candidate += next(self.cycle)
|
|
193
|
+
return self.candidate
|
|
194
|
+
|
|
195
|
+
__repr__ = pretty_repr
|
|
196
|
+
|
|
197
|
+
class PrimesIterableContainer(IterableContainer):
|
|
198
|
+
"""a singleton class representing the infinite sequence of prime numbers. you can do things like:
|
|
199
|
+
|
|
200
|
+
Primes = PrimeSequence()
|
|
201
|
+
|
|
202
|
+
Primes[0] == 2
|
|
203
|
+
Primes[1] == 3
|
|
204
|
+
Primes[2] == 5
|
|
205
|
+
Primes[3] == 7
|
|
206
|
+
|
|
207
|
+
assert 5 in Primes
|
|
208
|
+
assert 13 in Primes
|
|
209
|
+
|
|
210
|
+
primes = Primes[:10]
|
|
211
|
+
|
|
212
|
+
for prime in Primes[:100]:
|
|
213
|
+
print(prime)
|
|
214
|
+
|
|
215
|
+
uses a 2x3 wheel by default, thus only testing 6*1-1, 6*1+1, 6*2-1, 6*2+1, 6*3-1, 6*3+1, … for generation. larger wheels like 2x3x5, 2x3x5x7, … can be used for better efficiency (see Wheel class)
|
|
216
|
+
"""
|
|
217
|
+
|
|
218
|
+
_cache: MutableSequence[Integral] = [2, 3]
|
|
219
|
+
|
|
220
|
+
def __init__(self, wheel_factors: Iterable[int] = [2, 3]):
|
|
221
|
+
self.wheel: Wheel = Wheel(wheel_factors)
|
|
222
|
+
self._cache.extend(wheel_factors[len(self._cache):])
|
|
223
|
+
|
|
224
|
+
def _extend_cache_by(self, count: int = 1) -> None:
|
|
225
|
+
'grow cache by a certain .count of primes'
|
|
226
|
+
|
|
227
|
+
while count > 0:
|
|
228
|
+
candidate = next(self.wheel)
|
|
229
|
+
|
|
230
|
+
# "is candidate prime?"
|
|
231
|
+
if all(candidate % prime != 0 for prime in self._cache):
|
|
232
|
+
count -= 1
|
|
233
|
+
self._cache.append(candidate)
|
|
234
|
+
|
|
235
|
+
def __getitem__(self, index: slice | int):
|
|
236
|
+
if isinstance(index, slice):
|
|
237
|
+
self._extend_cache_by(index.stop - len(self._cache))
|
|
238
|
+
elif isinstance(index, int):
|
|
239
|
+
if index < 0:
|
|
240
|
+
raise ValueError('cannot use negative indices on an infinite sequence')
|
|
241
|
+
self._extend_cache_by(index + 1 - len(self._cache))
|
|
242
|
+
else:
|
|
243
|
+
raise TypeError('index must be either slice or int')
|
|
244
|
+
|
|
245
|
+
return self._cache[index]
|
|
246
|
+
|
|
247
|
+
def __iter__(self):
|
|
248
|
+
i = 0
|
|
249
|
+
|
|
250
|
+
while True:
|
|
251
|
+
yield self[i := i + 1]
|
|
252
|
+
|
|
253
|
+
@classmethod
|
|
254
|
+
def __contains__(cls, number: Natural) -> bool:
|
|
255
|
+
# number is known (from cache) to be prime
|
|
256
|
+
if number in cls._cache:
|
|
257
|
+
return True
|
|
258
|
+
|
|
259
|
+
# number is within cache
|
|
260
|
+
if cls._cache[-1] >= number:
|
|
261
|
+
return number in cls._cache
|
|
262
|
+
|
|
263
|
+
# number is not within cache. perform divisibility check
|
|
264
|
+
# this way, cache is generated up to ≥⌊√n⌋ instead of ≥n
|
|
265
|
+
limit: int = math.floor(math.sqrt(number))
|
|
266
|
+
|
|
267
|
+
for prime in Primes:
|
|
268
|
+
if prime > limit:
|
|
269
|
+
return True
|
|
270
|
+
if number % prime == 0:
|
|
271
|
+
return False
|
|
272
|
+
|
|
273
|
+
def index(self, number):
|
|
274
|
+
if number not in self:
|
|
275
|
+
raise numberError(f'{number} is not prime')
|
|
276
|
+
|
|
277
|
+
while self._cache[-1] < number:
|
|
278
|
+
self._extend_cache_by(1)
|
|
279
|
+
|
|
280
|
+
return self._cache.index(number)
|
|
281
|
+
|
|
282
|
+
__repr__ = pretty_repr
|
|
283
|
+
|
|
284
|
+
Primes = PrimesIterableContainer()
|
|
285
|
+
|
|
286
|
+
'''
|
|
287
|
+
# here we dont subclass Rational because surds are not rational!
|
|
288
|
+
class Surd(Real):
|
|
289
|
+
'like how Integral is a sub(Natural, Natural) pair, and Rational is a div(Integral, Integral) pair, Surd is a root(Integral, Integral) pair. its cousin is the log(Integral, Integral) pair, which i have not named yet. the equivalence class is op(a, b) = op(c, d). so, for example, 2√2 = 4√4'
|
|
290
|
+
|
|
291
|
+
def __init__(self, base: Rational, degree: Rational):
|
|
292
|
+
self.base = base
|
|
293
|
+
self.degree = degree
|
|
294
|
+
|
|
295
|
+
def __float__(self) -> float:
|
|
296
|
+
return self.base ** (1 / self.degree)
|
|
297
|
+
|
|
298
|
+
def __mul__(self, other) -> Surd:
|
|
299
|
+
return self.base
|
|
300
|
+
'''
|
|
301
|
+
|
|
302
|
+
class Monzo(Hashable, Sequence):#, Rational):
|
|
303
|
+
"""a datatype that represents rational number stored as prime factors — conceptually as a sparse sequence, implemented as a mapping, but has the interface of a sequence :)
|
|
304
|
+
|
|
305
|
+
examples:
|
|
306
|
+
Monzo(2) = [1⟩ = {0: 1} = 2¹
|
|
307
|
+
Monzo(3) = [0 1⟩ = {1: 1} = 3¹
|
|
308
|
+
Monzo(4) = [2⟩ = {0: 2} = 2²
|
|
309
|
+
Monzo(5) = [0 0 1⟩ = {2: 1} = 5¹
|
|
310
|
+
"""
|
|
311
|
+
|
|
312
|
+
def __init__(self, factors: HashableMapping, sign: Literal[-1, 0, +1] = 1):
|
|
313
|
+
self.factors: HashableMapping = factors
|
|
314
|
+
self.sign: Literal[-1, 0, +1] = sign
|
|
315
|
+
|
|
316
|
+
@cached_property
|
|
317
|
+
def numerator(self) -> Integral:
|
|
318
|
+
return self.sign * math.prod(Primes[prime_index] ** exponent for prime_index, exponent in self.factors.items() if exponent > 0)
|
|
319
|
+
|
|
320
|
+
@cached_property
|
|
321
|
+
def denominator(self) -> Integral:
|
|
322
|
+
return math.prod(Primes[prime_index] ** -exponent for prime_index, exponent in self.factors.items() if exponent < 0)
|
|
323
|
+
|
|
324
|
+
@cached_property
|
|
325
|
+
def prime_factors(self) -> Mapping[Integral, Integral]:
|
|
326
|
+
return frozendefaultdict.from_items(int, ((Primes[prime_index], exponent) for prime_index, exponent in self.factors.items()))
|
|
327
|
+
|
|
328
|
+
# to support Sequence interface
|
|
329
|
+
def __getitem__(self, index: int) -> int:
|
|
330
|
+
return self.factors[index] if index in self.factors else 0
|
|
331
|
+
def __len__(self) -> Cardinal:
|
|
332
|
+
keys = self.factors.keys()
|
|
333
|
+
return max(keys) + 1 if len(keys) > 0 else 1
|
|
334
|
+
def __iter__(self):
|
|
335
|
+
yield from (self.factors[index] for index in range(len(self)))
|
|
336
|
+
|
|
337
|
+
# to support Rational interface
|
|
338
|
+
def __add__(self, other) -> Monzo:
|
|
339
|
+
'a / b + c / d = (a * d + b * c) / (b * d)'
|
|
340
|
+
a, b, c, d = self.numerator, self.denominator, other.numerator, other.denominator
|
|
341
|
+
return cls.from_parts(a * d + b * c, b * d)
|
|
342
|
+
def __eq__(self, other) -> bool:
|
|
343
|
+
'a / b = c / d'
|
|
344
|
+
a, b, c, d = self.numerator, self.denominator, other.numerator, other.denominator
|
|
345
|
+
return a * d == b * c
|
|
346
|
+
def __float__(self) -> float:
|
|
347
|
+
return self.numerator / self.denominator
|
|
348
|
+
|
|
349
|
+
@classmethod
|
|
350
|
+
def from_prime_factors(cls, prime_factors: Mapping, *args, **kwargs) -> Monzo:
|
|
351
|
+
return cls(frozendefaultdict.from_items(int, ((Primes.index(factor), exponent) for factor, exponent in prime_factors.items())), *args, **kwargs)
|
|
352
|
+
|
|
353
|
+
@staticmethod
|
|
354
|
+
def _prime_factorize(number: Natural) -> HashableMapping[int, int]:
|
|
355
|
+
factors: MutableMapping = defaultdict(int)
|
|
356
|
+
|
|
357
|
+
prime_index = 0
|
|
358
|
+
|
|
359
|
+
while number > 1:
|
|
360
|
+
prime = Primes[prime_index]
|
|
361
|
+
|
|
362
|
+
if number % prime == 0:
|
|
363
|
+
number /= prime
|
|
364
|
+
if prime_index in factors:
|
|
365
|
+
factors[prime_index] += 1
|
|
366
|
+
else:
|
|
367
|
+
factors[prime_index] = 1
|
|
368
|
+
else:
|
|
369
|
+
prime_index += 1
|
|
370
|
+
|
|
371
|
+
return frozendefaultdict.from_items(int, factors.items())
|
|
372
|
+
|
|
373
|
+
@classmethod
|
|
374
|
+
def from_parts(cls, numerator: Integral, denominator: Integral) -> Monzo:
|
|
375
|
+
sign: Integral = int(math.copysign(1.0, numerator * denominator))
|
|
376
|
+
|
|
377
|
+
numerator = Monzo._prime_factorize(numerator)
|
|
378
|
+
denominator = Monzo._prime_factorize(denominator)
|
|
379
|
+
|
|
380
|
+
factors: MutableMapping = defaultdict(int, numerator)
|
|
381
|
+
|
|
382
|
+
for prime_factor, exponent in denominator.items():
|
|
383
|
+
if prime_factor in factors:
|
|
384
|
+
factors[prime_factor] -= exponent
|
|
385
|
+
else:
|
|
386
|
+
factors[prime_factor] = -exponent
|
|
387
|
+
|
|
388
|
+
return cls(frozendefaultdict.from_items(int, factors.items()), sign)
|
|
389
|
+
|
|
390
|
+
@classmethod
|
|
391
|
+
def from_rational(cls, number: Rational) -> Monzo:
|
|
392
|
+
return cls.from_parts(number.numerator, number.denominator)
|
|
393
|
+
|
|
394
|
+
@classmethod
|
|
395
|
+
def from_sequence(cls, exponents: Sequence[Integral]) -> Monzo:
|
|
396
|
+
'construct from a sequence. has to use reversed, len, and indexing'
|
|
397
|
+
# because we dont want [1⟩ from [1 0 0], not [1 0 0⟩
|
|
398
|
+
trailing_zero_count: int = 0
|
|
399
|
+
for exponent in reversed(exponents):
|
|
400
|
+
if exponent != 0:
|
|
401
|
+
break
|
|
402
|
+
trailing_zero_count += 1
|
|
403
|
+
|
|
404
|
+
items = enumerate(exponents if trailing_zero_count == 0 else exponents[:-trailing_zero_count])
|
|
405
|
+
|
|
406
|
+
return cls(frozendefaultdict.from_items(int, items))
|
|
407
|
+
|
|
408
|
+
def __hash__(self) -> int:
|
|
409
|
+
return hash(Fraction(self))
|
|
410
|
+
|
|
411
|
+
__repr__ = pretty_repr
|
|
412
|
+
def __str__(self) -> str:
|
|
413
|
+
return '[' + ' '.join(str(exponent) for exponent in self) + '⟩'
|
|
414
|
+
|
|
415
|
+
Rational.register(Monzo)
|