---
title: "Mathematica on Easley"
description: "Derive exact beam-deflection formulas with Mathematica, then run the examples across CPUs, nodes, and a GPU on Easley."
type: Tutorial
tags:
  - Mathematica
  - Wolfram Language
  - Slurm
  - Easley
  - GPU
  - Scientific computing
generated:
  by: "process:codex"
  at: "2026-09-29T00:00:00Z"
---

# Mathematica on Easley

Mathematica combines symbolic mathematics with numerical computing. This
tutorial distributes symbolic derivations across CPUs and nodes, then
evaluates many load combinations on a GPU.

The distinguishing feature is the workflow: solve differential equations with
symbolic parameters, manipulate the resulting formulas, and then evaluate them
numerically in the same language. MATLAB, Julia, and R also have symbolic
tools; this example highlights Mathematica's built-in symbolic solver.

## Files and setup

Download [the complete example archive (ZIP)](https://carc.unm.edu/docs/assets/files/mathematica/mathematica-examples.zip),
extract it, and work in that directory on Easley. Keep it on a filesystem
accessible from compute nodes.

```bash
wget https://carc.unm.edu/docs/assets/files/mathematica/mathematica-examples.zip
unzip mathematica-examples.zip
cd mathematica-examples
```

The archive includes the three Slurm scripts, Wolfram Language inputs, and the
shared launcher. Run the jobs below one at a time.

The examples use `neutrino.phys.unm.edu`; please substitute the fully
qualified hostname of your Mathematica license server. The shared
`math-kernel.sh` launcher contains:

```bash
#!/bin/bash
# Use the same license server for the controller and every worker.
# Replace neutrino.phys.unm.edu with your license server's FQDN.
exec math -pwfile <(printf '!neutrino.phys.unm.edu\n') "$@"
```

The Bash inline file supplies the license information without creating a
persistent license file. `"$@"` forwards the kernel's arguments. Both the
controller and parallel workers use this launcher, so each gets the same
server. See Wolfram's [kernel documentation](https://reference.wolfram.com/language/ref/program/WolframKernel.html){target=_blank}
for `-pwfile` details.

## Example: Beam Deflection

For a simply supported beam, the small-deflection model is:

> *EI* d⁴*y*/d*x*⁴ = *q*₀ (*x*/L)ⁿ

with simply supported boundary conditions:

> *y*(0) = *y*(L) = 0; d²*y*/d*x*²(0) = d²*y*/d*x*²(L) = 0

Here, `y(x)` is downward deflection, `length` is the beam length, `load` is
the load-intensity scale, and `rigidity` is the flexural rigidity (`E × I`).
The ends have zero deflection and zero bending moment. Setting `n = 0` gives a
uniform load; `n = 1` gives a linearly increasing load.

The shared `beam-model.wl` input derives the formula without assigning
numerical values to these parameters:

```wolfram
(* Simply supported beam: load (x/length)^power, constant flexural rigidity. *)
Clear[beamDeflection, x, length, load, rigidity];
beamDeflection[power_Integer] := Module[{y},
    Factor[DSolveValue[{
        rigidity y''''[x] == load (x/length)^power,
        y[0] == 0, y[length] == 0,
        y''[0] == 0, y''[length] == 0
    }, y[x], x]]
];
```

`DSolveValue` solves the differential equation and its boundary conditions.
`Factor` simplifies the resulting polynomial. For a uniform load, the exact
midpoint deflection is:

> *y*(L/2) = 5*q*₀L⁴/(384 *EI*)

This formula describes a family of beams and loads rather than one numerical
case.

## Multiple CPUs on one node

`parallel.wl` launches two local workers and gives them the definition from
`beam-model.wl`. `ParallelTable` derives midpoint formulas for load powers 0,
1, 2, and 3. It also prints each worker's hostname and license server.

```wolfram title="parallel.wl"
workers = ToExpression[Environment["SLURM_CPUS_PER_TASK"]] - 1;
launcher = FileNameJoin[{Directory[], "math-kernel.sh"}];

LaunchKernels[KernelConfiguration["Local",
    "KernelCommand" -> ("\"" <> launcher <> "\""),
    "KernelCount" -> workers]];
If[Length[Kernels[]] != workers, Print["Worker launch failed"]; Exit[1]];

Print["Workers: ", ParallelEvaluate[{$KernelID, $MachineName}]];
Print["Worker license servers: ", ParallelEvaluate[$LicenseServer]];
Get["beam-model.wl"];
DistributeDefinitions[beamDeflection];
midpoints = ParallelTable[
    Factor[beamDeflection[n] /. x -> length/2], {n, 0, 3}];
Print["Midpoint formulas (load powers 0 through 3): ",
    ToString[midpoints, InputForm]];
CloseKernels[];
Exit[];
```

`mathematica_multicpu.sbatch` contains:

```bash
#!/bin/bash -l
#SBATCH --job-name=mathematica-multicpu
#SBATCH --partition=debug
#SBATCH --nodes=1
#SBATCH --ntasks=1
#SBATCH --cpus-per-task=3
#SBATCH --mem=4G
#SBATCH --time=00:05:00
#SBATCH --output=%x-%j.out

module load mathematica/15.0.1
cd "$SLURM_SUBMIT_DIR"
chmod +x math-kernel.sh
export OMP_NUM_THREADS=1 MKL_NUM_THREADS=1 OPENBLAS_NUM_THREADS=1

srun ./math-kernel.sh -script parallel.wl
```

Submit it with:

```bash
sbatch mathematica_multicpu.sbatch
```

Three CPUs provide one for the controller and two for workers. The thread
settings limit each worker's math libraries to one thread. To request more
workers, increase `--cpus-per-task`; the input reserves one CPU for the
controller. Available licenses also limit the worker count.

The output includes two workers on the same node and these four exact formulas:

```text
{5 load length^4/(384 rigidity),
 5 load length^4/(768 rigidity),
 89 load length^4/(23040 rigidity),
 13 load length^4/(5120 rigidity)}
```

### Measured scaling

CARC timed 128 independent symbolic beam calculations on Easley with
Mathematica 15.0.1. Each result is the median of three runs after kernel
creation and warm-up; every requested worker processed tasks and produced the
same formulas.

| Workers | Median calculation time | Speedup |
| ---: | ---: | ---: |
| 1 | 2.21 s | 1.00× |
| 2 | 1.21 s | 1.83× |
| 4 | 0.59 s | 3.77× |

## Multiple nodes

`distributed.wl` performs the same four derivations with one worker on each
of two nodes. It uses Slurm to start the workers and Mathematica's WSTP
protocol to connect them to the controller.

```wolfram title="distributed.wl"
nodes = StringSplit[RunProcess[
    {"scontrol", "show", "hostnames", Environment["SLURM_JOB_NODELIST"]},
    "StandardOutput"]];
launcher = FileNameJoin[{Directory[], "math-kernel.sh"}];

links = Table[LinkCreate[LinkProtocol -> "TCPIP"], {Length[nodes]}];
processes = Table[
    StartProcess[{
        "srun", "--exact", "--nodes=1", "--ntasks=1", "--cpus-per-task=1",
        "--nodelist=" <> nodes[[i]],
        "--output=worker-%j-%N.out", "--error=worker-%j-%N.err",
        launcher, "-subkernel", "-noinit", "-wstp",
        "-linkmode", "Connect", "-linkprotocol", "TCPIP",
        "-linkname", First[links[[i]]]
    }],
    {i, Length[nodes]}
];

TimeConstrained[LaunchKernels[links], 90, Exit[1]];
If[Length[Kernels[]] != Length[nodes], Exit[1]];

Print["Workers: ", ParallelEvaluate[{$KernelID, $MachineName}]];
Print["Worker license servers: ", ParallelEvaluate[$LicenseServer]];
Get["beam-model.wl"];
DistributeDefinitions[beamDeflection];
midpoints = ParallelTable[
    Factor[beamDeflection[n] /. x -> length/2], {n, 0, 3}];
Print["Midpoint formulas (load powers 0 through 3): ",
    ToString[midpoints, InputForm]];

Scan[LinkWrite[#, Unevaluated[EvaluatePacket[Quit[0]]]] &, links];
TimeConstrained[
    While[AnyTrue[processes, ProcessStatus[#] === "Running" &], Pause[0.1]],
    20, Exit[1]
];
codes = ProcessInformation[#, "ExitCode"] & /@ processes;
CloseKernels[];
If[codes =!= ConstantArray[0, Length[nodes]], Exit[1]];
Exit[];
```

`mathematica_multinode.sbatch` contains:

```bash
#!/bin/bash -l
#SBATCH --job-name=mathematica-multinode
#SBATCH --partition=debug
#SBATCH --nodes=2
#SBATCH --ntasks-per-node=2
#SBATCH --cpus-per-task=1
#SBATCH --mem=4G
#SBATCH --time=00:05:00
#SBATCH --output=%x-%j.out

module load mathematica/15.0.1
cd "$SLURM_SUBMIT_DIR"
chmod +x math-kernel.sh
export OMP_NUM_THREADS=1 MKL_NUM_THREADS=1 OPENBLAS_NUM_THREADS=1

./math-kernel.sh -script distributed.wl
```

Submit it with:

```bash
sbatch mathematica_multinode.sbatch
```

The controller runs directly in the batch step. The input starts separate
`srun` steps for its workers; adding `srun` in front of the controller would
start duplicate controllers. Each worker uses the same shared license launcher.

Expect the same formulas as above, but worker hostnames on **two different
nodes**. Worker messages are saved in `worker-*.out` and `worker-*.err`. The
input includes a connection timeout and closes workers before exiting.

## GPU evaluation

The GPU example uses the symbolic formulas as input to a numerical calculation.
It does not run the symbolic differential-equation solver on the GPU.

`gpu.wl` derives four beam-response formulas on the CPU. It sets `length =
load = rigidity = 1` to obtain normalized response shapes, samples them at 256
positions, and combines them using 1,024 sets of load coefficients:

```wolfram
gpuResult = CUDADot[shapeMatrix, coefficients];
```

This GPU matrix multiplication produces deflection at every position for every
load combination. Superposition applies because the beam equation is linear.
The example compares the GPU result with a CPU multiplication and prints the
maximum difference. See Wolfram's [CUDADot documentation](https://reference.wolfram.com/language/CUDALink/ref/CUDADot.html){target=_blank}.

```wolfram title="gpu.wl"
Get["beam-model.wl"];
Needs["CUDALink`"];
If[!TrueQ[CUDAQ[]], Print["CUDA unavailable"]; Exit[1]];

(* Derive exact responses for four polynomial loads, then normalize the units. *)
shapes = Table[
    beamDeflection[n] /. {length -> 1, load -> 1, rigidity -> 1},
    {n, 0, 3}
];
Print["Uniform-load formula: ", ToString[shapes[[1]], InputForm]];

(* Evaluate at 256 positions for 1024 combinations of the four loads. *)
positions = N[Subdivide[0, 1, 255]];
shapeMatrix = Table[shapes /. x -> position, {position, positions}];
coefficients = N[Table[Sin[i j/100], {i, 1, 4}, {j, 1, 1024}]];

gpuResult = CUDADot[shapeMatrix, coefficients];
difference = Max[Abs[Flatten[gpuResult - shapeMatrix.coefficients]]];
Print["License server: ", $LicenseServer];
Print["Result dimensions: ", Dimensions[gpuResult]];
Print["Maximum difference from CPU: ", difference];
If[difference > 10^-10, Exit[1]];
Exit[];
```

`mathematica_gpu.sbatch` contains:

```bash
#!/bin/bash -l
#SBATCH --job-name=mathematica-gpu
#SBATCH --partition=debug
#SBATCH --nodes=1
#SBATCH --ntasks=1
#SBATCH --cpus-per-task=1
#SBATCH --gpus-per-node=1
#SBATCH --mem=4G
#SBATCH --time=00:05:00
#SBATCH --output=%x-%j.out

module load mathematica/15.0.1
cd "$SLURM_SUBMIT_DIR"
chmod +x math-kernel.sh

srun ./math-kernel.sh -script gpu.wl
```

Submit it with:

```bash
sbatch mathematica_gpu.sbatch
```

Expect result dimensions `{256, 1024}` and a maximum CPU/GPU difference close
to zero. Small floating-point differences are normal. This example checks the
CPU-to-GPU workflow; a small workload may be faster on the CPU.

On first use, CUDALink may download supporting software into your Wolfram
cache, which can add startup time.

## Read the results

`sbatch` prints the job number. Check progress with:

```bash
squeue --me
```

Each job writes `mathematica-<mode>-<jobid>.out` in the submission directory.
For example:

```bash
cat mathematica-multicpu-1235085.out
```

Use your own job number. If a job reports `No valid password found`, check the
hostname in `math-kernel.sh` and license availability. If a two-node debug job
is pending with `QOSMaxNodePerUserLimit`, let your other debug jobs finish.

## Further reading

- [Wolfram Language](https://reference.wolfram.com/language/){target=_blank}
- [Parallel kernel configuration](https://reference.wolfram.com/language/ref/KernelConfiguration.html){target=_blank}
- [CUDALink](https://reference.wolfram.com/language/CUDALink/tutorial/Overview.html){target=_blank}
- [Introduction to Slurm](https://carc.unm.edu/docs/running-jobs/slurm-intro/)
