nx_calc_series.nx source
↩ module page · 83 lines · 3179 B
1// nx_calc_series.nx -- Taylor / Maclaurin series expansion engine.
2//
3// Per the comparison commit's named LOSE_BIG vs Mathematica:
4// "ship symbolic Integrate + Series + Solve". Integrate shipped
5// last commit; this commit ships Series.
6//
7// Taylor series of f(x) about center 0 up to degree N:
8// f(x) ~ f(0) + f'(0)*x + f''(0)*x^2/2! + ... + f^(n)(0) * x^n / n!
9//
10// We build the symbolic series as a Term using nx_calc_deriv n times
11// to get each f^(k). At evaluation time the user can plug numeric
12// values; this engine returns the SYMBOLIC polynomial form.
13//
14// "About center 0" = Maclaurin. General Taylor about center c is the
15// same algorithm with substitution (x - c) for x; named follow-up.
16
17// nx_safety_envelope:
18// intended_use: AUTO_APPLIED -- primitive-specific tuning queued
19// sil_target: SIL1
20// evidence: [bulk_applied_2026-05-16, see-file-comment-for-detail]
21// verdict: NOT_YET_EVALUATED
22
23import "nx_calc.nx"
24
25// nx_factorial(n) -- precompute small factorials for series coefficients.
26func nx_factorial(n: nx_int) -> nx_int {
27 var r: nx_int = 1
28 var i: nx_int = 1
29 while i <= n {
30 r = r * i
31 i = i + 1
32 }
33 return r
34}
35
36// Substitute x -> 0 in t. We don't have a general substitution
37// engine yet, so use a structural pass that replaces every VAR with
38// CONST(0). Returns a new Term.
39func nx_calc_subst_zero(t: *Term, var_id: nx_int) -> *Term {
40 if t.kind == NX_TERM_VAR {
41 if t.sym == var_id { return nx_calc_const_int(0) }
42 return t
43 }
44 if t.kind == NX_TERM_CONST { return t }
45 if t.sym == NX_CALC_SYM_CONST { return t }
46 if t.n_args == 1 {
47 let inner: *Term = nx_term_arg(t, 0)
48 let new_inner: *Term = nx_calc_subst_zero(inner, var_id)
49 return nx_calc_un(t.sym, new_inner)
50 }
51 if t.n_args == 2 {
52 let a: *Term = nx_term_arg(t, 0)
53 let b: *Term = nx_term_arg(t, 1)
54 let new_a: *Term = nx_calc_subst_zero(a, var_id)
55 let new_b: *Term = nx_calc_subst_zero(b, var_id)
56 return nx_calc_bin(t.sym, new_a, new_b)
57 }
58 return t
59}
60
61// Maclaurin series: build f(0) + f'(0)*x + f''(0)*x^2/2! + ... up to
62// degree N. Returns a Term that is the polynomial sum. Each k-th
63// coefficient = f^(k)(0) / k! built symbolically.
64func nx_calc_maclaurin(f: *Term, var_id: nx_int, max_degree: nx_int) -> *Term {
65 let x: *Term = nx_term_var(var_id)
66 var sum: *Term = nx_calc_subst_zero(f, var_id) // c0 = f(0)
67 var current: *Term = f
68 var k: nx_int = 1
69 while k <= max_degree {
70 // current = derivative_of_previous
71 current = nx_calc_deriv(current, var_id)
72 let coef_at_zero: *Term = nx_calc_subst_zero(current, var_id)
73 let kfact: nx_int = nx_factorial(k)
74 let coef_term: *Term = nx_calc_bin(NX_CALC_SYM_DIV,
75 coef_at_zero,
76 nx_calc_const_int(kfact))
77 let xk: *Term = nx_calc_pow(x, nx_calc_const_int(k))
78 let term_k: *Term = nx_calc_mul(coef_term, xk)
79 sum = nx_calc_add(sum, term_k)
80 k = k + 1
81 }
82 return sum
83}