Public
Edited
Jan 1, 2023
Insert cell
Insert cell
ζs_partials = {
const L = [(0), (1)]
for (let n = 2; n <= N; ++n) {
L[n] = L[n-1].add((n).pow(s_neg))
}
return L
}
Insert cell
ζr_partials = {
const L = [(0), (1)]
const z_neg = (1).sub(s).neg()
for (let n = 2; n <= N; ++n) {
L[n] = L[n-1].add((n).pow(z_neg))
}
return L
}
Insert cell
frac_parts = {
const denom = N_subdiv + 1
const unit = 1 / denom
const domain = Array.from({ length: N_subdiv }).map((_,i) => (i+1) / denom)
const range = domain.map(q => ζ_H(s,q))
domain.push(1)
range.push(ζs)
return { domain, range }
}
Insert cell
Insert cell
ζs_H_trace = {
const denom = N_subdiv + 1
const total = N * denom + 1
const P = Array(total)

P[0] = { x: 0, y: 0, z: 0 }

// loop over each frac
for (let i = 0; i < denom; ++i) {
const frac = frac_parts.domain[i]
let last = frac_parts.range[i]
// set initial value from precomputed frac range values
let k = i + 1
P[k] = { x: last.re, y: last.im, z: frac }

// loop from 1 to N and compute derived values
for (let n = 1; n < N; ++n) {
last = last.sub((n + frac - 1).pow(2).pow(s_neg.div(2)))
// set new point at appropriate index
k += denom
P[k] = { x: last.re, y: last.im, z: n + frac }
}
}
return P
}
Insert cell
// s = ℂ(mathjs.bignumber(0.5),mathjs.bignumber('14.1347251417346937904572519835624766'))
// s = ℂ(mathjs.bignumber(0.5),mathjs.bignumber('347.27267758442048447579709488806986'))
s = (ℜs,ℑs)
Insert cell
Insert cell
ζs = ζ_H(s,1)
Insert cell
ζn = (z,n) => {
if (n == 0) return (0)
if (n == 1) return (1)
let cur = (1)
for (let k = 2; k <= n; ++k) {
cur = cur.add((k).pow(z.neg()))
}
return cur
}
Insert cell
ζs_ℑs_π = ζ_H(s, Math.abs(s.im) / Math.PI) // TODO mod out by π?
Insert cell
ζs_q = ζ_H(s,q)
Insert cell
Insert cell
ζ_a_next = (z, a, z_a) => (z_a || ζ_H(z, a)).sub((1).div((a).pow(2).pow(z.div(2))))
Insert cell
Insert cell
ζ_a_prev = (z, a, z_a) => (z_a || ζ_H(z, a)).add((1).div((a).sub(1).pow(2).pow(z.div(2))))
Insert cell
ζs_q_next = ζs_q.sub((1).div((q).pow(2).pow(s.div(2))))
Insert cell
ζs_q_prev = ζs_q.add((1).div((q).sub(1).pow(2).pow(s.div(2))))
Insert cell
ζs_q_partials = {
const L = [(0)]
for (let n = 1; n <= N; ++n) {
L[n] = L[n-1].add((n+q).pow(s_neg))
}
return L
}
Insert cell
ζr = ζ_H((1).sub(s), 1) // TODO just use reflection formula on ζs
Insert cell
ζs_over_ζs_reflected = ζs.div(ζr)
Insert cell
Insert cell
χ = (s) => (mathjs.eval('pi^(s-1/2) gamma((1-s)/2) / gamma(s/2)', { s }))
Insert cell
χs = χ(s)
Insert cell
Insert cell
γr = (s) => (mathjs.eval('2^s pi^(s-1) sin(pi s/2) gamma(1-s)', { s }))
Insert cell
γr_s = γr(s)
Insert cell
γr_s
Insert cell
Insert cell
γr_r = γr(s_neg.add(1))
Insert cell
mathjs.eval('2 gamma(s) (2 pi)^-s cos(pi s/2)', {s})
Insert cell
mathjs.eval('gamma(s) (2 pi)^-s * (exp(i*pi*s/2) + exp(-i*pi*s/2))', {s})
Insert cell
[ ζs_over_ζs_reflected, χs, γr_s ]
Insert cell
Insert cell
ζs_half = ζs.mul((2).pow(s).sub(1))
Insert cell
Insert cell
Insert cell
Insert cell
// TODO
// ζs_q_next_d2q = s.mul(s.add(1)).mul(ζ_H(s.add(2),q+1))
Insert cell
// TODO
// ζ_approx = (t) => s
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
{
const ζ_θ_trace = rotoshifted_detailed_traces.filter((t) => t.name === 'ζ<sub>θ</sub>(s,t)')[0]
const ζ_τ_trace = rotoshifted_detailed_traces.filter((t) => t.name === 'ζ<sup>τ*</sup>(s,t)')[0]

const ζ_θ_series = ζ_θ_trace.z.map((z,n) => ({
v: (ζ_θ_trace.x[n], ζ_θ_trace.y[n]),
t: z+1 // add back the subtracted z offset
}))
const ζ_τ_series = ζ_τ_trace.z.map((z,n) => ({
v: (ζ_τ_trace.x[n], ζ_τ_trace.y[n]),
t: z+1 // add back the subtracted z offset
}))
// NB this nearly tracks abs(ζ(0+yi,a)), but flipped...
// const max = Math.max(...ζ_θ_trace.z)
// const d_ds_series = []
// for (let t = 0; t <= max; t+= 0.1) {
// d_ds_series.push({
// t,
// v: 1/(π*t) * mathjs.log(mathjs.gamma(t-s.im/π)) - mathjs.log(2*π) / 2
// })
// }
const max = Math.max(...ζ_θ_trace.z)
const d_ds_series = []
for (let t = 0; t <= max; t+= 0.1) {
// y: ζ_θ_series.map((o) => mathjs.eval('log(gamma(t)) - 1/2 log(2 pi)', o)),
d_ds_series.push({
t,
v: mathjs.log(mathjs.gamma(t-Math.abs(s.im)/π)) - mathjs.log(2*π) / 2
})
}
const kink_t_low = Math.abs(s.im) / (π * hurwitz_spin_factor)
const kink_t_low_arg = ζ_H(s,kink_t_low).mul(mathjs.exp(('i').mul(Math.abs(s.im)))).arg()
// NB not right
const kink_t_approx = Math.abs(s.im) / (π * hurwitz_spin_factor) + 1/2 // 1 - γ0
const kink_t_approx_arg = ζ_H(s,kink_t_approx).mul(mathjs.exp(('i').mul(π * hurwitz_spin_factor * kink_t_approx))).arg()

const traces = [
{
name: 'ℜ{ζ<sub>θ</sub>(s,t)}',
x: ζ_θ_series.map((o) => o.t),
y: ζ_θ_series.map((o) => o.v.re),
type: 'scatter',
mode: 'lines',
marker: {
color: 'blue',
},
visible: trace_visibility.includes('rotated_hurwitz_spirals') ? true : 'legendonly',
},
{
name: 'ℑ{ζ<sub>θ</sub>(s,t)}',
x: ζ_θ_series.map((o) => o.t),
y: ζ_θ_series.map((o) => o.v.im),
type: 'scatter',
mode: 'lines',
marker: {
color: 'red',
},
visible: trace_visibility.includes('rotated_hurwitz_spirals') ? true : 'legendonly',
},

// abs/arg
{
name: '|ζ<sup>θ</sup>(s,t)|',
x: ζ_θ_series.map((o) => o.t),
y: ζ_θ_series.map((o) => o.v.abs()),
type: 'scatter',
mode: 'lines',
marker: {
color: 'pink',
},
visible: trace_visibility.includes('rotated_hurwitz_spirals') ? true : 'legendonly',
},
{
name: '-|ζ<sup>θ</sup>(s,t)|',
x: ζ_θ_series.map((o) => o.t),
y: ζ_θ_series.map((o) => -o.v.abs()),
type: 'scatter',
mode: 'lines',
marker: {
color: 'pink',
},
visible: trace_visibility.includes('rotated_hurwitz_spirals') ? true : 'legendonly',
},
{
name: 'Arg(ζ<sup>θ</sup>(s,t))',
x: ζ_θ_series.map((o) => o.t),
y: ζ_θ_series.map((o) => o.v.arg()),
type: 'scatter',
mode: 'lines',
marker: {
color: 'purple',
},
visible: trace_visibility.includes('rotated_hurwitz_spirals') ? true : 'legendonly',
},

// {
// name: '∂/∂s ζ(0,t), s=0',
// x: d_ds_series.map((o) => o.t),
// y: d_ds_series.map((o) => o.v),
// type: 'scatter',
// mode: 'lines',
// marker: {
// color: 'teal',
// },
// visible: trace_visibility.includes('rotated_hurwitz_spirals') ? true : 'legendonly',
// },

// approximate maximal kink
{
name: `ζ<sub>θ</sub>(s,ℑs/(πr)) inflection`,
x: [ kink_t_low, kink_t_approx ],
y: [ kink_t_low_arg, kink_t_approx_arg ],
type: 'scatter',
mode: 'markers',
marker: {
// size: 3,
color: 'gray',
},
visible: trace_visibility.includes('rotated_hurwitz_spirals') ? true : 'legendonly',
},
{
name: 'ℜ{ζ<sup>τ*</sup>(s,t)}',
x: ζ_τ_series.map((o) => o.t),
y: ζ_τ_series.map((o) => o.v.re),
// .map((x,n) => ℂ(x,ζ_τ_trace.y[n]).div(χs).re),
type: 'scatter',
mode: 'lines',
marker: {
color: 'green',
},
visible: trace_visibility.includes('rotoshifted_spirals') ? true : 'legendonly',
},
{
name: 'ℑ{ζ<sup>τ*</sup>(s,t)}',
x: ζ_τ_series.map((o) => o.t),
y: ζ_τ_series.map((o) => o.v.im),
type: 'scatter',
mode: 'lines',
marker: {
color: 'orange',
},
visible: trace_visibility.includes('rotoshifted_spirals') ? true : 'legendonly',
},
{
name: '|ζ<sup>τ*</sup>(s,t)|',
x: ζ_τ_series.map((o) => o.t),
y: ζ_τ_series.map((o) => o.v.abs()),
type: 'scatter',
mode: 'lines',
marker: {
color: 'brown',
},
visible: trace_visibility.includes('rotoshifted_spirals') ? true : 'legendonly',
},
{
name: 'Arg(ζ<sup>τ*</sup>(s,t))',
x: ζ_τ_series.map((o) => o.t),
y: ζ_τ_series.map((o) => o.v.arg()),
type: 'scatter',
mode: 'lines',
marker: {
color: 'tan',
},
visible: trace_visibility.includes('rotoshifted_spirals') ? true : 'legendonly',
},

{
name: `q point, |χs|`,
x: [ q, q, q ],
y: [ ζs_over_ζs_reflected.abs(), 0, -ζs_over_ζs_reflected.abs() ],
type: 'scatter',
mode: 'markers+lines',
marker: {
// size: 3,
color: 'black',
},
line: {
dash: 'dot',
},
// visible: trace_visibility.includes('rotated_hurwitz_spirals') ? true : 'legendonly',
},
]
const layout = {
autosize: false,
width: plot_width,
height: plot_height,
showlegend: true,
xaxis: {
// type: 'log',
// rangemode: 'tozero',
// range: [ 0, 95 ],
},
yaxis: {
autorange: false,
range: [ -rval*2, rval*2 ],
}
}

Plotly.react(
graph2Div,
traces,
layout,
{
// scrollZoom: true,
displayModeBar: true,
displaylogo: false,
// modeBarButtonsToRemove: ['toImage'],
// responsive: true,
},
)
}
Insert cell
Insert cell
// TODO is there a spiral which perfectly connects the C_n values (pendant centers)?
// obviously clothoid-like, but could be anything (e.g. log, hyperbolic, etc.)
// calculate endpoints, two per C_n pendant center

