Nerdamer Scripting

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.

Performance: Nerdamer Scripting adds parser and deferred-execution overhead compared with equivalent direct JavaScript / TypeScript code. It is useful when keeping the algorithm inside Nerdamer's language is valuable, but use it cautiously for performance-sensitive loops or repeated numerical work. The direct API may be a better fit for those workloads.

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.

JavaScript / TypeScript alternative: The same example is available using the direct JavaScript / TypeScript API. That version moves the iteration and multistart control into application code instead of the Nerdamer scripting layer.
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.

JavaScript / TypeScript alternative: The same calculation is available using the direct JavaScript / TypeScript API. The scripting form keeps the entire symbolic workflow inside Nerdamer's language.
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.