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.
Evaluate a periodic solution family
A symbolic solve can return an indexed family rather than a finite list. For
sin(x)=0, Nerdamer returns {_n*pi}, where _n ranges over the integers.
Store the SolutionSet with :, then provide a value for _n only for the
evaluation that needs it.
sol: solve(sin(x),x);
evaluate(sol,{_n=>0})
The result is {0}. The substitution Dictionary is local to evaluate; it does not assign
_n globally. _n:0 remains invalid because _n is reserved for solver-generated
families. Also note that = is equation syntax, not assignment syntax, so
sol = solve(sin(x),x) would not store the solver result under sol.
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(foundRoots,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]
-foundRoots[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,
foundRoots: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(
foundRoots,
rootCount,
attempt,
tolerance
)
),
block(
if(
rootCount,
foundRoots:augment(
foundRoots,
transpose(matrix(attempt))
),
foundRoots:transpose(matrix(attempt))
),
rootCount:rootCount+1
)
)
)
)
),
if(
rootCount,
return(transpose(foundRoots)),
return([])
)
)
);
runNewtonExample(residuals,variables,seeds,iterations,tolerance):=
let(
J:jacobian(residuals,variables),
starts:buildStarts(seeds,variables),
foundRoots:findRoots(
residuals,
variables,
J,
starts,
iterations,
tolerance
),
return(numeric(foundRoots,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.
