Skip to content

Commit e36f90e

Browse files
Implement function Riemann Zeta (#2975, #2950)
* Riemann Zeta Function * Big Number zeta and added docs * Original algorithm paper credited * Update index.d.ts * Update riemannZeta.js * Update index.d.ts * Renamed files to reflect zeta * chore: make all the tests pass * chore: refactor `zeta` (WIP) * chore: reuse the validation logic of both number and BigNumber * fix: type definitions of `zeta` * fix: test the accuracy with numbers and BigNumbers (WIP) * chore: make linter happy * docs: fix example outputs * docs: update history * docs: update history * docs: describe the limited precision of `zeta` --------- Co-authored-by: BuildTools <[email protected]> Co-authored-by: Anik Patel <[email protected]>
1 parent 3ab9bc1 commit e36f90e

File tree

8 files changed

+272
-1
lines changed

8 files changed

+272
-1
lines changed

HISTORY.md

+4
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22

33
# unpublished changes since 11.9.1
44

5+
- Extend function `quantileSeq` with support for a `dimension` (#3002).
6+
Thanks @dvd101x.
7+
- Implement #2735: Support indexing with an array of booleans (#2994),
8+
for example `a[[true, false, true]]` and `a[a > 2]`. Thanks @dvd101x.
59
- Fix #2990: `DenseMatrix` can mutate input arrays (#2991).
610

711

src/expression/embeddedDocs/embeddedDocs.js

+2
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,7 @@ import { setUnionDocs } from './function/set/setUnion.js'
184184
import { zpk2tfDocs } from './function/signal/zpk2tf.js'
185185
import { freqzDocs } from './function/signal/freqz.js'
186186
import { erfDocs } from './function/special/erf.js'
187+
import { zetaDocs } from './function/special/zeta.js'
187188
import { madDocs } from './function/statistics/mad.js'
188189
import { maxDocs } from './function/statistics/max.js'
189190
import { meanDocs } from './function/statistics/mean.js'
@@ -527,6 +528,7 @@ export const embeddedDocs = {
527528

528529
// functions - special
529530
erf: erfDocs,
531+
zeta: zetaDocs,
530532

531533
// functions - statistics
532534
cumsum: cumSumDocs,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
export const zetaDocs = {
2+
name: 'zeta',
3+
category: 'Special',
4+
syntax: [
5+
'zeta(s)'
6+
],
7+
description: 'Compute the Riemann Zeta Function using an infinite series and Riemanns Functional Equation for the entire complex plane',
8+
examples: [
9+
'zeta(0.2)',
10+
'zeta(-0.5)',
11+
'zeta(4)'
12+
],
13+
seealso: []
14+
}

src/factoriesAny.js

+1
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ export { createFft } from './function/matrix/fft.js'
9696
export { createIfft } from './function/matrix/ifft.js'
9797
export { createSolveODE } from './function/numeric/solveODE.js'
9898
export { createErf } from './function/special/erf.js'
99+
export { createZeta } from './function/special/zeta.js'
99100
export { createMode } from './function/statistics/mode.js'
100101
export { createProd } from './function/statistics/prod.js'
101102
export { createFormat } from './function/string/format.js'

src/factoriesNumber.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -249,7 +249,7 @@ export { createUnequalNumber as createUnequal } from './function/relational/uneq
249249

250250
// special
251251
export { createErf } from './function/special/erf.js'
252-
252+
export { createZeta } from './function/special/zeta.js'
253253
// statistics
254254
export { createMode } from './function/statistics/mode.js'
255255
export { createProd } from './function/statistics/prod.js'

src/function/special/zeta.js

+140
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
import { factory } from '../../utils/factory.js'
2+
3+
const name = 'zeta'
4+
const dependencies = ['typed', 'config', 'multiply', 'pow', 'divide', 'factorial', 'equal', 'smallerEq', 'isNegative', 'gamma', 'sin', 'subtract', 'add', '?Complex', '?BigNumber', 'pi']
5+
6+
export const createZeta = /* #__PURE__ */ factory(name, dependencies, ({ typed, config, multiply, pow, divide, factorial, equal, smallerEq, isNegative, gamma, sin, subtract, add, Complex, BigNumber, pi }) => {
7+
/**
8+
* Compute the Riemann Zeta function of a value using an infinite series for
9+
* all of the complex plane using Riemann's Functional equation.
10+
*
11+
* Based off the paper by Xavier Gourdon and Pascal Sebah
12+
* ( http://numbers.computation.free.fr/Constants/Miscellaneous/zetaevaluations.pdf )
13+
*
14+
* Implementation and slight modification by Anik Patel
15+
*
16+
* Note: the implementation is accurate up to about 6 digits.
17+
*
18+
* Syntax:
19+
*
20+
* math.zeta(n)
21+
*
22+
* Examples:
23+
*
24+
* math.zeta(5) // returns 1.0369277551433895
25+
* math.zeta(-0.5) // returns -0.2078862249773449
26+
* math.zeta(math.i) // returns 0.0033002236853253153 - 0.4181554491413212i
27+
*
28+
*
29+
* @param {number | Complex | BigNumber} s A Real, Complex or BigNumber parameter to the Riemann Zeta Function
30+
* @return {number | Complex | BigNumber} The Riemann Zeta of `s`
31+
*/
32+
return typed(name, {
33+
number: (s) => zetaNumeric(s, value => value, () => 20),
34+
BigNumber: (s) => zetaNumeric(
35+
s,
36+
value => new BigNumber(value),
37+
() => {
38+
// epsilon is for example 1e-12. Extract the positive exponent 12 from that
39+
return Math.abs(Math.log10(config.epsilon))
40+
}
41+
),
42+
Complex: zetaComplex
43+
})
44+
45+
/**
46+
* @param {number | BigNumber} s
47+
* @param {(value: number) => number | BigNumber} createValue
48+
* @param {(value: number | BigNumber | Complex) => number} determineDigits
49+
* @returns {number | BigNumber}
50+
*/
51+
function zetaNumeric (s, createValue, determineDigits) {
52+
if (equal(s, 0)) {
53+
return createValue(-0.5)
54+
}
55+
if (equal(s, 1)) {
56+
return createValue(NaN)
57+
}
58+
if (!isFinite(s)) {
59+
return isNegative(s) ? createValue(NaN) : createValue(1)
60+
}
61+
62+
return zeta(s, createValue, determineDigits, s => s)
63+
}
64+
65+
/**
66+
* @param {Complex} s
67+
* @returns {Complex}
68+
*/
69+
function zetaComplex (s) {
70+
if (s.re === 0 && s.im === 0) {
71+
return new Complex(-0.5)
72+
}
73+
if (s.re === 1) {
74+
return new Complex(NaN, NaN)
75+
}
76+
if (s.re === Infinity && s.im === 0) {
77+
return new Complex(1)
78+
}
79+
if (s.im === Infinity || s.re === -Infinity) {
80+
return new Complex(NaN, NaN)
81+
}
82+
83+
return zeta(s, value => value, s => Math.round(1.3 * 15 + 0.9 * Math.abs(s.im)), s => s.re)
84+
}
85+
86+
/**
87+
* @param {number | BigNumber | Complex} s
88+
* @param {(value: number) => number | BigNumber | Complex} createValue
89+
* @param {(value: number | BigNumber | Complex) => number} determineDigits
90+
* @param {(value: number | BigNumber | Complex) => number} getRe
91+
* @returns {*|number}
92+
*/
93+
function zeta (s, createValue, determineDigits, getRe) {
94+
const n = determineDigits(s)
95+
if (getRe(s) > -(n - 1) / 2) {
96+
return f(s, createValue(n), createValue)
97+
} else {
98+
// Function Equation for reflection to x < 1
99+
let c = multiply(pow(2, s), pow(createValue(pi), subtract(s, 1)))
100+
c = multiply(c, (sin(multiply(divide(createValue(pi), 2), s))))
101+
c = multiply(c, gamma(subtract(1, s)))
102+
return multiply(c, zeta(subtract(1, s), createValue, determineDigits, getRe))
103+
}
104+
}
105+
106+
/**
107+
* Calculate a portion of the sum
108+
* @param {number | BigNumber} k a positive integer
109+
* @param {number | BigNumber} n a positive integer
110+
* @return {number} the portion of the sum
111+
**/
112+
function d (k, n) {
113+
let S = k
114+
for (let j = k; smallerEq(j, n); j = add(j, 1)) {
115+
const factor = divide(
116+
multiply(factorial(add(n, subtract(j, 1))), pow(4, j)),
117+
multiply(factorial(subtract(n, j)), factorial(multiply(2, j)))
118+
)
119+
S = add(S, factor)
120+
}
121+
122+
return multiply(n, S)
123+
}
124+
125+
/**
126+
* Calculate the positive Riemann Zeta function
127+
* @param {number} s a real or complex number with s.re > 1
128+
* @param {number} n a positive integer
129+
* @param {(number) => number | BigNumber | Complex} createValue
130+
* @return {number} Riemann Zeta of s
131+
**/
132+
function f (s, n, createValue) {
133+
const c = divide(1, multiply(d(createValue(0), n), subtract(1, pow(2, subtract(1, s)))))
134+
let S = createValue(0)
135+
for (let k = createValue(1); smallerEq(k, n); k = add(k, 1)) {
136+
S = add(S, divide(multiply((-1) ** (k - 1), d(k, n)), pow(k, s)))
137+
}
138+
return multiply(c, S)
139+
}
140+
})
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
/* eslint-disable no-loss-of-precision */
2+
3+
import assert from 'assert'
4+
import approx from '../../../../tools/approx.js'
5+
import math from '../../../../src/defaultInstance.js'
6+
7+
const zeta = math.zeta
8+
const epsilon = 1e-6 // FIXME: make zeta work with an epsilon of 1e-12
9+
10+
function approxEqual (a, b) {
11+
approx.equal(a, b, epsilon)
12+
}
13+
14+
describe('Riemann Zeta', function () {
15+
it('should calculate the Riemann Zeta Function of a positive integer', function () {
16+
assert.ok(isNaN(zeta(1)))
17+
approxEqual(zeta(2), 1.6449340668482264)
18+
approxEqual(zeta(3), 1.2020569031595942)
19+
approxEqual(zeta(4), 1.0823232337111381)
20+
approxEqual(zeta(5), 1.0369277551433699)
21+
assert.strictEqual(zeta(Infinity), 1) // shouldn't stall
22+
})
23+
24+
it('should calculate the Riemann Zeta Function of a non-positive integer', function () {
25+
assert.strictEqual(zeta(0), -0.5)
26+
approxEqual(zeta(-1), -1 / 12)
27+
approxEqual(zeta(-2), 0)
28+
approxEqual(zeta(-3), 1 / 120)
29+
approxEqual(zeta(-13), -1 / 12)
30+
assert.ok(isNaN(zeta(-Infinity)))
31+
})
32+
33+
it('should calculate the Riemann Zeta Function of a BigNumber', function () {
34+
const bigEpsilon = 1e-5 // FIXME: should work with for example an epsilon of 1e-64
35+
const digits = Math.abs(Math.log10(bigEpsilon))
36+
37+
const math2 = math.create()
38+
math2.config({ epsilon: bigEpsilon })
39+
40+
function bigApproxEqual (a, b) {
41+
assert.strictEqual(
42+
a.toSignificantDigits(digits).valueOf(),
43+
b.toSignificantDigits(digits).valueOf(),
44+
(a + ' ~= ' + b + ' (epsilon: ' + epsilon + ')')
45+
)
46+
}
47+
48+
bigApproxEqual(zeta(math2.bignumber(0)), math2.bignumber('-0.5'))
49+
assert.ok(zeta(math2.bignumber(1)).isNaN())
50+
bigApproxEqual(zeta(math2.bignumber(2)), math2.bignumber('1.6449340668482264364724151666460251892189499012067984377355582293'))
51+
bigApproxEqual(zeta(math2.bignumber(-2)).add(1), math2.bignumber('1')) // we add 1 on both sides since we cannot easily compare zero
52+
bigApproxEqual(zeta(math2.bignumber(20)), math2.bignumber('1.0000009539620338727961131520386834493459437941874105957500564898'))
53+
bigApproxEqual(zeta(math2.bignumber(-21)), math2.bignumber('-281.4601449275362318840579710144927536231884057971014492753623188'))
54+
bigApproxEqual(zeta(math2.bignumber(50)), math2.bignumber('1.0000000000000008881784210930815903096091386391386325608871464644'))
55+
bigApproxEqual(zeta(math2.bignumber(-211)), math2.bignumber('2.7274887879083469529027229775609299175103750961568681644229e231'))
56+
bigApproxEqual(zeta(math2.bignumber(100)), math2.bignumber('1.0000000000000000000000000000007888609052210118073520537827660414'))
57+
bigApproxEqual(zeta(math2.bignumber(Infinity)), math2.bignumber('1')) // shouldn't stall
58+
bigApproxEqual(zeta(math2.bignumber(-Infinity)), math2.bignumber(NaN)) // shouldn't stall
59+
})
60+
61+
it('should calculate the Riemann Zeta Function of a rational number', function () {
62+
approxEqual(zeta(0.125), -0.6327756234986952552935)
63+
approxEqual(zeta(0.25), -0.81327840526189165652144)
64+
approxEqual(zeta(0.5), -1.460354508809586812889499)
65+
approxEqual(zeta(1.5), 2.61237534868548834334856756)
66+
approxEqual(zeta(2.5), 1.34148725725091717975676969)
67+
approxEqual(zeta(3.5), 1.12673386731705664642781249)
68+
approxEqual(zeta(30.5), 1.00000000065854731257004465)
69+
approxEqual(zeta(144.9), 1.0000000000000000000000000)
70+
71+
approxEqual(zeta(-0.5), -0.2078862249773545660173067)
72+
approxEqual(zeta(-1.5), -0.0254852018898330359495429)
73+
approxEqual(zeta(-2.5), 0.00851692877785033054235856)
74+
})
75+
76+
it('should calculate the Riemann Zeta Function of an irrational number', function () {
77+
approxEqual(zeta(Math.SQRT2), 3.0207376794860326682709)
78+
approxEqual(zeta(Math.PI), 1.17624173838258275887215)
79+
approxEqual(zeta(Math.E), 1.26900960433571711576556)
80+
81+
approxEqual(zeta(-Math.SQRT2), -0.0325059805396893552173896)
82+
approxEqual(zeta(-Math.PI), 0.00744304047846672771406904635)
83+
approxEqual(zeta(-Math.E), 0.00915987755942023170457566822)
84+
})
85+
86+
it('should calculate the Riemann Zeta Function of a Complex number', function () {
87+
approxEqual(zeta(math.complex(0, 1)), math.complex(0.00330022368532410287421711, -0.418155449141321676689274239))
88+
approxEqual(zeta(math.complex(3, 2)), math.complex(0.97304196041894244856408189, -0.1476955930004537946298999860))
89+
approxEqual(zeta(math.complex(-1.5, 3.7)), math.complex(0.244513626137832304395, 0.2077842378226353306923615))
90+
approxEqual(zeta(math.complex(3.9, -5.2)), math.complex(0.952389583517691366229, -0.03276345793831000384775143962))
91+
approxEqual(zeta(math.complex(-1.2, -9.3)), math.complex(2.209608454242663005234, -0.67864225792147162441259999407))
92+
approxEqual(zeta(math.complex(0.5, 14.14)), math.complex(-0.00064921838659084911, 0.004134963322496717323762898714))
93+
})
94+
})

types/index.d.ts

+16
Original file line numberDiff line numberDiff line change
@@ -2600,6 +2600,14 @@ declare namespace math {
26002600
*/
26012601
erf<T extends number | MathCollection>(x: T): NoLiteralType<T>
26022602

2603+
/**
2604+
* Compute the Riemann Zeta function of a value using an infinite series
2605+
* and Riemann's Functional equation.
2606+
* @param s A real, complex or BigNumber
2607+
* @returns The Riemann Zeta of s
2608+
*/
2609+
zeta<T extends number | Complex | BigNumber>(s: T): T
2610+
26032611
/*************************************************************************
26042612
* Statistics functions
26052613
************************************************************************/
@@ -5977,6 +5985,14 @@ declare namespace math {
59775985
this: MathJsChain<T>
59785986
): MathJsChain<NoLiteralType<T>>
59795987

5988+
/**
5989+
* Compute the Riemann Zeta function of a value using an infinite series
5990+
* and Riemann's Functional equation.
5991+
*/
5992+
zeta<T extends number | Complex | BigNumber>(
5993+
this: MathJsChain<T>
5994+
): MathJsChain<T>
5995+
59805996
/*************************************************************************
59815997
* Statistics functions
59825998
************************************************************************/

0 commit comments

Comments
 (0)