foxmath 0.1.2__tar.gz → 0.2.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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: foxmath
3
- Version: 0.1.2
3
+ Version: 0.2.0
4
4
  Summary: Minimalist CLI for math & cryptography exploration 🦊
5
5
  Author-email: Abhrankan Chakrabarti <abhrankan@gmail.com>
6
6
  License: MIT
@@ -27,11 +27,12 @@ Built with curiosity — for students, tinkerers, and crypto/math enthusiasts.
27
27
 
28
28
  ## Features
29
29
  - Legendre symbol
30
- - Catalan-inspired π approximation
30
+ - Euler/Hermann Machin-like π approximation
31
31
  - Elliptic curve point addition (over finite fields)
32
- - Symlink-friendly (foxmath-legendre, foxmath-pi, foxmath-ecadd)
32
+ - Chinese Remainder Theorem solver (handles non-coprime moduli)
33
+ - Symlink-friendly (foxmath-legendre, foxmath-pi, foxmath-ecadd, foxmath-crt)
33
34
  - JSON output mode
34
- - Coming soon: CRT, continued fractions, challenge mode, more curves
35
+ - Coming soon: continued fractions, challenge mode, more curves
35
36
 
36
37
  ## Installation
37
38
 
@@ -49,24 +50,50 @@ pip install -e .
49
50
  ## Usage Examples
50
51
 
51
52
  ```bash
52
- # Basic
53
53
  foxmath pi --terms 200
54
+ ```
55
+ ```
56
+ π ≈ 3.1415926535897932384626433832795028841971693993752 (200 terms)
57
+ ```
58
+
59
+ ```bash
54
60
  foxmath legendre 5 17
61
+ ```
62
+ ```
63
+ Legendre (5/17) = -1
64
+ ```
55
65
 
66
+ ```bash
56
67
  # Symlinks (optional)
57
68
  foxmath-ecadd --x1 1 --y1 2 --x2 3 --y2 4 --p 17
69
+ ```
70
+ ```
71
+ Result point: (14, 2)
72
+ ```
58
73
 
59
- # JSON output
60
- foxmath pi --terms 100 --json
74
+ ```bash
75
+ # Chinese Remainder Theorem
76
+ foxmath crt --r 2 3 2 --m 3 5 7
77
+ ```
78
+ ```
79
+ x ≡ 23 (mod 105)
61
80
  ```
62
81
 
63
- ### Example Output
82
+ ```bash
83
+ # JSON output
84
+ foxmath pi --terms 100 --json
64
85
  ```
65
- π ≈ 3.1415926535897932384626433832795028841971693993751 (200 terms)
86
+ ```json
87
+ {
88
+ "tool": "foxmath",
89
+ "command": "pi",
90
+ "terms": 100,
91
+ "approx": "3.1415926535897932384626433832795028841971693993752"
92
+ }
66
93
  ```
67
94
 
68
95
  ## Why FoxMath?
