Scripting examples
These examples are written as Nerdamer Scripting source only. Paste the text into a Nerdamer scripting input, the Playground, or another interface that evaluates Nerdamer language input.
Newton's method for a nonlinear system
This example solves three nonlinear equations using a symbolic Jacobian, Newton iteration, and a multistart search
for distinct real roots. The Jacobian is built once and then evaluated at each trial point with
evaluate(value, variables, point).
Based on the multivariable Newton method described in Courtney Remani, Numerical Methods for Solving Systems of Nonlinear Equations, Lakehead University.
jacobian(residuals,variables):=
let(
dimension:count(variables),
J:imatrix(dimension),
row:0,
col:0,
block(
for(
row:0,
dimension-row,
row:row+1,
for(
col:0,
dimension-col,
col:col+1,
J[row,col]:diff(residuals[row],variables[col])
)
),
return(J)
)
);
solveLinear(coefficients,rhsVector):=
let(
dimension:count(rhsVector),
rhsMatrix:transpose(matrix(rhsVector)),
reduced:rref(augment(coefficients,rhsMatrix)),
solution:rhsVector*0,
row:0,
block(
for(
row:0,
dimension-row,
row:row+1,
solution[row]:reduced[row,dimension]
),
return(solution)
)
);
newtonSystem(residuals,variables,J,initial,iterations,tolerance):=
let(
point:initial,
iteration:0,
block(
for(
iteration:0,
iterations-iteration,
iteration:iteration+1,
let(
values:evaluate(residuals,variables,point),
if(
dot(values,values)<tolerance^2,
return(point),
let(
numericJacobian:evaluate(J,variables,point),
step:solveLinear(numericJacobian,values),
point:numeric(point-step,20)
)
)
)
),
let(
values:evaluate(residuals,variables,point),
if(
dot(values,values)<tolerance^2,
return(point),
return([])
)
)
)
);
containsRoot(roots,rootCount,candidate,tolerance):=
let(
rootIndex:0,
coordinate:0,
found:0,
block(
for(
rootIndex:0,
rootCount-rootIndex,
rootIndex:rootIndex+1,
let(
same:1,
block(
for(
coordinate:0,
count(candidate)-coordinate,
coordinate:coordinate+1,
if(
abs(
candidate[coordinate]
-roots[coordinate,rootIndex]
)>tolerance,
block(
same:0,
break()
)
)
),
if(
same,
block(
found:1,
break()
)
)
)
)
),
return(found)
)
);
buildStarts(seeds,variables):=
let(
dimension:count(variables),
seedCount:count(seeds),
total:seedCount^dimension,
columns:imatrix(dimension),
startIndex:0,
coordinate:0,
startCount:0,
block(
for(
startIndex:0,
total-startIndex,
startIndex:startIndex+1,
let(
candidate:variables*0,
block(
for(
coordinate:0,
dimension-coordinate,
coordinate:coordinate+1,
candidate[coordinate]:
seeds[
mod(
floor(
startIndex/
(
seedCount^
(dimension-coordinate-1)
)
),
seedCount
)
]
),
if(
startCount,
columns:augment(
columns,
transpose(matrix(candidate))
),
columns:transpose(matrix(candidate))
),
startCount:startCount+1
)
)
),
return(transpose(columns))
)
);
findRoots(residuals,variables,J,starts,iterations,tolerance):=
let(
dimensions:size(starts),
startCount:dimensions[0],
rootCount:0,
roots:imatrix(count(variables)),
startIndex:0,
block(
for(
startIndex:0,
startCount-startIndex,
startIndex:startIndex+1,
let(
attempt:iferror(
newtonSystem(
residuals,
variables,
J,
starts[startIndex],
iterations,
tolerance
),
[]
),
if(
count(attempt),
if(
not(
containsRoot(
roots,
rootCount,
attempt,
tolerance
)
),
block(
if(
rootCount,
roots:augment(
roots,
transpose(matrix(attempt))
),
roots:transpose(matrix(attempt))
),
rootCount:rootCount+1
)
)
)
)
),
if(
rootCount,
return(transpose(roots)),
return([])
)
)
);
runNewtonExample(residuals,variables,seeds,iterations,tolerance):=
let(
J:jacobian(residuals,variables),
starts:buildStarts(seeds,variables),
roots:findRoots(
residuals,
variables,
J,
starts,
iterations,
tolerance
),
return(numeric(roots,8))
);
runNewtonExample(
[
x^2+y^2+z^2-14,
x*y+y*z+z*x-11,
x*y*z-6
],
[x,y,z],
[0,2,4],
12,
1e-8
)
The 27 starting points generated from [0,2,4] recover all six permutations of
[1,2,3] for this system. The substitution-aware evaluate calls avoid repeatedly rebuilding
the residuals and Jacobian with subst().
Derive the normal modes of coupled oscillators
Two identical masses connected by three springs have symmetric and antisymmetric normal modes. This scripting version builds the symbolic mass and stiffness matrices, solves the characteristic equation, and derives a normalized nullspace basis for each mode.
Follow the same physical system in McGill University's PHYS 232 notes: Coupled oscillators — 2 masses and 3 springs.
modeShape(dynamic,eigenvalue):=
let(
basis:nullspace(
evaluate(dynamic,{q=>eigenvalue})
),
shape:basis[0],
return(shape/shape[0])
);
normalModes():=
let(
K:matrix(
[k+kappa,-kappa],
[-kappa,k+kappa]
),
M:matrix(
[m,0],
[0,m]
),
dynamic:K-M*q,
characteristic:factor(determinant(dynamic)),
eigenvalues:solve(characteristic,q),
frequencies:[
simplify(eigenvalues[0]^(1/2)),
simplify(eigenvalues[1]^(1/2))
],
modes:[
modeShape(dynamic,eigenvalues[0]),
modeShape(dynamic,eigenvalues[1])
],
return([
characteristic,
eigenvalues,
frequencies,
modes
])
);
assume(k>0);
assume(m>0);
assume(kappa>0);
normalModes()
The returned value contains the factored characteristic expression, the two eigenvalues
(omega^2), their square roots, and the normalized mode shapes. The assumptions restrict the symbolic
parameters to the physical positive domain, as in the direct API example.
Where to go next
See the Nerdamer Scripting reference for the language constructs used above, or compare this example with the direct JavaScript / TypeScript version.
