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 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,12 @@
1
+ module RhMath
2
+ ComparisonResult = Struct.new(
3
+ :verdict,
4
+ :comparison_method,
5
+ :reference_value_used_text,
6
+ :observed_value_text,
7
+ :tolerance_used_text,
8
+ :difference_text,
9
+ :notes,
10
+ keyword_init: true
11
+ )
12
+ 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,11 @@
1
+ module RhMath
2
+ PointwiseResult = Struct.new(
3
+ :theorem_key,
4
+ :x_value,
5
+ :lhs_value,
6
+ :rhs_value,
7
+ :verdict,
8
+ :notes,
9
+ keyword_init: true
10
+ )
11
+ 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
@@ -0,0 +1,3 @@
1
+ module RhMath
2
+ VERSION = "0.1.0".freeze
3
+ end
@@ -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: []