69
- Because math and cryptography are more fun when you can play with them instantly in the terminal — with the same reliability as FoxPipe and fox-vault.
96
+ Because math and cryptography are more fun when you can play with them instantly in the terminal — with the same reliability as [FoxPipe](https://github.com/foxhackerzdevs/FoxPipe) and [fox-vault](https://github.com/foxhackerzdevs/fox-vault).
70
97
 
71
98
  **Simple. Practical. Reliable.**
72
99
 
@@ -8,11 +8,12 @@ Built with curiosity — for students, tinkerers, and crypto/math enthusiasts.
8
8
 
9
9
  ## Features
10
10
  - Legendre symbol
11
- - Catalan-inspired π approximation
11
+ - Euler/Hermann Machin-like π approximation
12
12
  - Elliptic curve point addition (over finite fields)
13
- - Symlink-friendly (foxmath-legendre, foxmath-pi, foxmath-ecadd)
13
+ - Chinese Remainder Theorem solver (handles non-coprime moduli)
14
+ - Symlink-friendly (foxmath-legendre, foxmath-pi, foxmath-ecadd, foxmath-crt)
14
15
  - JSON output mode
15
- - Coming soon: CRT, continued fractions, challenge mode, more curves
16
+ - Coming soon: continued fractions, challenge mode, more curves
16
17
 
17
18
  ## Installation
18
19
 
@@ -30,24 +31,50 @@ pip install -e .
30
31
  ## Usage Examples
31
32
 
32
33
  ```bash
33
- # Basic
34
34
  foxmath pi --terms 200
35
+ ```
36
+ ```
37
+ π ≈ 3.1415926535897932384626433832795028841971693993752 (200 terms)
38
+ ```
39
+
40
+ ```bash
35
41
  foxmath legendre 5 17
42
+ ```
43
+ ```
44
+ Legendre (5/17) = -1
45
+ ```
36
46
 
47
+ ```bash
37
48
  # Symlinks (optional)
38
49
  foxmath-ecadd --x1 1 --y1 2 --x2 3 --y2 4 --p 17
50
+ ```
51
+ ```
52
+ Result point: (14, 2)
53
+ ```
39
54
 
40
- # JSON output
41
- foxmath pi --terms 100 --json
55
+ ```bash
56
+ # Chinese Remainder Theorem
57
+ foxmath crt --r 2 3 2 --m 3 5 7
58
+ ```
59
+ ```
60
+ x ≡ 23 (mod 105)
42
61
  ```
43
62
 
44
- ### Example Output
63
+ ```bash
64
+ # JSON output
65
+ foxmath pi --terms 100 --json
45
66
  ```
46
- π ≈ 3.1415926535897932384626433832795028841971693993751 (200 terms)
67
+ ```json
68
+ {
69
+ "tool": "foxmath",
70
+ "command": "pi",
71
+ "terms": 100,
72
+ "approx": "3.1415926535897932384626433832795028841971693993752"
73
+ }
47
74
  ```
48
75
 
49
76
  ## Why FoxMath?
50
- Because math and cryptography are more fun when you can play with them instantly in the terminal — with the same reliability as FoxPipe and fox-vault.
77
+ Because math and cryptography are more fun when you can play with them instantly in the terminal — with the same reliability as [FoxPipe](https://github.com/foxhackerzdevs/FoxPipe) and [fox-vault](https://github.com/foxhackerzdevs/fox-vault).
51
78
 
52
79
  **Simple. Practical. Reliable.**
53
80
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "foxmath"
7
- version = "0.1.2"
7
+ version = "0.2.0"
8
8
  description = "Minimalist CLI for math & cryptography exploration 🦊"
9
9
  readme = "README.md"
10
10
  license = {text = "MIT"}
@@ -31,3 +31,4 @@ foxmath = "foxmath.foxmath:main"
31
31
  foxmath-legendre = "foxmath.foxmath:main"
32
32
  foxmath-pi = "foxmath.foxmath:main"
33
33
  foxmath-ecadd = "foxmath.foxmath:main"
34
+ foxmath-crt = "foxmath.foxmath:main"
@@ -27,7 +27,7 @@ def _arctan_series(inv_n: Decimal, terms: int) -> Decimal:
27
27
  total += sign * inv_n**(2 * k + 1) / (2 * k + 1)
28
28
  return total
29
29
 
30
- def catalan_pi_approx(terms: int = 100) -> Decimal:
30
+ def euler_hermann_pi_approx(terms: int = 100) -> Decimal:
31
31
  """
32
32
  Fast-converging π approximation via the Euler/Hermann Machin-like
33
33
  identity: pi/4 = arctan(1/2) + arctan(1/3).
@@ -54,6 +54,48 @@ def ec_point_add(x1, y1, x2, y2, a, p):
54
54
  y3 = (lam * (x1 - x3) - y1) % p
55
55
  return x3, y3
56
56
 
57
+ def _extended_gcd(a: int, b: int):
58
+ """Returns (g, x, y) such that a*x + b*y == g == gcd(a, b)."""
59
+ old_r, r = a, b
60
+ old_s, s = 1, 0
61
+ old_t, t = 0, 1
62
+ while r != 0:
63
+ q = old_r // r
64
+ old_r, r = r, old_r - q * r
65
+ old_s, s = s, old_s - q * s
66
+ old_t, t = t, old_t - q * t
67
+ return old_r, old_s, old_t
68
+
69
+ def _combine_crt(r1: int, m1: int, r2: int, m2: int):
70
+ """Combine x≡r1 (mod m1) and x≡r2 (mod m2) into a single x≡r (mod lcm(m1,m2))."""
71
+ g, p, _ = _extended_gcd(m1, m2)
72
+ if (r2 - r1) % g != 0:
73
+ raise ValueError(
74
+ f"No solution exists: moduli {m1} and {m2} are inconsistent "
75
+ f"for remainders {r1} and {r2}."
76
+ )
77
+ lcm = m1 // g * m2
78
+ x = (r1 + (r2 - r1) // g * p % (m2 // g) * m1) % lcm
79
+ return x, lcm
80
+
81
+ def crt_solve(remainders, moduli):
82
+ """
83
+ Chinese Remainder Theorem, generalized to non-pairwise-coprime moduli.
84
+ Returns (x, m) such that x is the unique solution mod m = lcm(moduli),
85
+ or raises ValueError if the system is inconsistent.
86
+ """
87
+ if len(remainders) != len(moduli):
88
+ raise ValueError("remainders and moduli must have the same length")
89
+ if not remainders:
90
+ raise ValueError("need at least one congruence")
91
+ if any(m <= 0 for m in moduli):
92
+ raise ValueError("all moduli must be positive")
93
+
94
+ r, m = remainders[0] % moduli[0], moduli[0]
95
+ for ri, mi in zip(remainders[1:], moduli[1:]):
96
+ r, m = _combine_crt(r, m, ri % mi, mi)
97
+ return r, m
98
+
57
99
  # ------------------- CLI -------------------
58
100
 
59
101
  def get_command():
@@ -92,7 +134,7 @@ def main():
92
134
  leg.add_argument("p", type=int, help="Odd prime p")
93
135
 
94
136
  # Pi
95
- pi_cmd = subparsers.add_parser("pi", help="Catalan-inspired π approximation")
137
+ pi_cmd = subparsers.add_parser("pi", help="Euler/Hermann Machin-like π approximation")
96
138
  pi_cmd.add_argument("--terms", type=int, default=100)
97
139
 
98
140
  # Elliptic curve add
@@ -104,6 +146,11 @@ def main():
104
146
  ec.add_argument("--a", type=int, default=-3, help="Curve parameter a")
105
147
  ec.add_argument("--p", type=int, required=True, help="Prime modulus")
106
148
 
149
+ # Chinese Remainder Theorem
150
+ crt = subparsers.add_parser("crt", help="Solve a system of congruences x = r (mod m)")
151
+ crt.add_argument("--r", type=int, nargs="+", required=True, help="Remainders, e.g. --r 2 3 2")
152
+ crt.add_argument("--m", type=int, nargs="+", required=True, help="Moduli, e.g. --m 3 5 7")
153
+
107
154
  args = parser.parse_args(argv)
108
155
 
109
156
  result = {"tool": "foxmath"}
@@ -116,7 +163,7 @@ def main():
116
163
  print(f"Legendre ({args.a}/{args.p}) = {ls}")
117
164
 
118
165
  elif cmd == "pi" or args.command == "pi":
119
- approx = catalan_pi_approx(args.terms)
166
+ approx = euler_hermann_pi_approx(args.terms)
120
167
  result.update({"command": "pi", "terms": args.terms, "approx": str(approx)})
121
168
  if not json_output:
122
169
  print(f"π ≈ {approx} ({args.terms} terms)")
@@ -127,6 +174,12 @@ def main():
127
174
  if not json_output:
128
175
  print(f"Result point: ({x3}, {y3})")
129
176
 
177
+ elif cmd == "crt" or args.command == "crt":
178
+ x, m = crt_solve(args.r, args.m)
179
+ result.update({"command": "crt", "r": args.r, "m": args.m, "x": x, "mod": m})
180
+ if not json_output:
181
+ print(f"x ≡ {x} (mod {m})")
182
+
130
183
  except Exception as e:
131
184
  print(f"Error: {e}", file=sys.stderr)
132
185
  sys.exit(1)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: foxmath
3
- Version: 0.1.2
3
+ Version: 0.2.0
4
4
  Summary: Minimalist CLI for math & cryptography exploration 🦊
5
5
  Author-email: Abhrankan Chakrabarti <abhrankan@gmail.com>
6
6
  License: MIT
@@ -27,11 +27,12 @@ Built with curiosity — for students, tinkerers, and crypto/math enthusiasts.
27
27
 
28
28
  ## Features
29
29
  - Legendre symbol
30
- - Catalan-inspired π approximation
30
+ - Euler/Hermann Machin-like π approximation
31
31
  - Elliptic curve point addition (over finite fields)
32
- - Symlink-friendly (foxmath-legendre, foxmath-pi, foxmath-ecadd)
32
+ - Chinese Remainder Theorem solver (handles non-coprime moduli)
33
+ - Symlink-friendly (foxmath-legendre, foxmath-pi, foxmath-ecadd, foxmath-crt)
33
34
  - JSON output mode
34
- - Coming soon: CRT, continued fractions, challenge mode, more curves
35
+ - Coming soon: continued fractions, challenge mode, more curves
35
36
 
36
37
  ## Installation
37
38
 
@@ -49,24 +50,50 @@ pip install -e .
49
50
  ## Usage Examples
50
51
 
51
52
  ```bash
52
- # Basic
53
53
  foxmath pi --terms 200
54
+ ```
55
+ ```
56
+ π ≈ 3.1415926535897932384626433832795028841971693993752 (200 terms)
57
+ ```
58
+
59
+ ```bash
54
60
  foxmath legendre 5 17
61
+ ```
62
+ ```
63
+ Legendre (5/17) = -1
64
+ ```
55
65
 
66
+ ```bash
56
67
  # Symlinks (optional)
57
68
  foxmath-ecadd --x1 1 --y1 2 --x2 3 --y2 4 --p 17
69
+ ```
70
+ ```
71
+ Result point: (14, 2)
72
+ ```
58
73
 
59
- # JSON output
60
- foxmath pi --terms 100 --json
74
+ ```bash
75
+ # Chinese Remainder Theorem
76
+ foxmath crt --r 2 3 2 --m 3 5 7
77
+ ```
78
+ ```
79
+ x ≡ 23 (mod 105)
61
80
  ```
62
81
 
63
- ### Example Output
82
+ ```bash
83
+ # JSON output
84
+ foxmath pi --terms 100 --json
64
85
  ```
65
- π ≈ 3.1415926535897932384626433832795028841971693993751 (200 terms)
86
+ ```json
87
+ {
88
+ "tool": "foxmath",
89
+ "command": "pi",
90
+ "terms": 100,
91
+ "approx": "3.1415926535897932384626433832795028841971693993752"
92
+ }
66
93
  ```
67
94
 
68
95
  ## Why FoxMath?
69
- Because math and cryptography are more fun when you can play with them instantly in the terminal — with the same reliability as FoxPipe and fox-vault.
96
+ Because math and cryptography are more fun when you can play with them instantly in the terminal — with the same reliability as [FoxPipe](https://github.com/foxhackerzdevs/FoxPipe) and [fox-vault](https://github.com/foxhackerzdevs/fox-vault).
70
97
 
71
98
  **Simple. Practical. Reliable.**
72
99
 
@@ -1,5 +1,6 @@
1
1
  [console_scripts]
2
2
  foxmath = foxmath.foxmath:main
3
+ foxmath-crt = foxmath.foxmath:main
3
4
  foxmath-ecadd = foxmath.foxmath:main
4
5
  foxmath-legendre = foxmath.foxmath:main
5
6
  foxmath-pi = foxmath.foxmath:main
@@ -79,6 +79,12 @@ class TestSymlinkInvocation(unittest.TestCase):
79
79
  self.assertEqual(code, 0)
80
80
  self.assertIn("Result point:", out)
81
81
 
82
+ def test_crt_symlink(self):
83
+ script = self._make_symlink_script("foxmath-crt")
84
+ code, out, err = run("--r", "2", "3", "2", "--m", "3", "5", "7", script=script)
85
+ self.assertEqual(code, 0)
86
+ self.assertIn("x ≡ 23 (mod 105)", out)
87
+
82
88
  def test_symlink_with_json(self):
83
89
  script = self._make_symlink_script("foxmath-legendre")
84
90
  code, out, err = run("2", "7", "--json", script=script)
@@ -98,6 +104,24 @@ class TestNormalUsage(unittest.TestCase):
98
104
  self.assertEqual(code, 1)
99
105
  self.assertIn("point at infinity", err)
100
106
 
107
+ def test_crt(self):
108
+ code, out, err = run("crt", "--r", "2", "3", "2", "--m", "3", "5", "7")
109
+ self.assertEqual(code, 0)
110
+ self.assertIn("x ≡ 23 (mod 105)", out)
111
+
112
+ def test_crt_json_is_pure_json(self):
113
+ code, out, err = run("crt", "--r", "2", "3", "2", "--m", "3", "5", "7", "--json")
114
+ self.assertEqual(code, 0)
115
+ import json
116
+ parsed = json.loads(out)
117
+ self.assertEqual(parsed["x"], 23)
118
+ self.assertEqual(parsed["mod"], 105)
119
+
120
+ def test_crt_inconsistent_system_errors(self):
121
+ code, out, err = run("crt", "--r", "1", "2", "--m", "4", "6")
122
+ self.assertEqual(code, 1)
123
+ self.assertIn("inconsistent", err)
124
+
101
125
 
102
126
  if __name__ == "__main__":
103
127
  unittest.main()
@@ -12,7 +12,7 @@ from decimal import Decimal
12
12
 
13
13
  sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "src")))
14
14
 
15
- from foxmath.foxmath import legendre_symbol, catalan_pi_approx, ec_point_add
15
+ from foxmath.foxmath import legendre_symbol, euler_hermann_pi_approx, ec_point_add, crt_solve
16
16
 
17
17
  # Reference pi digits (mpmath, 250 dps of working precision), "3" + fractional digits.
18
18
  PI_REFERENCE = Decimal(
@@ -47,16 +47,16 @@ class TestLegendreSymbol(unittest.TestCase):
47
47
  legendre_symbol(3, 1)
48
48
 
49
49
 
50
- class TestCatalanPiApprox(unittest.TestCase):
50
+ class TestEulerHermannPiApprox(unittest.TestCase):
51
51
  def test_converges_to_pi(self):
52
52
  # Regression check: this used to converge to 16*arctan(1/2) =~ 7.418,
53
53
  # not pi, due to a double-counted scaling factor.
54
- approx = catalan_pi_approx(100)
54
+ approx = euler_hermann_pi_approx(100)
55
55
  self.assertLess(abs(approx - PI_REFERENCE), Decimal("1e-40"))
56
56
 
57
57
  def test_more_terms_is_more_accurate(self):
58
- err_10 = abs(catalan_pi_approx(10) - PI_REFERENCE)
59
- err_50 = abs(catalan_pi_approx(50) - PI_REFERENCE)
58
+ err_10 = abs(euler_hermann_pi_approx(10) - PI_REFERENCE)
59
+ err_50 = abs(euler_hermann_pi_approx(50) - PI_REFERENCE)
60
60
  self.assertLess(err_50, err_10)
61
61
 
62
62
 
@@ -82,5 +82,38 @@ class TestECPointAdd(unittest.TestCase):
82
82
  ec_point_add(3, 6, 3, self.P - 6, self.A, self.P)
83
83
 
84
84
 
85
+ class TestCRT(unittest.TestCase):
86
+ def test_classic_coprime_case(self):
87
+ x, m = crt_solve([2, 3, 2], [3, 5, 7])
88
+ self.assertEqual((x, m), (23, 105))
89
+ self.assertEqual(x % 3, 2)
90
+ self.assertEqual(x % 5, 3)
91
+ self.assertEqual(x % 7, 2)
92
+
93
+ def test_non_coprime_consistent(self):
94
+ x, m = crt_solve([2, 2], [4, 6])
95
+ self.assertEqual((x, m), (2, 12))
96
+
97
+ def test_non_coprime_inconsistent_raises(self):
98
+ with self.assertRaises(ValueError):
99
+ crt_solve([1, 2], [4, 6])
100
+
101
+ def test_single_congruence(self):
102
+ self.assertEqual(crt_solve([5], [11]), (5, 11))
103
+
104
+ def test_mismatched_lengths_raises(self):
105
+ with self.assertRaises(ValueError):
106
+ crt_solve([2, 3], [3, 5, 7])
107
+
108
+ def test_empty_raises(self):
109
+ with self.assertRaises(ValueError):
110
+ crt_solve([], [])
111
+
112
+ def test_negative_remainder_normalized(self):
113
+ x, m = crt_solve([-1, 3], [5, 7])
114
+ self.assertEqual(x % 5, 4) # -1 mod 5 == 4
115
+ self.assertEqual(x % 7, 3)
116
+
117
+
85
118
  if __name__ == "__main__":
86
119
  unittest.main()
File without changes
File without changes
File without changes