// revisit connection between approximate zeta log spiral differences and hurwitz partials
// this could shed light on formula for C_n with n > 0
Insert cell
Insert cell
viewof plot_width = html`<input type="range" min="200" max="${2*width}" step="1" value="${width}" style="width:${width}px" />`
Insert cell
viewof plot_height = html`<input type="range" min="200" max="${2*width}" step="1" value="${width}" style="width:${width}px" />`
Insert cell
ζs_q_next_dq
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
s.im
Insert cell
Insert cell
Insert cell
Insert cell
q_kink = s.im / (partials_folding_ratio * π) + partials_folding_ratio
// viewof q_kink = Inputs.range([40, 100], { value: 45, step: 0.01 })
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
// TODO fix z offsets, in particular: q hurwitz partials (olive) and derivative vectors
Insert cell
camera_current = cameras.front
Insert cell
C_n_list = {
const list = [];
const halfwinds = Math.floor(
(Math.abs(s.im) * Math.log(Math.abs(s.im) / π)) / π
);
const centers = 2; // Math.floor((halfwinds - 1) / 2) // TODO: +1 instead of -1?
for (let i = 0; i < centers; ++i) {
list[i] = ζ_H(s, Math.abs(s.im) / ((2 * i + 1) * π));
}
return list;
}
Insert cell
C_0_rot = C_n_list[0]
.sub((Math.abs(s.im) / π).pow(s_neg))
.mul(
mathjs.exp(
("i").mul(π * (Math.abs(s.im) / π - Math.floor(Math.abs(s.im) / π)))
)
)
.arg() - C_n_list[0].sub((Math.abs(s.im) / π).pow(s_neg)).arg()
Insert cell
ζs_rotoshift_smooth = (t, z_t) => {
const ζs_t_origin = z_t || ζ_H(s, t);

let d = (t).pow(s_neg);
d = ζs_t_origin.sub(d);

let xn = Math.abs(s.im) / (2 * π * t) - 1 / 2; // = (Math.abs(s.im) - π*t) / (2*π*t)

// just use ζ(s,t) - t^-s if past C_0 center
if (xn <= 0) return d;

const rotation = mathjs.exp(("i").mul(π * t * partials_folding_ratio));

let k = -1;
while (++k < xn) {
const shift = k > 0 ? ζn(s_neg.add(1), k).mul(χs) : (0);

// shift down to 0 to rotate
d = d.sub(shift);

d = d.mul(rotation);

// unshift back to position
d = d.add(shift);
}

// if rejoin rotoshifted requested, flip over line of symmetry
if (trace_config.includes("rejoin_rotoshifted_spirals")) {
// for (let k = 0, len = Math.min(C_n_list.length, xn); k < len; ++k) {
d = d
.mul(mathjs.exp(("i").mul(-C0_lower.arg())))
.conjugate()
.mul(mathjs.exp(("i").mul(C0_upper.arg())));
// }
}

return d;
}
Insert cell
ζs_rotoshift = (t, z_t) => {
const ζs_t_origin = z_t || ζ_H(s, t)

const scalefrac = t - Math.floor(t)
// rotate all sums by e^(2π i frac(t))
// = e^(2π i (t - floor(t)))
// = e^(2π i t - 2π i floor(t)))
// = e^(2π i t) / e^(2π i floor(t))
const rotation = mathjs.exp(('i').mul(π * scalefrac * partials_folding_ratio))

let d = (t).pow(s_neg)
d = ζs_t_origin.sub(d) // .mul(rotation)

let xn = Math.abs(s.im) / (2*π*t) - 1/2

if (xn >= 0) d = d.mul(rotation)
// this rotation is only factor for approach to C_1 pendant center
// (ζ(s,t) - t^(-s)) e^(2π i t) / e^(2π i floor(t))
// t^(-s) = e^(t^(-s)/ln(t^-s)) = e^(t^(-s)/(-s ln(t)) ...

// TODO show equality between χ-normalized hurwitz for s and 1-s through reflection
// demonstrate that only way they could be equal is if |γr(s)| = |γr(1-s)| = 1
// this is known to be true iff Re(s) = 1/2

let k = 0
while (++k < xn) {
// unshift by kth normalized reflected partial step, rotate, shift back
// shift = ζn(1-s) χ(s)
// = (ζ(1-s,1) - ζ(1-s,k)) ζ(s,1) / ζ(1-s,1)
// = ζ(1-s,1) ζ(s,1) / ζ(1-s,1) - ζ(1-s,k) ζ(s,1) / ζ(1-s,1)
// = (1 - ζ(1-s,k) ζ(s,1) / ζ(1-s,1)
// = (1 - ζ(1-s,k) γr(s)
// = (1 - ζ(1-s,k)) π^(s-1/2) Γ((1-s)/2) / Γ(s/2)
const shift = ζn(s_neg.add(1), k).mul(χs)
d = d.sub(shift).mul(rotation).add(shift)
}

return d
}
Insert cell
harmonic_hurwitz_spiral_traces = {
// const approx_
// const spiral = (t, z) => mathjs.eval('exp(z*t/(π-2))', { t, z, π: Math.PI })
const spiral = (z,t) => mathjs.eval('exp(2*π*t*z)*χs', { t, z: (z), π: Math.PI, χs })
return [
{
name: 'r ≈ ζ(s,1-Hn)',
type: 'scatter3d',
mode: 'lines+markers',
marker: {
color: 'blue',
size: 2,
opacity: 0.2,
},
visible: trace_visibility.includes('harmonic_spirals') ? true : 'legendonly',
...(() => {
const data = {}
const xs = []
const ys = []
const zs = []

const abs = Math.hypot(1 - s.re, s.im)
const arg = Math.atan2(s.im, 1 - s.re) + Math.PI
const offset = (0) // ζs

// for (let n = 0; n <= N_subdiv; ++n) {
for (let n = -100; n <= 100; ++n) {
const t = n/100 // 1/n

// const A = (t)**(ζs.re) * abs
// const B = -ζs.im * Math.log(t) + arg
const A = t**(1 - s.re) / abs
const B = -s.im * Math.log(t) + arg

// data.x.push(offset.re + A * Math.cos(B))
// data.y.push(offset.im + A * Math.sin(B))
// const d = offset.add(spiral(s, t).mul(ζs_half))
// const d = offset.add(spiral(ℂ(1,1), t).mul(ζs_half))
const d = offset.add(spiral(s, t))
xs.push(d.re)
ys.push(d.im)
zs.push(0)
}

return { x: xs, y: ys, z: zs }
})(),
}
]
}
Insert cell
Insert cell
rval = r_scale // * ζs.abs()
Insert cell
Insert cell
Insert cell
Insert cell
camera_current
Insert cell
ζs_rotoshift_alt = (t, z_t) => {
const ζs_t_origin = z_t || ζ_H(s, t)

const scalefrac = t - Math.floor(t)
// rotate all sums by e^(2π * frac(t))
// NB no longer the above ^^ -- but slight derivation
const rotation = mathjs.exp(('i').mul(π * scalefrac * partials_folding_ratio))

let d = (t).pow(s_neg)
d = ζs_t_origin.sub(d).mul(rotation)

let xn = Math.abs(s.im) / (2 * π * (t + 1/2) - π) - 1/2
// if (xn < 0) ... TODO turn around... appears we're not taking shortest path here

let k = 0
while (++k < xn) {
// shift one normalized/reversed partial step, rotate, shift back
const shift = ζn(s_neg.add(1), k).mul(ζs_over_ζs_reflected)
// rotate by difference between
d = d.sub(shift).mul(rotation).add(shift)
}

return d
}
Insert cell
ζs_q_rotoshift = ζs_rotoshift_smooth(q)
Insert cell
ζn(s_neg.add(1), 3).mul(ζs).div(ζr).toString()
Insert cell
Cn_upper = (n) => C_n_list[n].sub((Math.abs(s.im) / ((2*n+1)*π)).pow(s_neg)) // TODO we need a sum from 0 to n
Insert cell
Cn_lower = (n) => Cn_upper(n).mul(mathjs.exp(('i').mul(π * Math.abs(s.im) / ((2*n + 1)*π))))
Insert cell
C0_upper = C_n_list[0].sub((Math.abs(s.im) / π).pow(s_neg))
Insert cell
C0_lower = C0_upper.mul(mathjs.exp(("i").mul(Math.abs(s.im))))
Insert cell
rotoshifted_detailed_traces = [
(() => {
const data = { x: [], y: [], z: [] };
ζs_H_trace.forEach((v) => {
const t = v.z;
if (t < 1) return;
if (rotoshift_partial_min < Math.abs(s.im) / (t * π)) return;

const d = ζs_rotoshift(t, (v.x, v.y));
data.x.push(d.re);
data.y.push(d.im);
data.z.push(t);
});

return {
name: `ζ<sup>τ</sup>(s,t) rotoshift`,
...data,
type: "scatter3d",
mode: "lines+markers",
marker: {
size: 2,
color: "violet",
opacity: 0.5
},
line: {
// dash: 'dot',
},
visible: "legendonly"
// visible: trace_visibility.includes('rotoshifted_spirals') ? true : 'legendonly',
};
})(),
(() => {
const data = { x: [], y: [], z: [] };
ζs_H_trace.forEach((v) => {
const t = v.z;
if (t < 1) return;
if (rotoshift_partial_min < Math.abs(s.im) / (t * π)) return;

const d = ζs_rotoshift(t, (v.x, v.y));
// data.x.push(d.re)
// data.y.push(d.im)
// NB flip it back to align w/ original zeta partials, just because
data.x.push(ζs.re - d.re);
data.y.push(ζs.im - d.im);

data.z.push(t);
});

return {
name: `ζ(s) - ζ<sup>τ</sup>(s,t)`,
...data,
type: "scatter3d",
mode: "lines+markers",
marker: {
size: 2,
color: "maroon",
opacity: 0.5
},
visible: trace_visibility.includes("rotoshifted_spirals_reversed")
? true
: "legendonly"
};
})(),
(() => {
const data = { x: [], y: [], z: [] };
ζs_H_trace.forEach((v) => {
const t = v.z;
if (t < 1) return;
if (rotoshift_partial_min < Math.abs(s.im) / (t * π)) return;

let d = ζs_rotoshift_smooth(t, (v.x, v.y));
if (trace_config.includes("normalize_rotated_spirals"))
d = d.mul(γr_r).mul(mathjs.exp(("i").mul(π / 4)));

data.x.push(d.re);
data.y.push(d.im);

data.z.push(t);
});

return {
name: `ζ<sup>τ*</sup>(s,t)`,
...data,
type: "scatter3d",
mode: "lines+markers",
marker: {
size: 2,
color: "green",
opacity: 0.5
},
visible: trace_visibility.includes("rotoshifted_spirals")
? true
: "legendonly"
};
})(),
(() => {
const v = ζs_rotoshift_smooth(q_kink);
const data = { x: [v.re], y: [v.im], z: [q_kink] };
return {
name: `ζ<sup>τ*</sup>(s,q<sub>kink</sub>)`,
...data,
type: "scatter3d",
mode: "markers",
marker: {
size: 4,
color: "orange",
opacity: 0.9
},
visible: trace_visibility.includes("rotoshifted_spirals")
? true
: "legendonly"
};
})(),
{
name: `C<sub>0</sub> spread`,
...(() => {
const x = [];
const y = [];
const z = [];
const text = [];

const t = Math.abs(s.im) / π;
const scalefrac = t - Math.floor(t);
const rotation = mathjs.exp(("i").mul(π * scalefrac));
const C0 = C0_upper;
const C0_ = C0_lower; // .mul(mathjs.exp(ℂ('i').mul(-C0_lower.arg())))
const diff = C0_.neg().arg() - C0.arg();

x.push(C0.re);
y.push(C0.im);
z.push(Math.abs(s.im) / π);
text.push(C0.arg());

x.push(C0_.re);
y.push(C0_.im);
z.push(Math.abs(s.im) / π);
text.push(C0_.arg() + " diff: " + diff);
return { x, y, z, text };
})(),
type: "scatter3d",
mode: "markers",
marker: {
size: 3,
color: "black",
opacity: 0.7
},
visible: "legendonly" // trace_visibility.includes('rotoshifted_spirals') ? true : 'legendonly',
},
(() => {
const data = { x: [], y: [], z: [] };
ζs_H_trace.forEach((v) => {
const t = v.z;
if (t < 1) return;
if (rotoshift_partial_min < Math.abs(s.im) / (t * π)) return;

let d = (v.x, v.y).mul(
mathjs.exp(("i").mul(π * hurwitz_spin_factor * t))
);
if (trace_config.includes("normalize_rotated_spirals"))
d = d.mul(γr_r).mul(mathjs.exp(("i").mul(π / 4)));

data.x.push(d.re);
data.y.push(d.im);
data.z.push(t - 1);
});

return {
name: `ζ<sub>θ</sub>(s,t)`,
...data,
type: "scatter3d",
mode: "lines+markers",
marker: {
size: 1,
color: "gray"
// opacity: 0.5,
},
// visible: 'legendonly',
visible: trace_visibility.includes("rotated_hurwitz_spirals")
? true
: "legendonly"
};
})(),

(() => {
const data = { x: [], y: [], z: [] };
ζs_H_trace.forEach((v) => {
const t = v.z;
if (t < 1) return;
if (rotoshift_partial_min < Math.abs(s.im) / (t * π)) return;

const d = (v.x, v.y).mul(mathjs.exp(("i").mul(π * t)));
data.x.push(d.re);
data.y.push(d.im);

data.z.push(t - 1);
});

return {
name: `ζ<sub>θ[π]</sub>(s,t)`,
...data,
type: "scatter3d",
mode: "lines+markers",
marker: {
size: 1,
color: "rgba(0,0,200,0.2)"
// opacity: 0.5,
},
// visible: 'legendonly',
visible: trace_visibility.includes("fixed_half_hurwitz_spirals")
? true
: "legendonly"
};
})(),
(() => {
const data = { x: [], y: [], z: [] };
ζs_H_trace.forEach((v) => {
const t = v.z;
if (t < 1) return;
if (rotoshift_partial_min < Math.abs(s.im) / (t * π)) return;

const d = (v.x, v.y).mul(mathjs.exp(("i").mul(π * t + π)));
data.x.push(d.re);
data.y.push(d.im);

data.z.push(t - 1);
});

return {
name: `ζ<sub>θ[π*]</sub>(s,t)`,
...data,
type: "scatter3d",
mode: "lines+markers",
marker: {
size: 1,
color: "rgba(200,0,0,0.2)"
// opacity: 0.5,
},
// visible: 'legendonly',
visible: trace_visibility.includes("fixed_half_hurwitz_spirals")
? true
: "legendonly"
};
})(),
(() => {
const t = Math.abs(s.im) / (π * hurwitz_spin_factor);
const p = ζ_H(s, t).mul(
mathjs.exp(("i").mul(π * hurwitz_spin_factor * t))
);
const data = {
x: [0, p.re],
y: [0, p.im],
z: [t - 1, t - 1],
text: ["ζ(s,θ)"]
};
return {
name: `ζ<sub>θ</sub>(s,ℑs/(πr)) inflection`,
...data,
type: "scatter3d",
mode: "markers+lines+text",
marker: {
size: 3,
color: "gray"
},
line: {
dash: "dot"
},
visible: trace_visibility.includes("rotated_hurwitz_spirals")
? true
: "legendonly"
};
})()
// (() => {
// const range = [10,45]
// const data = { x: [], y: [], z: [] }
// linspace(range[0], range[1], 201).forEach(t => {
// const d = ζs_rotoshift(t)
// data.x.push(d.re)
// data.y.push(d.im)
// data.z.push(t)
// })
// return {
// name: `ζs_q:[${range}]_rotoshift`,
// ...data,
// type: 'scatter3d',
// mode: 'lines+markers',
// marker: {
// size: 3,
// color: 'violet',
// },
// line: {
// dash: 'dot',
// }
// }
// })(),
// (() => {
// const range = [1,10]
// const data = { x: [], y: [], z: [] }
// linspace(range[0], range[1], 201).forEach(t => {
// const d = ζs_rotoshift(t)
// data.x.push(d.re)
// data.y.push(d.im)
// data.z.push(t)
// })
// return {
// name: `ζs_q:[${range}]_rotoshift`,
// ...data,
// type: 'scatter3d',
// mode: 'lines+markers',
// marker: {
// size: 3,
// color: 'red',
// },
// line: {
// dash: 'dot',
// }
// }
// })()
]
Insert cell
Insert cell
Insert cell
rotoshifted_hurwitz_traces = {
const T = rotoshift_num / rotoshift_denom
const ζs_a_origin = ζ_H(s, T)

const scalefrac = T - Math.floor(T)
// rotate all sums by e^(2π * frac(T))
const rotation = mathjs.exp(('i').mul(Math.PI).mul(scalefrac))

return [
buildCurve(
'ζn<sup>τ</sup>(s,a): C<sub>n</sub>-rotoshifted/rotated',
{
marker: {
color: 'blue',
size: 2,
},
line: 'blue',
visible: 'legendonly',
},
(() => {
const n = 1 // pendant target
const p_index_range = [ Math.abs(s.im) / (2 * n * Math.PI + Math.PI), Math.abs(s.im) / (2 * n * Math.PI - Math.PI) ]
const discrete_range = [ Math.ceil(p_index_range[0]), Math.floor(p_index_range[1]) ]

// calculate hurwitz partials...
const L = [(0)]
const vals = {x:[],y:[],z:[]}
for (let i = 1; i <= discrete_range[1]; ++i) {
const t = i - 1 + T
let d = L[i] = L[i-1].add((t).pow(s_neg))
d = ζs_a_origin.sub(d).mul(rotation)

let xn = Math.abs(s.im) / (2 * Math.PI * (t + 1/2) - Math.PI) - 1/2
// if (xn < 0) ... TODO turn around... appears we're not taking shortest path here

let k = 0
while (++k < xn) {
// shift one normalized/reversed partial step, rotate, shift back
const shift = ζn(s_neg.add(1), k).mul(ζs).div(ζr)
d = d.sub(shift).mul(rotation).add(shift)
}

vals.x.push(d.re)
vals.y.push(d.im)
vals.z.push(t)
}
return vals
})()
),
buildCurve(
'ζn(s,a): C<sub>1</sub>-rotoshifted,rotated',
{
marker: {
color: 'brown',
size: 2,
},
line: 'brown',
visible: 'legendonly',
},
(() => {
const n = 1 // pendant target
const p_index_range = [ Math.abs(s.im) / (2 * n * Math.PI + Math.PI), Math.abs(s.im) / (2 * n * Math.PI - Math.PI) ]
const discrete_range = [ Math.ceil(p_index_range[0]), Math.floor(p_index_range[1]) ]

// calculate hurwitz partials...
const L = [(0)]
const vals = {x:[],y:[],z:[]}
for (let i = 1; i <= discrete_range[1]; ++i) {
const t = i-1
let d = L[i] = L[i-1].add((t + T).pow(s_neg))
// rescale by rotation factor
d = ζs_a_origin.sub(d).mul(rotation)

vals.x.push(d.re)
vals.y.push(d.im)
vals.z.push(t + T)
}
return vals
})()
),
buildCurve(
'ζn(s,a): C<sub>1</sub>-rotoshifted',
{
// marker: {
// color: 'purple',
// size: 2,
// },
line: 'purple',
visible: 'legendonly',
},
(() => {

// partial sum halfway between pendant center C_n and C_[n-1]:
// Math.abs(s.im) / (2 * n * Math.PI)

// partial sum approaching pendant center C_n from below:
// Math.abs(s.im) / (2 * n * Math.PI + Math.PI)

const n = 1 // pendant target

// range gives indices for partial sums falling into nth specific pendant
// TODO abs value on Im(s)?
const p_index_range = [ Math.abs(s.im) / (2 * n * Math.PI + Math.PI), Math.abs(s.im) / (2 * n * Math.PI - Math.PI) ]

// zeroth pendant is log spiral approached by hurwitz zeta aboout zero
// first pendant is final curl approaching zeta -- magnitude: ζ(s)/ζ(1-s)
// second pendant magnitude: 2^s ζ(s)/ζ(1-s) ???

const discrete_range = [ Math.ceil(p_index_range[0]), Math.floor(p_index_range[1]) ]

// calculate hurwitz partials...
const L = [(0)]
const vals = {x:[],y:[],z:[]}
for (let i = 1; i <= discrete_range[1]; ++i) {
const t = i-1
let d = L[i] = L[i-1].add((t + T).pow(s_neg))
d = ζs_a_origin.sub(d)
vals.x.push(d.re)
vals.y.push(d.im)
vals.z.push(t + T)
}

return vals
})()
),
// {
// name: `ζ<sup>τ</sup>(s,q+1) point, ∂/∂α vector`,
// x: [ ζs_q_rotoshift.re, ζs_q_rotoshift.re + ζs_q_rotoshift_dq.re ],
// y: [ ζs_q_rotoshift.im, ζs_q_rotoshift.im + ζs_q_rotoshift_dq.im ],
// z: [ q, q ],
// type: 'scatter3d',
// mode: 'markers+lines',
// visible: trace_visibility.includes('rotoshifted_spirals') ? true : 'legendonly',
// marker: {
// size: 3,
// color: 'blue',
// opacity: 1,
// },
// line: {
// color: 'black',
// }
// },
(() => {
const r = hurwitz_spin_factor
const ζ_base = ζ_H(s,q+1)
const p = ζ_base.mul(mathjs.exp(('i').mul(π * r * q)))

// d/da (ζ(s,a) exp(iπra)) = (-iπr ζ(s,a) + ζ(s+1,a) * s) * exp(iπra)

const ζs_q_spin_dq = mathjs.exp(('i').mul(π*q*r)).mul(
('i').mul(π*r).mul(ζs_q_next).add(ζs_q_next_dq)
)

const dq = ζs_q_spin_dq
const dv = p.add(ζs_q_spin_dq)
// normalize dq for tan vector
const Tv = ζs_q_spin_dq.div(ζs_q_spin_dq.abs())
const Tp = p.add(Tv)

const data = {
x: [ dv.re, p.re, Tp.re ],
y: [ dv.im, p.im, Tp.im ],
z: [ q + 1, q, q ],
text: [ '', `ζ<sub>θ</sub>(s,${q+1}) tan` ],
}

return {
name: `ζ<sub>θ</sub>(s,q+1) tangent vector`,
...data,
type: 'scatter3d',
mode: 'markers+lines',
visible: trace_visibility.includes('rotated_hurwitz_spirals') ? true : 'legendonly',
marker: {
size: 3,
color: 'red',
opacity: 1,
},
line: {
color: 'red',
}
}
})(),

// (() => {
// const p = mathjs.eval('q^(1-s) / (s-1) + 1/2 q^(-s)', {s,q})
// const data = {
// x: [ 0, p.re ],
// y: [ 0, p.im ],
// z: [ q, q ],
// }

// return {
// name: `hurwitz integral offset`,
// ...data,
// type: 'scatter3d',
// mode: 'markers+lines',
// visible: trace_visibility.includes('rotated_hurwitz_spirals') ? true : 'legendonly',
// marker: {
// size: 3,
// color: 'green',
// opacity: 1,
// },
// line: {
// color: 'green',
// }
// }
// })(),
]
}
Insert cell
ζs_q_rotoshift_dq = mathjs.exp(('i').mul(π*q*partials_folding_ratio)).mul(
('i').mul(π*partials_folding_ratio).mul(ζs_q_next).add(ζs_q_next_dq)
)
Insert cell
/*

(d^1)/(dq^1)(ζ(s,q) exp(i π q r)) =
exp(i π q r) * (
+ 1 (-i π r)^1 ζ(s,q)
+ 1 (-i π r)^0 ζ(s+1,q) (s)
)


(d^2)/(dq^2)(ζ(s,q) exp(i π q r)) =
exp(i π q r) * (
+ 1 (-i π r)^2 ζ(s+0,q)
+ 2 (-i π r)^1 ζ(s+1,q) (s)
+ 1 (-i π r)^0 ζ(s+2,q) (s)(s+1)
)


(d^3)/(dq^3)(ζ(s,q) exp(i π q r)) =
exp(i π q r) * (
+ 1 (-i π r)^3 ζ(s+0,q)
+ 3 (-i π r)^2 ζ(s+1,q) (s)
+ 3 (-i π r)^1 ζ(s+2,q) (s)(s+1)
+ 1 (-i π r)^0 ζ(s+3,q) (s)(s+1)(s+2)
)

(d^4)/(dq^4)(ζ(s,q) exp(i π q r)) =
exp(i π q r) * (
+ 1 (-i π r)^4 ζ(s+0,q)
+ 4 (-i π r)^3 ζ(s+1,q) (s)
+ 6 (-i π r)^2 ζ(s+2,q) (s)(s+1)
+ 4 (-i π r)^1 ζ(s+3,q) (s)(s+1)(s+2)
+ 1 (-i π r)^0 ζ(s+4,q) (s)(s+1)(s+2)(s+3)
)

(d^k)/(dq^k)(ζ(s,q) exp(i π q r)) =
exp(i π q r) ∑[n=0 to k] binom(n+1,k+1) (-i π r)^n ζ(s+n,q) s^((n))

where s^((n)) notation represents rising factorial
s^((n)) = Γ(s+n)/Γ(s)
binom(n,k) = n! / (k! * (n-k)!)
for non-negative integers...
for any n:
binomial(-n,k) = (-1)^k binomial(n+k-1,k)
binomial(n,k) = (-1)^k binomial(k-n-1,k)
= (-1)^k (k-n-1)! / (k! (n-1)!)

= exp(i π q r) ∑[n=0 to k] binom(n+1,k+1) (-i π r)^n ζ(s+n,q) Γ(s+n)/Γ(s)

integral:
(d^-1)/(dq^-1) (ζ(s,q) exp(i π q r)) =
exp(i π q r) (
+ binom(0,1) (-i π r)^-1 ζ(s+0,q)
+ binom(0,0) (-i π r)^-1 ζ(s-1,q) s^((-1))
)
=
exp(i π q r) ζ(s-1,q) s^((-1)) / (-i π r)

*/
Insert cell
binom(0,1)
Insert cell
binom = (n,k) => n >= k ? mathjs.eval('combinations(n,k)', { n,k }) : n === 0 ? (k === 0 ? 1 : 0) : mathjs.eval('(-1)^k * combinations(n+k-1,k)', { n: -n, k })
Insert cell
mathjs.eval('s * (s+1)', {s})
Insert cell
ζs_q_rotoshift_d2q = mathjs.exp(('i').mul(π*q*partials_folding_ratio)).mul(
('i').mul(π*partials_folding_ratio).mul(ζs_q_next).add(ζs_q_next_dq)
)
Insert cell
pendant_spool_traces = {
const length = Math.min(10, Math.floor(Math.abs(s.im) / (3 * Math.PI)))
// iterate over reflected partials, rescale by ζ(1-s)/ζ(s)
const rpartials = Array.from({ length }).map((_,i) => (
ζn(s_neg.add(1), i).mul(ζs).div(ζr)
))
// const rhpartials = Array.from({ length: 3 }).map((_,i) => {
// const n = Math.floor((i+1)/2)
// const h = Math.abs(s.im) / (2*n * Math.PI)
// return { h, v: ζs_rotoshift(h) }
// })

const C_name = (n) => `C<sub>${n}</sub>`
return rpartials.map((z,i) => ({
name: C_name(i),
x: [ z.re, z.re, z.re ],
y: [ z.im, z.im, z.im ],
z: [ Math.abs(s.im) / ((2*i+2)*Math.PI), Math.abs(s.im) / ((2*i+1)*Math.PI), i == 0 ? (N+1) : (Math.abs(s.im) / ((2*i)*Math.PI)) ],
text: [ '', C_name(i), '' ],
type: 'scatter3d',
mode: 'markers+lines+text',
marker: {
size: 2,
color: 'black',
// opacity: 0.6,
}
}))
.concat([
{
name: `C<sub>n</sub>`,
x: rpartials.map(v => [ v.re, v.re ]).flat(1),
y: rpartials.map(v => [ v.im, v.im ]).flat(1),
z: rpartials.map((_,i) => {
const n = Math.floor((i+1)/2)
return Math.abs(s.im) / (2*n * Math.PI)
}),
type: 'scatter3d',
mode: 'markers+lines',
marker: {
size: 2,
color: 'black',
// opacity: 0.6,
},
line: {
dash: 'dot',
},
},
// {
// name: `H<sub>n</sub> midpoints`,
// x: rhpartials.map(({v}) => v.re),
// y: rhpartials.map(({v}) => v.im),
// z: rpartials.map(({h}) => h),
// type: 'scatter3d',
// mode: 'markers+lines',
// marker: {
// size: 2,
// color: 'green',
// // opacity: 0.6,
// },
// line: {
// // dash: 'dot',
// },
// },
])
}
Insert cell
spiral_approximation_traces = [
buildCurve(
'r(θ) ≈ ζ(s)',
{
line: {
color: 'purple',
// color: 'rgba(80,0,20,0.75)',
// shape: 'spline',
// smoothing: 1.3,
// dash: 'dot',
},
visible: trace_visibility.includes('approx_spirals') ? true : 'legendonly',
},
(() => {
const length = 1 + N * (1 + N_subdiv)

const data = {}
data.x = Array(length)
data.y = Array(length)
data.z = Array(length)

const abs = Math.hypot(1 - s.re, s.im)
const arg = Math.atan2(s.im, 1 - s.re)
const offset = ζs

const space = linspace(0, 1 + N, 1 + 5 * (1 + N))
space.shift() // drop first entry
space.forEach((t,i) => {
const A = t**(1 - s.re) / abs
const B = -s.im * Math.log(t) + arg

data.x[i] = offset.re + A * Math.cos(B)
data.y[i] = offset.im + A * Math.sin(B)
data.z[i] = t - 1/2
})

return data
})()
),
buildCurve(
'r(θ) - ζ(s) ≈ 0',
{
line: {
color: 'green',
// color: 'rgba(80,0,20,0.75)',
// shape: 'spline',
// smoothing: 1.3,
// dash: 'dot',
},
visible: trace_visibility.includes('approx_spirals') ? true : 'legendonly',
},
(() => {
const length = 1 + N * (1 + N_subdiv)

const data = {}
data.x = Array(length)
data.y = Array(length)
data.z = Array(length)

const abs = Math.hypot(1 - s.re, s.im)
const arg = Math.atan2(s.im, 1 - s.re)
const offset = (0) // ζs

const space = linspace(0, 1 + N, 1 + 5 * (1 + N))
space.shift() // drop first entry
space.forEach((t,i) => {
const A = -1 * t**(1 - s.re) / abs
const B = -s.im * Math.log(t) + arg

data.x[i] = offset.re + A * Math.cos(B)
data.y[i] = offset.im + A * Math.sin(B)
data.z[i] = t - 1/2
})

return data
})()
)
]
Insert cell
symmetry_spiral_traces = [
buildCurve(
'ζn(s)/ζn(1-s) ≤ t ≤ ζn(1-s)/ζn(s)',
{
line: {
color: 'rgba(20,20,20,0.5)',
shape: 'spline',
smoothing: 1,
// dash: 'dot',
},
visible: 'legendonly',
},
(() => {
// const length = 1 + N * (1 + N_subdiv)
const length = 75

const data = {}
data.x = Array(length)
data.y = Array(length)
data.z = Array.from({ length }).fill(0)
data.text = Array(length)

// (|Z(1-s)|/|ζ(s)|)^-t cis(-t(Arg(Z(1-s)) - Arg(Z(s)))
// (|Z(s)|/|ζ(1-s)|)^t cis(t(Arg(Z(s)) - Arg(Z(1-s)))
const ζabs_ratio = ζs.abs() / ζr.abs()
const ζarg_ratio = ζs.arg() - ζr.arg()

linspace(-1, 1, length).forEach((t,i) => {
const ζabs_ratio_t = ζabs_ratio ** t
const ζarg_ratio_t = ζarg_ratio * t

data.x[i] = ζabs_ratio_t * Math.cos(ζarg_ratio_t)
data.y[i] = ζabs_ratio_t * Math.sin(ζarg_ratio_t)

if (i === 0) {
data.text[i] = 'ζn(1-s)ζn(s))'
} else if (i === length - 1) {
data.text[i] = 'ζn(s)/ζn(1-s)'
} else {
// data.text[i] = ''
}
})

return data
})()
)
]
Insert cell
hurwitz_traces = [
{
name: `ζ(s,t)`,
x: ζs_H_trace.map(v => v.x),
y: ζs_H_trace.map(v => v.y),
z: ζs_H_trace.map(v => v.z - 1),
type: 'scatter3d',
mode: 'lines',
hoverinfo: 'none',
visible: trace_visibility.includes('hurwitz_spirals') ? true : 'legendonly',
// marker: {
// color: 'orange',
// size: 2,
// opacity: 0.6,
// },
line: {
simplify: false,
color: 'rgba(255,105,0,0.6)',
},
},
{
name: `ζ(s,q+1) point, ∂/∂α vector`,
x: [ ζs_q_next.re, ζs_q_next.re + ζs_q_next_dq.re ],
y: [ ζs_q_next.im, ζs_q_next.im + ζs_q_next_dq.im ],
z: [ q, q ],
text: [ 'arg(∂/∂α ζ(s,q+1)): ' + ζs_q_next_dq.arg(), '|∂/∂α ζ(s,q+1)|: ' + ζs_q_next_dq.abs() ],
type: 'scatter3d',
mode: 'markers+lines',
visible: trace_visibility.includes('hurwitz_spirals') ? true : 'legendonly',
marker: {
size: 3,
color: 'red',
opacity: 0.8,
},
},
{
name: `ζ(s,t) : t ≤ 1`,
x: ζs_H_trace.map(v => v.z <= 1 ? v.x : null).filter(k => k != null),
y: ζs_H_trace.map(v => v.z <= 1 ? v.y : null).filter(k => k != null),
z: ζs_H_trace.map(v => v.z <= 1 ? v.z - 1 : null).filter(k => k != null),
type: 'scatter3d',
mode: 'lines',
// marker: {
// color: 'orange',
// size: 2,
// opacity: 0.6,
// },
// hoverinfo: 'none',
visible: trace_visibility.includes('harmonic_spirals') ? true : 'legendonly',
line: {
simplify: false,
color: 'rgba(255,105,0,0.9)',
},
},
]
Insert cell
axis_traces = [
{
name: '|ℂ|',
x: [0,1,rval],
y: [0,0,0],
z: [0,0,0],
type: 'scatter3d',
mode: 'lines+markers',
line: {
color: 'rgba(0,0,0,0.6)',
},
marker: {
size: 2,
// opacity: 0.6,
},
},
{
name: 'ℑ{s}',
x: [0,0],
y: [0,0],
z: [z_range_min,z_range_max],
type: 'scatter3d',
mode: 'lines',
line: {
color: 'rgba(0,0,0,0.6)',
dash: 'dot',
},
// visible: 'legendonly',
},
]
Insert cell
traces = [
...axis_traces,
...hurwitz_traces,
...symmetry_spiral_traces,
...spiral_approximation_traces,
...rotoshifted_hurwitz_traces,
...rotoshifted_detailed_traces,
...pendant_spool_traces,
...harmonic_hurwitz_spiral_traces,

// {
// // TODO this isn't right -- we actually need to shift to origin, then apply transform, then unshift}
// name: `rotoscaled ζn(s)`,
// x: ζs_partials.map(v => v.pow(reflected_rotoscale).re),
// y: ζs_partials.map(v => v.pow(reflected_rotoscale).im),
// z: ζs_partials.map((v,n) => n),
// mode: 'lines+markers',
// type: 'scatter3d',
// marker: {
// color: 'red',
// size: 2,
// opacity: 0.6,
// },
// },

{
name: `ζn(s)`,
x: ζs_partials.map(v => v.re),
y: ζs_partials.map(v => v.im),
z: ζs_partials.map((v,n) => n),
mode: 'lines+markers',
type: 'scatter3d',
// hoverinfo: 'x+y',
marker: {
color: 'red',
size: 2,
opacity: 0.6,
},
line: {
color: 'rgba(150,0,0,0.6)',
},
},
{
name: `ζn(1-s)`,
x: ζr_partials.map(v => v.re),
y: ζr_partials.map(v => v.im),
z: ζr_partials.map((v,n) => n),
mode: 'lines+markers',
type: 'scatter3d',
hoverinfo: 'x+y',
marker: {
color: 'pink',
size: 2,
opacity: 0.6,
},
visible: 'legendonly',
},
{
name: `ζn(1-s) : 1/z`,
x: ζr_partials.map(v => v.re),
y: ζr_partials.map(v => v.im),
z: ζr_partials.map((_,n) => 1/n),
mode: 'lines+markers',
type: 'scatter3d',
marker: {
color: 'pink',
size: 2,
opacity: 0.6,
},
// visible: 'legendonly',
},
{
name: `ζ(s) - ζn(s)`,
x: ζs_partials.map(v => ζs.re - v.re),
y: ζs_partials.map(v => ζs.im - v.im),
z: ζs_partials.map((v,n) => n),
mode: 'lines+markers',
type: 'scatter3d',
marker: {
color: 'steelblue',
size: 2,
opacity: 0.6,
},
line: {
color: 'rgba(40,100,140,0.6)',
// dash: 'dot',
},
},
{
name: `ζn(1-s) γr_s, rescaled`,
x: ζr_partials.map(v => v.mul(γr_s).re),
y: ζr_partials.map(v => v.mul(γr_s).im),
// maps n=0 to s.im/π, n=1 to s.im/(3π), etc...
z: ζr_partials.map((v,n) => Math.abs(s.im) / ((2*n+1)*π)),
mode: 'lines+markers',
type: 'scatter3d',
marker: {
color: 'magenta',
size: 2,
opacity: 0.6,
},
visible: trace_visibility.includes('rescaled_zeta_partials') ? true : 'legendonly',
},
// {
// name: `ζs-ζ(s,qt)`,
// x: ζ_H_trace[0].map((v) => ζs[0] - v),
// y: ζ_H_trace[1].map((v) => ζs[1] - v),
// z: ζ_H_trace[2].map((v) => v-1),
// type: 'scatter3d',
// mode: 'lines',
// line: {
// color: 'rgba(0,105,255,0.6)',
// simplify: false,
// },
// },
// {
// name: `ζn_q(s,q)`,
// // TODO reverse these partial sums such that they're centered on pole
// x: ζs_q_partials.x,
// y: ζs_q_partials.y,
// z: ζs_q_partials.z.map(() => q - 1),
// type: 'scatter3d',
// mode: 'lines',
// line: {
// simplify: false,
// color: 'rgba(0, 48, 0, 0.6)',
// },
// },
// {
// name: `ζn(s,q)`,
// x: ζs_q_partials.x,
// y: ζs_q_partials.y,
// z: ζs_q_partials.z.map((v) => v + q - 1),
// type: 'scatter3d',
// mode: 'lines+markers',
// marker: {
// size: 2,
// opacity: 0.6,
// },
// line: {
// simplify: false,
// color: ζs_q_partials.z,
// colorscale: 'Greens',
// },
// },
// {
// name: `ζ(s)-ζ(s,q)+ζn(s,q)`,
// x: ζs_q_partials.x.map((v) => (ζs[0] - ζs_q.re + v)),
// y: ζs_q_partials.y.map((v) => (ζs[1] - ζs_q.im + v)),
// z: ζs_q_partials.z.map((v) => v + q - 1),
// type: 'scatter3d',
// mode: 'lines+markers',
// marker: {
// size: 2,
// opacity: 0.6,
// },
// line: {
// color: ζs_q_partials.z,
// colorscale: 'Viridis',
// },
// },
// {
// name: `ζ(s) - ζ(s, ℑs / π)`,
// x: [ ζs[0] - ζs_ℑs_π[0] ],
// y: [ ζs[1] - ζs_ℑs_π[1] ],
// z: [ s[1] / Math.PI - 1 ],
// text: [ 'ζ(s) - ζ(s, ℑs / π)' ],
// type: 'scatter3d',
// mode: 'markers+text',
// marker: {
// size: 3,
// color: 'steelblue',
// // opacity: 0.6,
// },
// line: {
// simplify: false,
// color: 'rgba(70, 130, 180, 0.8)',
// },
// },
// {
// name: `ζ(s)-ζ(s,q)+ζn_q(s,q)`,
// x: ζs_q_partials.x.map((v) => (ζs[0] - ζs_q.re + v)),
// y: ζs_q_partials.y.map((v) => (ζs[1] - ζs_q.im + v)),
// z: ζs_q_partials.z.map(() => q - 1),
// type: 'scatter3d',
// mode: 'lines',
// line: {
// simplify: false,
// color: 'rgba(60, 12, 86, 0.6)',
// },
// },
// {
// name: `ζ(s, ℑs / π)`,
// x: [ ζs_ℑs_π[0] ],
// y: [ ζs_ℑs_π[1] ],
// z: [ s[1] / Math.PI - 1 ],
// text: [ 'ζ(s, ℑs / π)' ],
// type: 'scatter3d',
// mode: 'markers+text',
// marker: {
// size: 3,
// color: 'orange',
// // opacity: 0.6,
// },
// line: {
// simplify: false,
// color: 'rgba(70, 130, 180, 0.8)',
// },
// },
// {
// name: `ζ(s,q)`,
// x: [ ζs_q.re, ζs_q.re, ζs_q.re ],
// y: [ ζs_q.im, ζs_q.im, ζs_q.im ],
// z: [ 1, q-1, N+1 ],
// text: ['','','ζ(s,q)'],
// type: 'scatter3d',
// mode: 'markers+lines+text',
// marker: {
// size: 3,
// color: 'green',
// opacity: 0.6,
// },
// line: {
// simplify: false,
// color: 'rgba(0, 48, 0, 0.4)',
// },
// },

// (() => {
// const data = {}
// data.x = ζs_q_partials.map((v) => (v.re))
// data.y = ζs_q_partials.map((v) => (v.im))
// data.z = ζs_q_partials.map(() => q)

// return {
// name: `ζn(s,q)`,
// ...data,
// type: 'scatter3d',
// mode: 'lines+markers',
// marker: {
// size: 1,
// opacity: 0.5,
// color: 'lime',
// },
// }
// })(),
// (() => {
// const spin = mathjs.exp(ℂ('i').mul(π * hurwitz_spin_factor * q))
// const ζs_qθ_partials = ζs_q_partials.map((v) => ζs_q.mul(spin).sub(v.mul(spin)))
// const data = {}
// data.x = ζs_qθ_partials.map((v) => v.re)
// data.y = ζs_qθ_partials.map((v) => v.im)
// data.z = ζs_qθ_partials.map(() => q)

// return {
// name: `ζ<sub>θ</sub>(s,q) - ζn<sub>θ</sub>(s,q)`,
// ...data,
// type: 'scatter3d',
// mode: 'lines+markers',
// marker: {
// size: 1,
// opacity: 0.5,
// color: 'coral',
// },
// }
// })(),
(() => {
const ζs_qθ_partials = ζs_q_partials.map((v) => v.mul(mathjs.exp(('i').mul(π * hurwitz_spin_factor * q))))
const data = {}
data.x = ζs_qθ_partials.map((v) => v.re)
data.y = ζs_qθ_partials.map((v) => v.im)
data.z = ζs_qθ_partials.map(() => q)

return {
name: `ζn<sub>θ</sub>(s,q)`,
...data,
type: 'scatter3d',
mode: 'lines+markers',
marker: {
size: 1,
opacity: 0.5,
color: 'olive',
},
}
})(),

{
name: `ζ(s)`,
x: [ ζs.re, ζs.re, ζs.re, ζs.re ],
y: [ ζs.im, ζs.im, ζs.im, ζs.im ],
z: [ 0, 1, Math.abs(s.im) / Math.PI, N+1 ],
text: ['', '', /*'ℑs / π'*/, 'ζ(s)'],
type: 'scatter3d',
mode: 'lines+markers+text',
marker: {
size: 3,
color: 'steelblue',
opacity: 0.6,
},
line: {
simplify: false,
color: 'rgba(200, 10, 10, 0.4)',
},
},
{
name: `ζ(s,1/2)`,
x: [ ζs.mul((2).pow(s).sub(1)).re ],
y: [ ζs.mul((2).pow(s).sub(1)).im ],
z: [ -1/2 ],
text: [ 'ζ(s,1/2)' ],
type: 'scatter3d',
mode: 'markers+text',
marker: {
size: 3,
color: 'black',
opacity: 0.6,
},
},
{
name: `ζ(s,1/N_div)`,
// x: [ ζs.mul(ℂ(2).pow(s).sub(1)).mul(ℂ(2).pow(s)).re ],
// y: [ ζs.mul(ℂ(2).pow(s).sub(1)).mul(ℂ(2).pow(s)).im ],
...(() => {
const v = ζ_H(s, 1/(N_subdiv + 1))
return { x: [ v.re ], y: [ v.im ] }
})(),
z: [ 1/(N_subdiv + 1) - 1 ],
text: [ `ζ(s,1/${N_subdiv + 1})` ],
type: 'scatter3d',
mode: 'markers+text',
marker: {
size: 3,
color: 'black',
opacity: 0.6,
},
visible: 'legendonly',
},
{
name: `ζ(s,q) -> origin`,
x: [ ζs_q.re, 0 ],
y: [ ζs_q.im, 0 ],
z: [ q - 1, q - 1 ],
type: 'scatter3d',
mode: 'lines',
line: {
color: 'blue',
},
visible: 'legendonly',
},
{
name: `ζ(s,q±1)`,
x: [ ζs_q_prev.re, ζs_q.re, ζs_q_next.re ],
y: [ ζs_q_prev.im, ζs_q.im, ζs_q_next.im ],
z: [ q - 2, q - 1, q ],
text: [ '', `ζ(s,q)`, '' ],
type: 'scatter3d',
mode: 'lines+markers+text',
// hoverinfo: 'x+y',
marker: {
size: 3,
color: 'rgba(226,140,18,0.9)',
},
visible: 'legendonly',
},
{
name: `ζ(1-s)`,
x: [ ζr.re ],
y: [ ζr.im ],
z: [ -1 ],
text: [ `ζ(1-s)` ],
type: 'scatter3d',
mode: 'markers+text',
marker: {
size: 3,
color: 'pink',
opacity: 0.6,
},
},
]
Insert cell
cameras = {
return {
top: {
center: {
x: 0,
y: 0,
z: 0,
},
eye: {
x: 0,
y: 0,
z: 1000,
},
up: {
x: 1,
y: 0,
z: 1,
},
projection: {
type: 'orthographic',
},
},
front: {
center: {
x: 0,
y: 0,
z: 0,
},
eye: {
x: 0,
y: 1,
z: 0,
},
up: {
x: 0,
y: 0,
z: 0,
},
projection: {
type: 'orthographic',
},
},
front_diagonal: {
center: {
x: 0,
y: 0,
z: 0,
},
eye: {
x: -1,
y: -1,
z: 1,
},
up: {
x: 0,
y: 0,
z: 1,
},
projection: {
type: 'orthographic',
},
},
}
}
Insert cell
size = Math.min(width, 600)
Insert cell
z_range_max = z_axis_log_mode ? Math.sign(N+1)*Math.log10(Math.abs(N+1)) : N+1
Insert cell
z_range_min = -(1-Number(z_axis_to_zero))
Insert cell
layout = ({
autosize: false,
width: plot_width,
height: plot_height,
showlegend: true,
// legend: {
// x: 1,
// y: 0.5
// // orientation: 'h',
// },
title: "ζ(s,q) partial sum geometry",
scene: {
dragmode: "turntable",
hovermode: "closest",
aspectratio: {
x: width_aspect_scale,
y: width_aspect_scale,
z: height_aspect_scale
},
// annotations: https://plot.ly/javascript/reference/#layout-scene-annotations
xaxis: {
range: [-rval, rval],
fixedrange: true,
hoverformat: ".5r" // d3-format spec
// showticklabels: false,
},
yaxis: {
range: [-rval, rval],
fixedrange: true,
hoverformat: ".5r" // d3-format spec
// showticklabels: false,
},
zaxis: {
range: [z_range_min, z_range_max],
fixedrange: true,
showticklabels: false,
type: z_axis_log_mode ? "log" : "linear"
// ticks: 'outside',
// nticks: 0,
// exponentformat: 'e',
// rangemode: 'tozero',
},
margin: {
l: 0,
r: 0,
b: 0,
t: 0
},
camera: camera_current
}
})
Insert cell
{
Plotly.react(
graphDiv,
traces,
layout,
{
scrollZoom: true,
displayModeBar: true,
displaylogo: false,
// modeBarButtonsToRemove: ['toImage'],
// responsive: true,
},
)
graphDiv.removeAllListeners()

// graphDiv.on('plotly_relayout', (event) => {
// set_camera(event['scene.camera'])
// })

graphDiv.on('plotly_click', (event) => {
console.log('click', event)
})

graphDiv.on('plotly_doubleclick', (event) => {
console.log('doubleclick', event)
})
}
Insert cell
Insert cell
Insert cell
π = Math.PI
Insert cell
Plotly = require("https://cdn.plot.ly/plotly-latest.min.js")
Insert cell
linspace = {
const m = await import("https://cdn.skypack.dev/linspace@1.0.2?min");
return m.default;
}
Insert cell
import { Scrubber } from "@mbostock/scrubber"
Insert cell
import {slider, checkbox, number} from "@jashkenas/inputs"
Insert cell
import { ζ_H_, mathjs } from '2a0dd2a4796acc42'
Insert cell
ζ_H = (z,a) => {
if (a > 1e6) return (NaN)
return ζ_H_(z,a)
}
Insert cell
= mathjs.complex
Insert cell
= mathjs.fraction
Insert cell
ρ = (n) => (0.5,ρ_i[n-1])
Insert cell
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more