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.
@@ -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)