pythonic-peano-arithmetic 0.3.0__py3-none-any.whl
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.
- peano/__init__.py +66 -0
- peano/algebraic_root.py +180 -0
- peano/integer.py +345 -0
- peano/natural_number.py +340 -0
- peano/polynomial.py +431 -0
- peano/py.typed +1 -0
- peano/rational.py +295 -0
- peano/utils.py +189 -0
- pythonic_peano_arithmetic-0.3.0.dist-info/METADATA +240 -0
- pythonic_peano_arithmetic-0.3.0.dist-info/RECORD +12 -0
- pythonic_peano_arithmetic-0.3.0.dist-info/WHEEL +4 -0
- pythonic_peano_arithmetic-0.3.0.dist-info/licenses/LICENSE +21 -0
peano/natural_number.py
ADDED
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
"""Construct natural numbers and their operations from zero and successor."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from functools import total_ordering
|
|
7
|
+
from typing import TYPE_CHECKING, Iterator, cast
|
|
8
|
+
|
|
9
|
+
from .utils import LogMessage, localized, log
|
|
10
|
+
|
|
11
|
+
if TYPE_CHECKING:
|
|
12
|
+
from .integer import Integer
|
|
13
|
+
from .rational import Rational
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@total_ordering
|
|
17
|
+
@dataclass(frozen=True, slots=True, eq=False, repr=False)
|
|
18
|
+
class NaturalNumber:
|
|
19
|
+
"""A natural number based on the Peano axioms.
|
|
20
|
+
|
|
21
|
+
``None`` represents zero and ``NaturalNumber(n)`` represents the successor
|
|
22
|
+
:math:`S(n)`. Values are immutable. Addition and multiplication follow
|
|
23
|
+
their recursive definitions directly.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
pre: NaturalNumber | None = None
|
|
27
|
+
|
|
28
|
+
def __post_init__(self) -> None:
|
|
29
|
+
if self.pre is not None and not isinstance(self.pre, NaturalNumber):
|
|
30
|
+
raise TypeError("pre must be a NaturalNumber or None")
|
|
31
|
+
|
|
32
|
+
def __repr__(self) -> str:
|
|
33
|
+
return f"<N({int(self)})>"
|
|
34
|
+
|
|
35
|
+
def __str__(self) -> str:
|
|
36
|
+
return str(int(self))
|
|
37
|
+
|
|
38
|
+
def __int__(self) -> int:
|
|
39
|
+
value = 0
|
|
40
|
+
current: NaturalNumber | None = self
|
|
41
|
+
while current is not None and current.pre is not None:
|
|
42
|
+
value += 1
|
|
43
|
+
current = current.pre
|
|
44
|
+
return value
|
|
45
|
+
|
|
46
|
+
def structural_str(self) -> str:
|
|
47
|
+
"""Return the structure using only zero and successor notation."""
|
|
48
|
+
|
|
49
|
+
depth = 0
|
|
50
|
+
current = self
|
|
51
|
+
while current.pre is not None:
|
|
52
|
+
depth += 1
|
|
53
|
+
current = current.pre
|
|
54
|
+
return f"{'S(' * depth}0{')' * depth}"
|
|
55
|
+
|
|
56
|
+
@log(log_level=1)
|
|
57
|
+
def __eq__(self, other: object) -> tuple[bool, LogMessage]:
|
|
58
|
+
if not isinstance(other, NaturalNumber):
|
|
59
|
+
return (
|
|
60
|
+
cast(bool, NotImplemented),
|
|
61
|
+
lambda: f"{self!r} == {other!r} = NotImplemented",
|
|
62
|
+
)
|
|
63
|
+
left_predecessor = self.pre
|
|
64
|
+
right_predecessor = other.pre
|
|
65
|
+
if left_predecessor is None or right_predecessor is None:
|
|
66
|
+
result = left_predecessor is None and right_predecessor is None
|
|
67
|
+
return (
|
|
68
|
+
result,
|
|
69
|
+
lambda: (
|
|
70
|
+
f"{localized('[equality: zero case]', '[等値・0の場合]')} "
|
|
71
|
+
f"eq({self.structural_str()}, "
|
|
72
|
+
f"{other.structural_str()}) -> {result}"
|
|
73
|
+
),
|
|
74
|
+
)
|
|
75
|
+
return (
|
|
76
|
+
left_predecessor == right_predecessor,
|
|
77
|
+
lambda: (
|
|
78
|
+
f"{localized('[equality: successor case]', '[等値・後者の場合]')} "
|
|
79
|
+
f"eq({self.structural_str()}, "
|
|
80
|
+
f"{other.structural_str()}) -> "
|
|
81
|
+
f"eq({left_predecessor.structural_str()}, "
|
|
82
|
+
f"{right_predecessor.structural_str()})"
|
|
83
|
+
),
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
@log(log_level=2)
|
|
87
|
+
def __lt__(self, other: object) -> tuple[bool, LogMessage]:
|
|
88
|
+
if not isinstance(other, NaturalNumber):
|
|
89
|
+
return (
|
|
90
|
+
cast(bool, NotImplemented),
|
|
91
|
+
lambda: f"{self!r} < {other!r} = NotImplemented",
|
|
92
|
+
)
|
|
93
|
+
if self.pre is None:
|
|
94
|
+
result = other.pre is not None
|
|
95
|
+
return result, lambda: f"{self!r} < {other!r} = {result}"
|
|
96
|
+
if other.pre is None:
|
|
97
|
+
return False, lambda: f"{self!r} < {other!r} = False"
|
|
98
|
+
return (
|
|
99
|
+
self.pre < other.pre,
|
|
100
|
+
lambda: f"{self!r} < {other!r} = {self.pre!r} < {other.pre!r}",
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
@log(log_level=2)
|
|
104
|
+
def __le__(self, other: object) -> tuple[bool, LogMessage]:
|
|
105
|
+
if not isinstance(other, NaturalNumber):
|
|
106
|
+
return (
|
|
107
|
+
cast(bool, NotImplemented),
|
|
108
|
+
lambda: f"{self!r} <= {other!r} = NotImplemented",
|
|
109
|
+
)
|
|
110
|
+
if self.pre is None:
|
|
111
|
+
return True, lambda: f"{self!r} <= {other!r} = True"
|
|
112
|
+
if other.pre is None:
|
|
113
|
+
return False, lambda: f"{self!r} <= {other!r} = False"
|
|
114
|
+
return (
|
|
115
|
+
self.pre <= other.pre,
|
|
116
|
+
lambda: f"{self!r} <= {other!r} = {self.pre!r} <= {other.pre!r}",
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
@log(log_level=4)
|
|
120
|
+
def __add__(self, other: object) -> tuple[NaturalNumber, LogMessage]:
|
|
121
|
+
if not isinstance(other, NaturalNumber):
|
|
122
|
+
return (
|
|
123
|
+
cast(NaturalNumber, NotImplemented),
|
|
124
|
+
lambda: f"{self!r} + {other!r} = NotImplemented",
|
|
125
|
+
)
|
|
126
|
+
predecessor = other.pre
|
|
127
|
+
if predecessor is None:
|
|
128
|
+
return (
|
|
129
|
+
self,
|
|
130
|
+
lambda: (
|
|
131
|
+
f"{localized('[addition: base]', '[加法・基底]')} "
|
|
132
|
+
f"add({self.structural_str()}, 0) "
|
|
133
|
+
f"-> {self.structural_str()}"
|
|
134
|
+
),
|
|
135
|
+
)
|
|
136
|
+
return (
|
|
137
|
+
successor(self + predecessor),
|
|
138
|
+
lambda: (
|
|
139
|
+
f"{localized('[addition: recursive]', '[加法・再帰]')} "
|
|
140
|
+
f"add({self.structural_str()}, "
|
|
141
|
+
f"{other.structural_str()}) -> "
|
|
142
|
+
f"S(add({self.structural_str()}, {predecessor.structural_str()}))"
|
|
143
|
+
),
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
@log(log_level=4)
|
|
147
|
+
def __sub__(self, other: object) -> tuple[NaturalNumber, LogMessage]:
|
|
148
|
+
if not isinstance(other, NaturalNumber):
|
|
149
|
+
return (
|
|
150
|
+
cast(NaturalNumber, NotImplemented),
|
|
151
|
+
lambda: f"{self!r} - {other!r} = NotImplemented",
|
|
152
|
+
)
|
|
153
|
+
if other.pre is None:
|
|
154
|
+
return self, lambda: f"{self!r} - {other!r} = {self!r}"
|
|
155
|
+
if self.pre is None:
|
|
156
|
+
raise ValueError(
|
|
157
|
+
"natural-number subtraction cannot produce a negative value"
|
|
158
|
+
)
|
|
159
|
+
return (
|
|
160
|
+
self.pre - other.pre,
|
|
161
|
+
lambda: f"{self!r} - {other!r} = {self.pre!r} - {other.pre!r}",
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
@log(log_level=5)
|
|
165
|
+
def __mul__(self, other: object) -> tuple[NaturalNumber, LogMessage]:
|
|
166
|
+
if not isinstance(other, NaturalNumber):
|
|
167
|
+
return (
|
|
168
|
+
cast(NaturalNumber, NotImplemented),
|
|
169
|
+
lambda: f"{self!r} * {other!r} = NotImplemented",
|
|
170
|
+
)
|
|
171
|
+
predecessor = other.pre
|
|
172
|
+
if predecessor is None:
|
|
173
|
+
return (
|
|
174
|
+
N_ZERO,
|
|
175
|
+
lambda: (
|
|
176
|
+
f"{localized('[multiplication: base]', '[乗法・基底]')} "
|
|
177
|
+
f"mul({self.structural_str()}, 0) -> 0"
|
|
178
|
+
),
|
|
179
|
+
)
|
|
180
|
+
return (
|
|
181
|
+
self + self * predecessor,
|
|
182
|
+
lambda: (
|
|
183
|
+
f"{localized('[multiplication: recursive]', '[乗法・再帰]')} "
|
|
184
|
+
f"mul({self.structural_str()}, "
|
|
185
|
+
f"{other.structural_str()}) -> "
|
|
186
|
+
f"add({self.structural_str()}, "
|
|
187
|
+
f"mul({self.structural_str()}, {predecessor.structural_str()}))"
|
|
188
|
+
),
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
@log(log_level=5)
|
|
192
|
+
def __truediv__(self, other: object) -> tuple[Rational, LogMessage]:
|
|
193
|
+
if not isinstance(other, NaturalNumber):
|
|
194
|
+
return (
|
|
195
|
+
cast("Rational", NotImplemented),
|
|
196
|
+
lambda: f"{self!r} / {other!r} = NotImplemented",
|
|
197
|
+
)
|
|
198
|
+
if other.pre is None:
|
|
199
|
+
raise ZeroDivisionError("division by zero")
|
|
200
|
+
from .integer import Integer
|
|
201
|
+
from .rational import Rational
|
|
202
|
+
|
|
203
|
+
result = Rational(Integer(self, N_ZERO), Integer(other, N_ZERO))
|
|
204
|
+
return result, lambda: f"{self!r} / {other!r} = {result!r}"
|
|
205
|
+
|
|
206
|
+
@log(log_level=5)
|
|
207
|
+
def __floordiv__(self, other: object) -> tuple[NaturalNumber, LogMessage]:
|
|
208
|
+
if not isinstance(other, NaturalNumber):
|
|
209
|
+
return (
|
|
210
|
+
cast(NaturalNumber, NotImplemented),
|
|
211
|
+
lambda: f"{self!r} // {other!r} = NotImplemented",
|
|
212
|
+
)
|
|
213
|
+
if not other:
|
|
214
|
+
raise ZeroDivisionError("division by zero")
|
|
215
|
+
if self < other:
|
|
216
|
+
return N_ZERO, lambda: f"{self!r} // {other!r} = {N_ZERO!r}"
|
|
217
|
+
return (
|
|
218
|
+
N_ONE + ((self - other) // other),
|
|
219
|
+
lambda: (
|
|
220
|
+
f"{self!r} // {other!r} = "
|
|
221
|
+
f"{N_ONE!r} + (({self!r} - {other!r}) // {other!r})"
|
|
222
|
+
),
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
@log(log_level=5)
|
|
226
|
+
def __mod__(self, other: object) -> tuple[NaturalNumber, LogMessage]:
|
|
227
|
+
if not isinstance(other, NaturalNumber):
|
|
228
|
+
return (
|
|
229
|
+
cast(NaturalNumber, NotImplemented),
|
|
230
|
+
lambda: f"{self!r} % {other!r} = NotImplemented",
|
|
231
|
+
)
|
|
232
|
+
if not other:
|
|
233
|
+
raise ZeroDivisionError("division by zero")
|
|
234
|
+
if self < other:
|
|
235
|
+
return self, lambda: f"{self!r} % {other!r} = {self!r}"
|
|
236
|
+
return (
|
|
237
|
+
(self - other) % other,
|
|
238
|
+
lambda: f"{self!r} % {other!r} = ({self!r} - {other!r}) % {other!r}",
|
|
239
|
+
)
|
|
240
|
+
|
|
241
|
+
def __divmod__(self, other: object) -> tuple[NaturalNumber, NaturalNumber]:
|
|
242
|
+
if not isinstance(other, NaturalNumber):
|
|
243
|
+
return cast(tuple[NaturalNumber, NaturalNumber], NotImplemented)
|
|
244
|
+
return self // other, self % other
|
|
245
|
+
|
|
246
|
+
@log(log_level=6)
|
|
247
|
+
def __pow__(self, exponent: object) -> tuple[NaturalNumber, LogMessage]:
|
|
248
|
+
if not isinstance(exponent, NaturalNumber):
|
|
249
|
+
return (
|
|
250
|
+
cast(NaturalNumber, NotImplemented),
|
|
251
|
+
lambda: f"{self!r} ** {exponent!r} = NotImplemented",
|
|
252
|
+
)
|
|
253
|
+
if exponent.pre is None:
|
|
254
|
+
return N_ONE, lambda: f"{self!r} ** {exponent!r} = {N_ONE!r}"
|
|
255
|
+
return (
|
|
256
|
+
(self**exponent.pre) * self,
|
|
257
|
+
lambda: (
|
|
258
|
+
f"{self!r} ** S({exponent.pre!r}) = "
|
|
259
|
+
f"({self!r} ** {exponent.pre!r}) * {self!r}"
|
|
260
|
+
),
|
|
261
|
+
)
|
|
262
|
+
|
|
263
|
+
def __bool__(self) -> bool:
|
|
264
|
+
return self.pre is not None
|
|
265
|
+
|
|
266
|
+
def __hash__(self) -> int:
|
|
267
|
+
return hash(int(self))
|
|
268
|
+
|
|
269
|
+
def __pos__(self) -> NaturalNumber:
|
|
270
|
+
return self
|
|
271
|
+
|
|
272
|
+
def __neg__(self) -> Integer:
|
|
273
|
+
from .integer import Integer
|
|
274
|
+
|
|
275
|
+
return Integer(N_ZERO, self)
|
|
276
|
+
|
|
277
|
+
def __abs__(self) -> NaturalNumber:
|
|
278
|
+
return self
|
|
279
|
+
|
|
280
|
+
def __iter__(self) -> Iterator[NaturalNumber]:
|
|
281
|
+
current = N_ZERO
|
|
282
|
+
while current != self:
|
|
283
|
+
yield current
|
|
284
|
+
current = successor(current)
|
|
285
|
+
|
|
286
|
+
def __reversed__(self) -> Iterator[NaturalNumber]:
|
|
287
|
+
current = self
|
|
288
|
+
while current.pre is not None:
|
|
289
|
+
current = current.pre
|
|
290
|
+
yield current
|
|
291
|
+
|
|
292
|
+
def set_repr(self) -> frozenset[object]:
|
|
293
|
+
"""Return the von Neumann ordinal representation."""
|
|
294
|
+
|
|
295
|
+
if self.pre is None:
|
|
296
|
+
return frozenset()
|
|
297
|
+
predecessor = self.pre.set_repr()
|
|
298
|
+
return frozenset((predecessor,)) | predecessor
|
|
299
|
+
|
|
300
|
+
def set_str(self) -> str:
|
|
301
|
+
return (
|
|
302
|
+
str(self.set_repr())
|
|
303
|
+
.replace("{", "")
|
|
304
|
+
.replace("}", "")
|
|
305
|
+
.replace("frozenset(", "{")
|
|
306
|
+
.replace(")", "}")
|
|
307
|
+
)
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
def successor(number: NaturalNumber) -> NaturalNumber:
|
|
311
|
+
"""Return the successor :math:`S(n)`."""
|
|
312
|
+
|
|
313
|
+
if not isinstance(number, NaturalNumber):
|
|
314
|
+
raise TypeError("successor expects a NaturalNumber")
|
|
315
|
+
return NaturalNumber(number)
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
def natural_number(value: int) -> NaturalNumber:
|
|
319
|
+
"""Construct a natural number from a non-negative Python integer."""
|
|
320
|
+
|
|
321
|
+
if isinstance(value, bool) or not isinstance(value, int):
|
|
322
|
+
raise TypeError("only int values can be converted to NaturalNumber")
|
|
323
|
+
if value < 0:
|
|
324
|
+
raise ValueError("negative values cannot be converted to NaturalNumber")
|
|
325
|
+
result = N_ZERO
|
|
326
|
+
for _ in range(value):
|
|
327
|
+
result = successor(result)
|
|
328
|
+
return result
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
def cast2n(value: object) -> NaturalNumber:
|
|
332
|
+
"""Validate and return a ``NaturalNumber``."""
|
|
333
|
+
|
|
334
|
+
if not isinstance(value, NaturalNumber):
|
|
335
|
+
raise TypeError(f"{value!r} is not a NaturalNumber")
|
|
336
|
+
return value
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+
N_ZERO = NaturalNumber()
|
|
340
|
+
N_ONE = NaturalNumber(N_ZERO)
|