# Examples The examples are YAML configurations run through `simulations/main.py`. They are ordered by increasing complexity: constant properties, then latent heat, then temperature-dependent properties. This page close with library-mode usage. Reading them in order is recommended. All commands are issued from the repository root with [`uv`](https://github.com/astral-sh/uv): ```bash uv sync # one-time environment setup ``` The configurations are in `simulations/examples/`. ## Key concepts ### The configuration file Each example is one YAML file with five top-level sections: | Section | Describes | |---|---| | `simulation` | compute backend, time step `dt`, total `duration` | | `domain` | box size `[Lx, Ly, Lz]` and grid `[nx, ny, nz]` (the grid also sets the number of spectral modes) | | `material` | density, conductivity, heat capacity, latent heat, and phase-change temperatures | | `laser` | beam radius, absorptivity, power, and the path file | | `io` | which outputs to write, and where | A value is either a plain number (SI units assumed) or a `{value, unit}` pair: ```yaml dt: value: 4.0e-6 unit: "s" ``` `01_quickstart.yaml` is annotated field by field; {doc}`configuration` lists every key. ### The laser path (G-code) The laser trajectory is a G-code file under `simulations/examples/paths/`, selected by `laser.path.file`. The parser reads the subset needed to drive a moving heat source: | Command | Meaning | |---|---| | `G21`, `G90` | millimetre units, absolute coordinates | | `G0 X.. Y..` | rapid move, laser off (positioning) | | `M3 S

` / `M5` | laser on at power `

