rh_math 0.1.0
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.
- checksums.yaml +7 -0
- data/CITATION.cff +12 -0
- data/LICENSE.txt +21 -0
- data/README.md +255 -0
- data/lib/rh_math/arithmetic_functions.rb +29 -0
- data/lib/rh_math/bounds/schoenfeld1976.rb +35 -0
- data/lib/rh_math/comparators/absolute_tolerance.rb +38 -0
- data/lib/rh_math/comparators/exact_text.rb +23 -0
- data/lib/rh_math/comparison_result.rb +12 -0
- data/lib/rh_math/evaluators/schoenfeld_pointwise.rb +54 -0
- data/lib/rh_math/numeric_parser.rb +16 -0
- data/lib/rh_math/pointwise_result.rb +11 -0
- data/lib/rh_math/prime_support.rb +30 -0
- data/lib/rh_math/special_functions.rb +43 -0
- data/lib/rh_math/version.rb +3 -0
- data/lib/rh_math/zeta.rb +55 -0
- data/lib/rh_math.rb +15 -0
- data/rh_math.gemspec +27 -0
- metadata +93 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 860730b2a69b5d2063e5d069f4aca61fac3bdf9599691ef3c3ae02bb93f3b724
|
|
4
|
+
data.tar.gz: 043745b23d8c68e8edb4dfe21e4d20a19039775ca8fc02b64b438751afc24bbe
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: bf61ee583de0bbe1ab09fed6094d25837718cdc8a6a82d7d737e9e34d69858c2b058619b7c01a597e7cd6b7b3670ca7bb7be6c6c5e740e50c7342d27306642d9
|
|
7
|
+
data.tar.gz: 5f276317ecffecb86f0b6c565c137b2447567e87327957faa26135c439fb1a286e209bf6a119362c91a090368eb6bde8e86a8180adf87467421620bce0243040
|
data/CITATION.cff
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
cff-version: 1.2.0
|
|
2
|
+
title: rh_math
|
|
3
|
+
message: "If you use this software, please cite it as below."
|
|
4
|
+
type: software
|
|
5
|
+
authors:
|
|
6
|
+
- family-names: Bass
|
|
7
|
+
given-names: Tim
|
|
8
|
+
email: tim@unix.com
|
|
9
|
+
repository-code: "https://github.com/unixneo/rh_math"
|
|
10
|
+
license: MIT
|
|
11
|
+
version: 0.1.0
|
|
12
|
+
date-released: 2026-04-09
|
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026
|
|
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.
|
data/README.md
ADDED
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
# rh_math
|
|
2
|
+
|
|
3
|
+
## Status and Scope
|
|
4
|
+
|
|
5
|
+
This is a research-oriented, partial implementation focused on bounded evaluation of selected analytic number theory results (for example, Schoenfeld 1976 bounds and real `zeta(s)` for `s > 1`).
|
|
6
|
+
|
|
7
|
+
It is **not** a general-purpose number theory library.
|
|
8
|
+
|
|
9
|
+
The implementation is intentionally limited, explicit, and designed to support reproducible evaluation of LLM-generated code against peer-reviewed mathematical results.
|
|
10
|
+
|
|
11
|
+
`rh_math` is a pure-Ruby research gem for the `rh_llm_benchmark` project.
|
|
12
|
+
|
|
13
|
+
It provides small, explicit numeric parsing and comparison utilities, plus early arithmetic/bound routines used by executable pointwise benchmark checks.
|
|
14
|
+
|
|
15
|
+
## Purpose
|
|
16
|
+
|
|
17
|
+
- keep computation/comparison logic outside Rails orchestration and persistence
|
|
18
|
+
- provide a testable pure-Ruby layer for benchmark evaluation logic
|
|
19
|
+
- support incremental, auditable growth of RH-related routines
|
|
20
|
+
|
|
21
|
+
## Current Scope
|
|
22
|
+
|
|
23
|
+
Implemented today:
|
|
24
|
+
|
|
25
|
+
- `RhMath::NumericParser` for conservative decimal/scientific parsing to `BigDecimal`
|
|
26
|
+
- `RhMath::Comparators::ExactText`
|
|
27
|
+
- `RhMath::Comparators::AbsoluteTolerance`
|
|
28
|
+
- arithmetic helpers:
|
|
29
|
+
- `RhMath::PrimeSupport.primes_up_to`
|
|
30
|
+
- `RhMath::ArithmeticFunctions.prime_pi`
|
|
31
|
+
- `RhMath::ArithmeticFunctions.theta`
|
|
32
|
+
- `RhMath::ArithmeticFunctions.psi`
|
|
33
|
+
- Schoenfeld 1976 RHS bound helpers:
|
|
34
|
+
- `RhMath::Bounds::Schoenfeld1976.psi_rh_upper_bound`
|
|
35
|
+
- `RhMath::Bounds::Schoenfeld1976.theta_rh_upper_bound`
|
|
36
|
+
- `RhMath::Bounds::Schoenfeld1976.pi_li_rh_upper_bound`
|
|
37
|
+
- pointwise evaluators:
|
|
38
|
+
- `RhMath::Evaluators::SchoenfeldPointwise.evaluate_psi_bound`
|
|
39
|
+
- `RhMath::Evaluators::SchoenfeldPointwise.evaluate_theta_bound`
|
|
40
|
+
- `RhMath::Evaluators::SchoenfeldPointwise.evaluate_pi_li_bound`
|
|
41
|
+
- validated across representative moderate-x ranges used by the harness seed corpus
|
|
42
|
+
- special function:
|
|
43
|
+
- `RhMath::SpecialFunctions.li(x)`
|
|
44
|
+
- bounded zeta routines:
|
|
45
|
+
- `RhMath::Zeta.zeta_real(s, terms: N)` for real `s > 1` via truncated Dirichlet series
|
|
46
|
+
- `RhMath::Zeta.zeta_real_with_error(s, terms: N)` returning value plus a rigorous truncation tail bound
|
|
47
|
+
|
|
48
|
+
## Intended Use
|
|
49
|
+
|
|
50
|
+
- evaluation of LLM-generated code against known mathematical bounds
|
|
51
|
+
- reproducible research workflows
|
|
52
|
+
- small-scale numeric validation of analytic results
|
|
53
|
+
|
|
54
|
+
Not intended for:
|
|
55
|
+
|
|
56
|
+
- production numerical computing
|
|
57
|
+
- high-precision analytic number theory
|
|
58
|
+
- large-scale prime computations
|
|
59
|
+
|
|
60
|
+
## Current Non-Goals
|
|
61
|
+
|
|
62
|
+
- proving or disproving RH
|
|
63
|
+
- full zeta-function evaluation (complex inputs, analytic continuation, or critical-strip work)
|
|
64
|
+
- zero-finding
|
|
65
|
+
- symbolic theorem parsing
|
|
66
|
+
- complex arithmetic support
|
|
67
|
+
- production-grade high-precision analytic number theory engine
|
|
68
|
+
|
|
69
|
+
## Local Development Setup
|
|
70
|
+
|
|
71
|
+
From the gem repo:
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
cd ../rh_math
|
|
75
|
+
ruby -v
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Run tests file-by-file:
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
for f in test/*_test.rb; do ruby -Ilib:test "$f" || exit 1; done
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## Reproducibility
|
|
85
|
+
|
|
86
|
+
This gem is consumed by `rh_llm_benchmark` via local path dependency:
|
|
87
|
+
|
|
88
|
+
```ruby
|
|
89
|
+
gem "rh_math", path: "../rh_math"
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Key commands:
|
|
93
|
+
|
|
94
|
+
- gem tests: `for f in test/*_test.rb; do ruby -Ilib:test "$f" || exit 1; done`
|
|
95
|
+
- zeta reporting via Rails harness: `bin/rails "demo:report_zeta_real"`
|
|
96
|
+
|
|
97
|
+
## Rails Integration (Local Path)
|
|
98
|
+
|
|
99
|
+
`rh_llm_benchmark` consumes this gem by local path in its `Gemfile`:
|
|
100
|
+
|
|
101
|
+
```ruby
|
|
102
|
+
gem "rh_math", path: "../rh_math"
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
After gem changes, run in Rails repo:
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
cd ../rh_llm_benchmark
|
|
109
|
+
bundle install
|
|
110
|
+
bin/rails runner "puts RhMath::VERSION"
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## Public Modules and Classes
|
|
114
|
+
|
|
115
|
+
- `RhMath::NumericParser`
|
|
116
|
+
- `RhMath::Comparators::ExactText`
|
|
117
|
+
- `RhMath::Comparators::AbsoluteTolerance`
|
|
118
|
+
- `RhMath::PrimeSupport`
|
|
119
|
+
- `RhMath::ArithmeticFunctions`
|
|
120
|
+
- `RhMath::SpecialFunctions`
|
|
121
|
+
- `RhMath::Zeta`
|
|
122
|
+
- `RhMath::Bounds::Schoenfeld1976`
|
|
123
|
+
- `RhMath::Evaluators::SchoenfeldPointwise`
|
|
124
|
+
- `RhMath::ComparisonResult` (struct)
|
|
125
|
+
- `RhMath::PointwiseResult` (struct)
|
|
126
|
+
|
|
127
|
+
## Usage Examples
|
|
128
|
+
|
|
129
|
+
### Numeric parsing
|
|
130
|
+
|
|
131
|
+
```ruby
|
|
132
|
+
require "rh_math"
|
|
133
|
+
|
|
134
|
+
value = RhMath::NumericParser.parse_decimal("1.25e-3")
|
|
135
|
+
# => #<BigDecimal:...,'0.125E-2',...>
|
|
136
|
+
|
|
137
|
+
RhMath::NumericParser.parse_decimal("not-a-number")
|
|
138
|
+
# => nil
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### Exact text comparison
|
|
142
|
+
|
|
143
|
+
```ruby
|
|
144
|
+
require "rh_math"
|
|
145
|
+
|
|
146
|
+
result = RhMath::Comparators::ExactText.compare(
|
|
147
|
+
reference_text: " alpha ",
|
|
148
|
+
observed_text: "alpha"
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
result.verdict
|
|
152
|
+
# => "pass"
|
|
153
|
+
result.difference_text
|
|
154
|
+
# => "exact match"
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
### Absolute tolerance comparison
|
|
158
|
+
|
|
159
|
+
```ruby
|
|
160
|
+
require "rh_math"
|
|
161
|
+
|
|
162
|
+
result = RhMath::Comparators::AbsoluteTolerance.compare(
|
|
163
|
+
reference_text: "10.0",
|
|
164
|
+
observed_text: "10.12",
|
|
165
|
+
tolerance_text: "0.2"
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
result.verdict
|
|
169
|
+
# => "pass"
|
|
170
|
+
result.difference_text
|
|
171
|
+
# => "0.12"
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
### Arithmetic functions (`prime_pi`, `theta`, `psi`)
|
|
175
|
+
|
|
176
|
+
```ruby
|
|
177
|
+
require "rh_math"
|
|
178
|
+
|
|
179
|
+
RhMath::ArithmeticFunctions.prime_pi(10)
|
|
180
|
+
# => 4
|
|
181
|
+
|
|
182
|
+
RhMath::ArithmeticFunctions.theta(10)
|
|
183
|
+
# => sum(log p) for p <= 10
|
|
184
|
+
|
|
185
|
+
RhMath::ArithmeticFunctions.psi(10)
|
|
186
|
+
# => sum(log p) over prime powers p^k <= 10
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
### `li(x)` usage
|
|
190
|
+
|
|
191
|
+
```ruby
|
|
192
|
+
require "rh_math"
|
|
193
|
+
|
|
194
|
+
RhMath::SpecialFunctions.li(3000)
|
|
195
|
+
# => Float approximation of offset logarithmic integral li(3000)
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
### `zeta_real(s, terms:)` usage (real s > 1)
|
|
199
|
+
|
|
200
|
+
```ruby
|
|
201
|
+
require "rh_math"
|
|
202
|
+
|
|
203
|
+
approx = RhMath::Zeta.zeta_real(2.0, terms: 10_000)
|
|
204
|
+
# => ~1.644834 (approaches pi^2 / 6 as terms increase)
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
### `zeta_real_with_error(s, terms:)` usage (rigorous truncation bound)
|
|
208
|
+
|
|
209
|
+
```ruby
|
|
210
|
+
require "rh_math"
|
|
211
|
+
|
|
212
|
+
bounded = RhMath::Zeta.zeta_real_with_error(2.0, terms: 10_000)
|
|
213
|
+
# => {
|
|
214
|
+
# value: 1.644834..., # partial sum
|
|
215
|
+
# error_bound: 0.000099995...,# rigorous upper bound on tail truncation error
|
|
216
|
+
# lower_bound: value,
|
|
217
|
+
# upper_bound: value + error_bound
|
|
218
|
+
# }
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
### Schoenfeld pointwise evaluators
|
|
222
|
+
|
|
223
|
+
```ruby
|
|
224
|
+
require "rh_math"
|
|
225
|
+
|
|
226
|
+
psi_eval = RhMath::Evaluators::SchoenfeldPointwise.evaluate_psi_bound(1000)
|
|
227
|
+
psi_eval.verdict
|
|
228
|
+
# => "pass" (expected for seeded benchmark path)
|
|
229
|
+
|
|
230
|
+
theta_eval = RhMath::Evaluators::SchoenfeldPointwise.evaluate_theta_bound(1000)
|
|
231
|
+
pi_li_eval = RhMath::Evaluators::SchoenfeldPointwise.evaluate_pi_li_bound(5000)
|
|
232
|
+
|
|
233
|
+
pi_li_eval.theorem_key
|
|
234
|
+
# => "schoenfeld_1976_corollary_1_eq_6_18"
|
|
235
|
+
|
|
236
|
+
# Typical seeded ranges in rh_llm_benchmark:
|
|
237
|
+
# psi: 100, 1000, 5000
|
|
238
|
+
# theta: 700, 1000, 5000
|
|
239
|
+
# pi-li: 3000, 5000, 10000, 20000
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
## Limitations and Caveats
|
|
243
|
+
|
|
244
|
+
- This is research software under active development.
|
|
245
|
+
- Mathematical coverage is partial and intentionally narrow.
|
|
246
|
+
- `zeta_real(s, terms:)` and `zeta_real_with_error(s, terms:)` are real-domain only (`s > 1`).
|
|
247
|
+
- No analytic continuation is implemented.
|
|
248
|
+
- No complex arithmetic is implemented.
|
|
249
|
+
- `li(x)` is a moderate-`x`, Float-based numerical routine.
|
|
250
|
+
- `zeta_real_with_error(s, terms:)` provides a rigorous truncation-tail bound `N^(1-s)/(s-1)` for the partial sum; it does **not** bound floating-point roundoff error.
|
|
251
|
+
- Arithmetic routines are straightforward and not optimized for large-scale computation.
|
|
252
|
+
|
|
253
|
+
## Research Status Note
|
|
254
|
+
|
|
255
|
+
This gem currently supports benchmark architecture validation and early executable pointwise checks. It does not yet implement full RH computation workflows.
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
module RhMath
|
|
2
|
+
module ArithmeticFunctions
|
|
3
|
+
def self.prime_pi(n)
|
|
4
|
+
PrimeSupport.primes_up_to(n).length
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
def self.theta(n)
|
|
8
|
+
PrimeSupport.primes_up_to(n).sum { |p| Math.log(p) }
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
# Chebyshev psi: sum(log p) for each prime power p^k <= n.
|
|
12
|
+
def self.psi(n)
|
|
13
|
+
limit = Integer(n)
|
|
14
|
+
return 0.0 if limit < 2
|
|
15
|
+
|
|
16
|
+
PrimeSupport.primes_up_to(limit).sum do |prime|
|
|
17
|
+
value = prime
|
|
18
|
+
term_sum = 0.0
|
|
19
|
+
|
|
20
|
+
while value <= limit
|
|
21
|
+
term_sum += Math.log(prime)
|
|
22
|
+
value *= prime
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
term_sum
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
module RhMath
|
|
2
|
+
module Bounds
|
|
3
|
+
module Schoenfeld1976
|
|
4
|
+
COEFFICIENT = 1.0 / (8.0 * Math::PI)
|
|
5
|
+
|
|
6
|
+
def self.psi_rh_upper_bound(x)
|
|
7
|
+
squared_log_bound(x)
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def self.theta_rh_upper_bound(x)
|
|
11
|
+
squared_log_bound(x)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# Schoenfeld 1976 Corollary 1, eq. (6.18) RHS term.
|
|
15
|
+
def self.pi_li_rh_upper_bound(x)
|
|
16
|
+
value = positive_float(x)
|
|
17
|
+
COEFFICIENT * Math.sqrt(value) * Math.log(value)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def self.squared_log_bound(x)
|
|
21
|
+
value = positive_float(x)
|
|
22
|
+
COEFFICIENT * Math.sqrt(value) * (Math.log(value)**2)
|
|
23
|
+
end
|
|
24
|
+
private_class_method :squared_log_bound
|
|
25
|
+
|
|
26
|
+
def self.positive_float(x)
|
|
27
|
+
value = Float(x)
|
|
28
|
+
raise ArgumentError, "x must be positive" unless value.positive?
|
|
29
|
+
|
|
30
|
+
value
|
|
31
|
+
end
|
|
32
|
+
private_class_method :positive_float
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
module RhMath
|
|
2
|
+
module Comparators
|
|
3
|
+
class AbsoluteTolerance
|
|
4
|
+
COMPARISON_METHOD = "absolute_tolerance_numeric".freeze
|
|
5
|
+
|
|
6
|
+
def self.compare(reference_text:, observed_text:, tolerance_text:)
|
|
7
|
+
reference_value = NumericParser.parse_decimal(reference_text)
|
|
8
|
+
observed_value = NumericParser.parse_decimal(observed_text)
|
|
9
|
+
tolerance_value = NumericParser.parse_decimal(tolerance_text)
|
|
10
|
+
|
|
11
|
+
if reference_value.nil? || observed_value.nil? || tolerance_value.nil? || tolerance_value.negative?
|
|
12
|
+
return ComparisonResult.new(
|
|
13
|
+
verdict: "error",
|
|
14
|
+
comparison_method: COMPARISON_METHOD,
|
|
15
|
+
reference_value_used_text: reference_text,
|
|
16
|
+
observed_value_text: observed_text,
|
|
17
|
+
tolerance_used_text: tolerance_text,
|
|
18
|
+
difference_text: nil,
|
|
19
|
+
notes: "Invalid numeric input for expected, observed, or tolerance text."
|
|
20
|
+
)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
difference = (observed_value - reference_value).abs
|
|
24
|
+
verdict = difference <= tolerance_value ? "pass" : "fail"
|
|
25
|
+
|
|
26
|
+
ComparisonResult.new(
|
|
27
|
+
verdict: verdict,
|
|
28
|
+
comparison_method: COMPARISON_METHOD,
|
|
29
|
+
reference_value_used_text: reference_text,
|
|
30
|
+
observed_value_text: observed_text,
|
|
31
|
+
tolerance_used_text: tolerance_text,
|
|
32
|
+
difference_text: difference.to_s("F"),
|
|
33
|
+
notes: "Absolute tolerance numeric comparison using BigDecimal."
|
|
34
|
+
)
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
module RhMath
|
|
2
|
+
module Comparators
|
|
3
|
+
class ExactText
|
|
4
|
+
COMPARISON_METHOD = "demo_exact_text_match".freeze
|
|
5
|
+
|
|
6
|
+
def self.compare(reference_text:, observed_text:)
|
|
7
|
+
reference = reference_text.to_s.strip
|
|
8
|
+
observed = observed_text.to_s.strip
|
|
9
|
+
matches = reference == observed
|
|
10
|
+
|
|
11
|
+
ComparisonResult.new(
|
|
12
|
+
verdict: matches ? "pass" : "fail",
|
|
13
|
+
comparison_method: COMPARISON_METHOD,
|
|
14
|
+
reference_value_used_text: reference_text,
|
|
15
|
+
observed_value_text: observed_text,
|
|
16
|
+
tolerance_used_text: nil,
|
|
17
|
+
difference_text: matches ? "exact match" : "text differs",
|
|
18
|
+
notes: "Exact text comparator (trimmed string equality)."
|
|
19
|
+
)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
module RhMath
|
|
2
|
+
module Evaluators
|
|
3
|
+
module SchoenfeldPointwise
|
|
4
|
+
PSI_THEOREM_KEY = "schoenfeld_1976_theorem_10_eq_6_2".freeze
|
|
5
|
+
THETA_THEOREM_KEY = "schoenfeld_1976_theorem_10_eq_6_3".freeze
|
|
6
|
+
PI_LI_THEOREM_KEY = "schoenfeld_1976_corollary_1_eq_6_18".freeze
|
|
7
|
+
|
|
8
|
+
def self.evaluate_psi_bound(x)
|
|
9
|
+
integer_x = Integer(x)
|
|
10
|
+
lhs = (ArithmeticFunctions.psi(integer_x) - integer_x).abs
|
|
11
|
+
rhs = Bounds::Schoenfeld1976.psi_rh_upper_bound(integer_x)
|
|
12
|
+
|
|
13
|
+
PointwiseResult.new(
|
|
14
|
+
theorem_key: PSI_THEOREM_KEY,
|
|
15
|
+
x_value: integer_x,
|
|
16
|
+
lhs_value: lhs,
|
|
17
|
+
rhs_value: rhs,
|
|
18
|
+
verdict: lhs <= rhs ? "pass" : "fail",
|
|
19
|
+
notes: "Pointwise check of Schoenfeld 1976 Theorem 10 eq. (6.2)."
|
|
20
|
+
)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def self.evaluate_theta_bound(x)
|
|
24
|
+
integer_x = Integer(x)
|
|
25
|
+
lhs = (ArithmeticFunctions.theta(integer_x) - integer_x).abs
|
|
26
|
+
rhs = Bounds::Schoenfeld1976.theta_rh_upper_bound(integer_x)
|
|
27
|
+
|
|
28
|
+
PointwiseResult.new(
|
|
29
|
+
theorem_key: THETA_THEOREM_KEY,
|
|
30
|
+
x_value: integer_x,
|
|
31
|
+
lhs_value: lhs,
|
|
32
|
+
rhs_value: rhs,
|
|
33
|
+
verdict: lhs <= rhs ? "pass" : "fail",
|
|
34
|
+
notes: "Pointwise check of Schoenfeld 1976 Theorem 10 eq. (6.3)."
|
|
35
|
+
)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def self.evaluate_pi_li_bound(x)
|
|
39
|
+
integer_x = Integer(x)
|
|
40
|
+
lhs = (ArithmeticFunctions.prime_pi(integer_x) - SpecialFunctions.li(integer_x)).abs
|
|
41
|
+
rhs = Bounds::Schoenfeld1976.pi_li_rh_upper_bound(integer_x)
|
|
42
|
+
|
|
43
|
+
PointwiseResult.new(
|
|
44
|
+
theorem_key: PI_LI_THEOREM_KEY,
|
|
45
|
+
x_value: integer_x,
|
|
46
|
+
lhs_value: lhs,
|
|
47
|
+
rhs_value: rhs,
|
|
48
|
+
verdict: lhs <= rhs ? "pass" : "fail",
|
|
49
|
+
notes: "Pointwise check of Schoenfeld 1976 Corollary 1 eq. (6.18)."
|
|
50
|
+
)
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
require "bigdecimal"
|
|
2
|
+
|
|
3
|
+
module RhMath
|
|
4
|
+
class NumericParser
|
|
5
|
+
NUMERIC_PATTERN = /\A[+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?\z/.freeze
|
|
6
|
+
|
|
7
|
+
def self.parse_decimal(value)
|
|
8
|
+
text = value.to_s.strip
|
|
9
|
+
return nil unless NUMERIC_PATTERN.match?(text)
|
|
10
|
+
|
|
11
|
+
BigDecimal(text)
|
|
12
|
+
rescue ArgumentError
|
|
13
|
+
nil
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
module RhMath
|
|
2
|
+
module PrimeSupport
|
|
3
|
+
def self.primes_up_to(n)
|
|
4
|
+
limit = Integer(n)
|
|
5
|
+
return [] if limit < 2
|
|
6
|
+
|
|
7
|
+
primes = []
|
|
8
|
+
(2..limit).each do |candidate|
|
|
9
|
+
primes << candidate if prime?(candidate)
|
|
10
|
+
end
|
|
11
|
+
primes
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def self.prime?(n)
|
|
15
|
+
return false if n < 2
|
|
16
|
+
return true if n == 2
|
|
17
|
+
return false if n.even?
|
|
18
|
+
|
|
19
|
+
divisor = 3
|
|
20
|
+
while divisor * divisor <= n
|
|
21
|
+
return false if (n % divisor).zero?
|
|
22
|
+
|
|
23
|
+
divisor += 2
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
true
|
|
27
|
+
end
|
|
28
|
+
private_class_method :prime?
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
module RhMath
|
|
2
|
+
module SpecialFunctions
|
|
3
|
+
# Offset logarithmic integral li(x) for x > 1, implemented as:
|
|
4
|
+
# li(x) = li(2) + integral_{2}^{x} dt / log(t)
|
|
5
|
+
# using composite Simpson's rule on [2, x].
|
|
6
|
+
#
|
|
7
|
+
# This routine is intended for moderate x used by pointwise benchmark checks,
|
|
8
|
+
# not high-precision analytic number theory work.
|
|
9
|
+
LI_AT_2 = 1.0451637801174928
|
|
10
|
+
|
|
11
|
+
def self.li(x, steps: nil)
|
|
12
|
+
value = Float(x)
|
|
13
|
+
raise ArgumentError, "x must be >= 2" if value < 2.0
|
|
14
|
+
return LI_AT_2 if value == 2.0
|
|
15
|
+
|
|
16
|
+
n = steps || default_steps_for(value)
|
|
17
|
+
n += 1 if n.odd?
|
|
18
|
+
|
|
19
|
+
h = (value - 2.0) / n
|
|
20
|
+
sum = integrand(2.0) + integrand(value)
|
|
21
|
+
|
|
22
|
+
i = 1
|
|
23
|
+
while i < n
|
|
24
|
+
t = 2.0 + i * h
|
|
25
|
+
weight = i.odd? ? 4.0 : 2.0
|
|
26
|
+
sum += weight * integrand(t)
|
|
27
|
+
i += 1
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
LI_AT_2 + (h / 3.0) * sum
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def self.integrand(t)
|
|
34
|
+
1.0 / Math.log(t)
|
|
35
|
+
end
|
|
36
|
+
private_class_method :integrand
|
|
37
|
+
|
|
38
|
+
def self.default_steps_for(x)
|
|
39
|
+
[[(x / 2.0).to_i, 2_000].max, 40_000].min
|
|
40
|
+
end
|
|
41
|
+
private_class_method :default_steps_for
|
|
42
|
+
end
|
|
43
|
+
end
|
data/lib/rh_math/zeta.rb
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
module RhMath
|
|
2
|
+
module Zeta
|
|
3
|
+
DEFAULT_TERMS = 10_000
|
|
4
|
+
|
|
5
|
+
# Truncated Dirichlet series approximation for real s > 1:
|
|
6
|
+
# zeta(s) ≈ sum_{n=1..terms} 1 / n^s
|
|
7
|
+
# Convergence is faster for larger s and slower as s approaches 1.
|
|
8
|
+
# Accuracy is controlled by the selected truncation length (terms).
|
|
9
|
+
def self.zeta_real(s, terms: DEFAULT_TERMS)
|
|
10
|
+
validated_inputs = validate_inputs!(s, terms)
|
|
11
|
+
partial_sum_for(validated_inputs[:s], validated_inputs[:terms])
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# Truncated Dirichlet series with a classical integral-tail bound for s > 1:
|
|
15
|
+
# tail <= integral_N^infinity x^{-s} dx = N^(1-s)/(s-1)
|
|
16
|
+
# This gives a rigorous upper bound on truncation error when using N terms.
|
|
17
|
+
def self.zeta_real_with_error(s, terms: DEFAULT_TERMS)
|
|
18
|
+
validated_inputs = validate_inputs!(s, terms)
|
|
19
|
+
exponent = validated_inputs[:s]
|
|
20
|
+
n_terms = validated_inputs[:terms]
|
|
21
|
+
|
|
22
|
+
partial_sum = partial_sum_for(exponent, n_terms)
|
|
23
|
+
tail_bound = (n_terms**(1.0 - exponent)) / (exponent - 1.0)
|
|
24
|
+
|
|
25
|
+
{
|
|
26
|
+
value: partial_sum,
|
|
27
|
+
error_bound: tail_bound,
|
|
28
|
+
lower_bound: partial_sum,
|
|
29
|
+
upper_bound: partial_sum + tail_bound
|
|
30
|
+
}
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def self.validate_inputs!(s, terms)
|
|
34
|
+
exponent = Float(s)
|
|
35
|
+
n_terms = Integer(terms)
|
|
36
|
+
|
|
37
|
+
raise ArgumentError, "s must be > 1 for this truncated real-series routine" unless exponent > 1.0
|
|
38
|
+
raise ArgumentError, "terms must be >= 1" if n_terms < 1
|
|
39
|
+
|
|
40
|
+
{ s: exponent, terms: n_terms }
|
|
41
|
+
end
|
|
42
|
+
private_class_method :validate_inputs!
|
|
43
|
+
|
|
44
|
+
def self.partial_sum_for(exponent, n_terms)
|
|
45
|
+
sum = 0.0
|
|
46
|
+
n = 1
|
|
47
|
+
while n <= n_terms
|
|
48
|
+
sum += 1.0 / (n**exponent)
|
|
49
|
+
n += 1
|
|
50
|
+
end
|
|
51
|
+
sum
|
|
52
|
+
end
|
|
53
|
+
private_class_method :partial_sum_for
|
|
54
|
+
end
|
|
55
|
+
end
|
data/lib/rh_math.rb
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
require_relative "rh_math/version"
|
|
2
|
+
require_relative "rh_math/comparison_result"
|
|
3
|
+
require_relative "rh_math/pointwise_result"
|
|
4
|
+
require_relative "rh_math/numeric_parser"
|
|
5
|
+
require_relative "rh_math/prime_support"
|
|
6
|
+
require_relative "rh_math/arithmetic_functions"
|
|
7
|
+
require_relative "rh_math/special_functions"
|
|
8
|
+
require_relative "rh_math/zeta"
|
|
9
|
+
require_relative "rh_math/bounds/schoenfeld1976"
|
|
10
|
+
require_relative "rh_math/evaluators/schoenfeld_pointwise"
|
|
11
|
+
require_relative "rh_math/comparators/exact_text"
|
|
12
|
+
require_relative "rh_math/comparators/absolute_tolerance"
|
|
13
|
+
|
|
14
|
+
module RhMath
|
|
15
|
+
end
|
data/rh_math.gemspec
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
require_relative "lib/rh_math/version"
|
|
2
|
+
|
|
3
|
+
Gem::Specification.new do |spec|
|
|
4
|
+
spec.name = "rh_math"
|
|
5
|
+
spec.version = RhMath::VERSION
|
|
6
|
+
spec.authors = ["Tim Bass"]
|
|
7
|
+
spec.email = ["tim@unix.com"]
|
|
8
|
+
|
|
9
|
+
spec.summary = "Bounded pure-Ruby math routines for rh_llm_benchmark"
|
|
10
|
+
spec.description = "A research-oriented pure-Ruby math layer for bounded analytic number theory evaluation workflows."
|
|
11
|
+
spec.homepage = "https://github.com/unixneo/rh_math"
|
|
12
|
+
spec.license = "MIT"
|
|
13
|
+
spec.required_ruby_version = ">= 3.0"
|
|
14
|
+
|
|
15
|
+
spec.metadata["homepage_uri"] = spec.homepage
|
|
16
|
+
spec.metadata["source_code_uri"] = spec.homepage
|
|
17
|
+
spec.metadata["changelog_uri"] = "#{spec.homepage}/releases"
|
|
18
|
+
spec.metadata["rubygems_mfa_required"] = "true"
|
|
19
|
+
|
|
20
|
+
spec.files = Dir.chdir(__dir__) do
|
|
21
|
+
Dir["lib/**/*.rb", "README.md", "LICENSE.txt", "CITATION.cff", "rh_math.gemspec"]
|
|
22
|
+
end
|
|
23
|
+
spec.require_paths = ["lib"]
|
|
24
|
+
|
|
25
|
+
spec.add_development_dependency "minitest", "~> 5.0"
|
|
26
|
+
spec.add_development_dependency "rake", "~> 13.0"
|
|
27
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: rh_math
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Tim Bass
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2026-04-09 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: minitest
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - "~>"
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '5.0'
|
|
20
|
+
type: :development
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - "~>"
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: '5.0'
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: rake
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - "~>"
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: '13.0'
|
|
34
|
+
type: :development
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - "~>"
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: '13.0'
|
|
41
|
+
description: A research-oriented pure-Ruby math layer for bounded analytic number
|
|
42
|
+
theory evaluation workflows.
|
|
43
|
+
email:
|
|
44
|
+
- tim@unix.com
|
|
45
|
+
executables: []
|
|
46
|
+
extensions: []
|
|
47
|
+
extra_rdoc_files: []
|
|
48
|
+
files:
|
|
49
|
+
- CITATION.cff
|
|
50
|
+
- LICENSE.txt
|
|
51
|
+
- README.md
|
|
52
|
+
- lib/rh_math.rb
|
|
53
|
+
- lib/rh_math/arithmetic_functions.rb
|
|
54
|
+
- lib/rh_math/bounds/schoenfeld1976.rb
|
|
55
|
+
- lib/rh_math/comparators/absolute_tolerance.rb
|
|
56
|
+
- lib/rh_math/comparators/exact_text.rb
|
|
57
|
+
- lib/rh_math/comparison_result.rb
|
|
58
|
+
- lib/rh_math/evaluators/schoenfeld_pointwise.rb
|
|
59
|
+
- lib/rh_math/numeric_parser.rb
|
|
60
|
+
- lib/rh_math/pointwise_result.rb
|
|
61
|
+
- lib/rh_math/prime_support.rb
|
|
62
|
+
- lib/rh_math/special_functions.rb
|
|
63
|
+
- lib/rh_math/version.rb
|
|
64
|
+
- lib/rh_math/zeta.rb
|
|
65
|
+
- rh_math.gemspec
|
|
66
|
+
homepage: https://github.com/unixneo/rh_math
|
|
67
|
+
licenses:
|
|
68
|
+
- MIT
|
|
69
|
+
metadata:
|
|
70
|
+
homepage_uri: https://github.com/unixneo/rh_math
|
|
71
|
+
source_code_uri: https://github.com/unixneo/rh_math
|
|
72
|
+
changelog_uri: https://github.com/unixneo/rh_math/releases
|
|
73
|
+
rubygems_mfa_required: 'true'
|
|
74
|
+
post_install_message:
|
|
75
|
+
rdoc_options: []
|
|
76
|
+
require_paths:
|
|
77
|
+
- lib
|
|
78
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
79
|
+
requirements:
|
|
80
|
+
- - ">="
|
|
81
|
+
- !ruby/object:Gem::Version
|
|
82
|
+
version: '3.0'
|
|
83
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
84
|
+
requirements:
|
|
85
|
+
- - ">="
|
|
86
|
+
- !ruby/object:Gem::Version
|
|
87
|
+
version: '0'
|
|
88
|
+
requirements: []
|
|
89
|
+
rubygems_version: 3.4.10
|
|
90
|
+
signing_key:
|
|
91
|
+
specification_version: 4
|
|
92
|
+
summary: Bounded pure-Ruby math routines for rh_llm_benchmark
|
|
93
|
+
test_files: []
|