` watts / laser off | | `G1 X.. Y.. F` | linear move at feedrate `` (mm/min); sets the scan speed | The quickstart path is a single track scanned at 0.8 m/s with a 200 W beam: ```gcode G21 ; mm G90 ; absolute G0 X0.0 Y1.25 ; move to start, laser off M3 S200 ; laser on, 200 W G1 X9.7 Y1.25 F48000 ; scan +x at 48000 mm/min = 0.8 m/s M5 ; laser off ``` The absorbed power is the beam power (`S`) times `laser.absorptivity`. Longer trajectories chain several `G0`/`G1` moves. (example-quickstart)= ## 1. Quickstart {download}`01_quickstart.yaml <../simulations/examples/01_quickstart.yaml>` scans a single laser track over a coarse cuboid with constant material properties. Latent heat and surface convection are disabled, leaving evaporative cooling as the only non-linear surface flux. The run takes about a minute on one CPU core. Its YAML is annotated field by field and serves as the configuration reference. ```bash uv run python simulations/main.py simulations/examples/01_quickstart.yaml ``` Results are written to `out/_quickstart/`: - `profiles/` — temperature along $x$, $y$, $z$ through the laser spot (text). - `fields/` — the final volume field, readable in ParaView through the `.xmf` file. - `slices/` — the cross-section image configured by `io.slice_planes`. - `logs/simulation.log` — per-step metrics (`T_surface_max`, laser power). ```{figure} _images/example_01_meltpool.png :alt: Longitudinal melt-pool section, example 01 :width: 100% Longitudinal ($xz$, normal $y$) section through the laser spot at the end of the run. The red contour marks the solidus/liquidus. ``` (example-single-track)= ## 2. Single track with latent heat {download}`02_single_track.yaml <../simulations/examples/02_single_track.yaml>` adds the two effects that example 1 omits: latent heat of fusion (`L_f`) and bottom-face convection (`h_conv_bottom`). Properties remain constant, obtained by evaluating the temperature-dependent 316L polynomials at $T_0 = 293$ K. The domain is small with a fine $z$-grid ($\Delta z \approx 1.25\ \mu\text{m}$) so the narrow 316L mushy zone is resolved. The run takes a few minutes on one CPU core. ```{note} The 316L thermophysical properties used in these examples ($\rho$, $k$, $c_p$, with separate solid and liquid branches) are taken from Chadwick et al., *Acta Materialia* **282** (2025) 120482, [doi:10.1016/j.actamat.2024.120482](https://doi.org/10.1016/j.actamat.2024.120482). ``` ```bash uv run python simulations/main.py simulations/examples/02_single_track.yaml ``` ```{figure} _images/example_02_meltpool.png :alt: Longitudinal melt-pool section, example 02 :width: 100% Melt-pool section for the single track. The two red contours are the solidus and liquidus. ``` (example-nonlinear)= ## 3. Temperature-dependent properties {download}`03_nonlinear_tdep.yaml <../simulations/examples/03_nonlinear_tdep.yaml>` lets the conductivity, density and specific heat vary with temperature (solid and liquid expression branches blended by the liquid fraction), making the problem non-linear. Each step is solved by a Picard iteration that also resolves the property correction (see {doc}`theory`): - `max_picard_iter` — iteration cap per step (`80` here). - `picard_omega` — relaxation factor; larger values converge faster until the iteration destabilises (`0.31` is near the limit for this case). ```bash uv run python simulations/main.py simulations/examples/03_nonlinear_tdep.yaml ``` ```{figure} _images/example_03_meltpool.png :alt: Conduction-mode melt pool, example 03 :width: 100% Melt-pool section for the temperature-dependent run. The red line marks the solidus/liquidus. ``` This is a reduced-grid version of the finite-element comparison case. (example-library)= ## 4. Library mode The solver can be driven directly from Python rather than the CLI. An external script builds a `SimulationContext` and calls `solver.step(...)` in a loop, which is useful when embedding the solver in a larger codebase. Nothing is written to disk. **Installing as a library.** To import `spectral_galerkin_heat` from a project outside this repository, install it as an editable package into your environment: ```bash uv pip install -e /path/to/spectral_galerkin_heat # or: pip install -e /path/to/spectral_galerkin_heat ``` Or declare it as an editable source in another `uv` project's `pyproject.toml`: ```toml [project] dependencies = ["spectral_galerkin_heat"] [tool.uv.sources] spectral_galerkin_heat = { path = "/path/to/spectral_galerkin_heat", editable = true } ``` Either way the import name is `spectral_galerkin_heat`, and edits to the source take effect without reinstalling. The full runnable script is {download}`orchestrator.py <../simulations/examples/orchestrator.py>`: ```bash uv run python simulations/examples/orchestrator.py ``` ```python from spectral_galerkin_heat.core.parameters import SimulationContext from spectral_galerkin_heat.solvers.spectral import SpectralSolver from spectral_galerkin_heat.backends import NumpyBackend # 1. Configuration as a plain dictionary config = { "simulation": {"backend": "cpu", "dt": 6e-6, "duration": 6e-5}, "domain": {"size": [0.01, 0.005, 0.0025], "mesh": [64, 32, 16]}, "material": {"rho": 7957.5, "k": 13.851, "Cp": 497.89, "name": "316L"}, # reference 316L values "laser": {"radius": 60.0e-6, "absorptivity": 0.30, "power_nominal": 200.0, "path": {"type": "gcode", "file": "linear_track.gcode"}}, "io": {}, # empty: the IOManager is not used } # 2. Build the context. config_dir must point to the directory holding the # paths/ subfolder with the G-code (use an absolute path when running elsewhere). context = SimulationContext.from_dict(config, config_dir="simulations/examples") # 3. Instantiate and initialise the solver solver = SpectralSolver(NumpyBackend()) # get_backend("cupy") for GPU state = solver.initialize(context) # 4. Time loop t, dt, t_end = 0.0, context.num.dt, context.num.t_end while t < t_end: state, metrics = solver.step(t, dt) t += dt print(f"t = {t:.4e} s | T_max = {metrics.get('T_surface_max'):.1f} K") ``` ## Further reading Every configuration key is documented in {doc}`configuration`, and the output formats in {doc}`outputs`.