Metadata-Version: 2.5
Name: laserttm
Version: 0.1.7
Summary: Two-temperature model solvers for ultrafast pulsed-laser heating of metals (Python port of the Ultrafast Laser TTM Toolbox)
Project-URL: Homepage, https://github.com/dfieser/ultrafast-laser-ttm-py
Project-URL: MATLAB reference implementation, https://github.com/dfieser/ultrafast-laser-ttm-toolbox
Project-URL: Paper, https://doi.org/10.1007/s11665-026-14738-6
Author-email: David Fieser <dfieser9@gmail.com>
License: MIT License
        
        Copyright (c) 2026 David Fieser
        
        Permission is hereby granted, free of charge, to any person obtaining a copy
        of this software and associated documentation files (the "Software"), to deal
        in the Software without restriction, including without limitation the rights
        to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
        copies of the Software, and to permit persons to whom the Software is
        furnished to do so, subject to the following conditions:
        
        The above copyright notice and this permission notice shall be included in all
        copies or substantial portions of the Software.
        
        THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
        IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
        FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
        AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
        LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
        OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
        SOFTWARE.
License-File: LICENSE
Keywords: femtosecond,heat-accumulation,laser-heating,tungsten,two-temperature-model,ultrafast-laser
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Science/Research
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Topic :: Scientific/Engineering :: Physics
Requires-Python: >=3.10
Requires-Dist: matplotlib>=3.7
Requires-Dist: numba>=0.58
Requires-Dist: numpy>=1.24
Requires-Dist: scipy>=1.10
Provides-Extra: dev
Requires-Dist: mcp>=1.2; extra == 'dev'
Requires-Dist: pytest>=7.0; extra == 'dev'
Requires-Dist: ruff; extra == 'dev'
Provides-Extra: mcp
Requires-Dist: mcp>=1.2; extra == 'mcp'
Description-Content-Type: text/markdown

<p align="center">
  <picture>
    <source media="(prefers-color-scheme: dark)" srcset="docs/assets/banner-dark.png">
    <img src="docs/assets/banner.png" alt="Ultrafast Laser TTM Toolbox: two-temperature model solvers for femtosecond laser heating of metals" width="100%">
  </picture>
</p>

<p align="center">
  <a href="https://doi.org/10.1007/s11665-026-14738-6"><img src="https://img.shields.io/badge/Paper-10.1007%2Fs11665--026--14738--6-b31b1b" alt="Paper DOI"></a>
  <a href="https://doi.org/10.5281/zenodo.22210435"><img src="https://zenodo.org/badge/1352513618.svg" alt="DOI"></a>
  <a href="https://github.com/dfieser/ultrafast-laser-ttm-py/actions/workflows/ci.yml"><img src="https://img.shields.io/github/actions/workflow/status/dfieser/ultrafast-laser-ttm-py/ci.yml?branch=main&label=CI" alt="CI status"></a>
  <a href="https://pypi.org/project/laserttm/"><img src="https://img.shields.io/pypi/v/laserttm" alt="PyPI version"></a>
  <a href="https://github.com/dfieser/ultrafast-laser-ttm-py/releases"><img src="https://img.shields.io/github/v/release/dfieser/ultrafast-laser-ttm-py" alt="Latest release"></a>
  <a href="LICENSE"><img src="https://img.shields.io/badge/License-MIT-yellow.svg" alt="MIT License"></a>
  <img src="https://img.shields.io/badge/Python-3.10%2B%20%C2%B7%20NumPy%20%C2%B7%20SciPy%20%C2%B7%20Numba-3776ab" alt="Python 3.10+, NumPy, SciPy, Numba">
</p>

Python solvers for ultrafast pulsed-laser heating of metals, built on the two-temperature model (TTM) and packaged as **`laserttm`**. The toolbox spans single-pulse electron-lattice dynamics on femtosecond timescales, heat accumulation over thousands of pulses, and moving-beam scans. A two-stage solution strategy keeps multi-pulse simulations fast on a laptop, with no MATLAB required.

This is the Python port of the [Ultrafast Laser TTM Toolbox](https://github.com/dfieser/ultrafast-laser-ttm-toolbox), the MATLAB reference implementation for the model published in:

> Fieser, D., Dewanjee, U. N., and Hu, A. (2026). *A Computationally Efficient Two-Stage Two-Temperature Model for Multi-pulse Femtosecond Laser Heat Accumulation in Tungsten*. Journal of Materials Engineering and Performance. [doi:10.1007/s11665-026-14738-6](https://doi.org/10.1007/s11665-026-14738-6)

The defaults reflect the tungsten work in the paper, but material presets for W, Cu, Au, and Al plus a `custom` mode support other metals, pulse widths, spot sizes, repetition rates, and scanning conditions.

## Highlights

- **Two-stage multi-pulse strategy.** Each pulse period splits into a full electron-lattice TTM solve during the pulse and relaxation, then Crank-Nicolson thermal diffusion for the inter-pulse gap. The depth solvers use a stiff BDF integrator and the 0D solvers use step-identical adaptive RK4. The baseline 50-pulse accumulation run finishes in well under a second.
- **Six solver entry points.** 0D surface point, 1D depth-resolved, radial profile, single-pulse visualization, electron-lattice inversion analysis, and a scanning-beam surface model.
- **Validated against the MATLAB reference.** Every solver is tested against golden fixtures generated by the MATLAB toolbox. The kernel-based solvers agree to round-off, near 1e-15 relative error, and the stiff solvers agree at integrator tolerance. See [Validation](#validation-against-the-matlab-reference).
- **Captures the surface temperature inversion.** The lattice runs hotter than the electrons after each pulse. Resolving this requires depth resolution, and it is a focus of the companion paper.
- **Config-dict interfaces.** Every solver accepts a plain `cfg` dict with the same field names and defaults as the MATLAB `cfg` structs, and returns a results dict with the same shared v1 field contract, so existing studies translate directly.
- **Pure scientific Python.** NumPy, SciPy, and Numba, with figures in matplotlib. Kernels compile once and are cached on disk.

## Gallery

All figures below come from the solvers in this repository at their baseline example settings. The generating script is [docs/assets/generate_gallery.py](docs/assets/generate_gallery.py).

<p align="center">
  <picture>
    <source media="(prefers-color-scheme: dark)" srcset="docs/assets/fig_single_pulse-dark.png">
    <img src="docs/assets/fig_single_pulse.png" alt="Electron and lattice surface temperature during one femtosecond pulse in tungsten" width="90%">
  </picture>
</p>
<p align="center"><em>Single-pulse electron-lattice dynamics at the tungsten surface, from the 0D solver. The electron bath spikes above 2600 K within the 500 fs pulse, then equilibrates with the lattice through electron-phonon coupling in a few picoseconds.</em></p>

<p align="center">
  <picture>
    <source media="(prefers-color-scheme: dark)" srcset="docs/assets/fig_heat_accumulation-dark.png">
    <img src="docs/assets/fig_heat_accumulation.png" alt="Multi-pulse heat accumulation in tungsten over 600 pulses" width="90%">
  </picture>
</p>
<p align="center"><em>Multi-pulse heat accumulation from the 0D solver, 600 pulses at 5 MHz. The equilibrated and residual surface temperatures climb pulse by pulse as heat arrives faster than it diffuses away.</em></p>

<table align="center">
  <tr>
    <td align="center" width="50%">
      <picture>
        <source media="(prefers-color-scheme: dark)" srcset="docs/assets/fig_scanning_map-dark.png">
        <img src="docs/assets/fig_scanning_map.png" alt="Peak surface temperature map for a scanning femtosecond laser beam" width="100%">
      </picture><br>
      <em>Scanning-beam peak-temperature footprint at 40 W, 18 MHz, and 1 m/s. Accumulation along the scan carries the peak past tungsten's 3422 &deg;C melt point.</em>
    </td>
    <td align="center" width="50%">
      <picture>
        <source media="(prefers-color-scheme: dark)" srcset="docs/assets/fig_radial_profile-dark.png">
        <img src="docs/assets/fig_radial_profile.png" alt="Radial surface temperature profile under a Gaussian femtosecond laser spot" width="100%">
      </picture><br>
      <em>Residual radial temperature profile after 100 pulses under a Gaussian spot.</em>
    </td>
  </tr>
</table>

## Getting Started

**Requirements:** Python 3.10 or newer. NumPy, SciPy, Numba, and matplotlib are installed automatically.

Install the latest release from PyPI:

```bash
pip install laserttm              # add "laserttm[mcp]" for the MCP server
```

Or, for development, clone the repository and install editable:

```bash
git clone https://github.com/dfieser/ultrafast-laser-ttm-py.git
cd ultrafast-laser-ttm-py
pip install -e ".[dev]"           # pytest + ruff + the MCP extra
```

Pin a released version with `pip install laserttm==X.Y.Z` in environments used for real studies, and keep the editable install for development work only.

Then run a baseline example:

```bash
python examples/run_surface_point.py
```

Or call a solver directly with your own parameters:

```python
from laserttm import surface_point_solver

cfg = {
    "material": "W",            # tungsten preset ('Cu', 'Au', 'Al', 'custom')
    "Pavg": 10,                 # average power [W]
    "spotRadius": 100e-6,       # 1/e^2 spot radius [m]
    "f_rep": 5e6,               # repetition rate [Hz]
    "tau_FWHM": 500e-15,        # pulse width, FWHM [s]
}
cfg["simDuration"] = 50 / cfg["f_rep"]   # simulate 50 pulses

results = surface_point_solver(cfg)
```

Every run writes its text output under `outputs/` and returns a `results` dict you can inspect or post-process. The first solver call in a fresh environment takes a little longer while Numba compiles the kernels. They are cached on disk after that.

**Suggested progression:**

1. `examples/run_surface_point.py` for the simplest pulse-accumulation case
2. `examples/run_depth_profile.py` for the main 1D depth-resolved model
3. `examples/run_radial_profile.py` for radial spread under a Gaussian spot
4. `examples/run_scanning_beam.py` for a moving-beam process

The MATLAB repo's [project wiki](https://github.com/dfieser/ultrafast-laser-ttm-toolbox/wiki) covers the model physics and every config field. The fields carry over one-to-one.

## Solvers

| Solver | Module | What it computes | Best use |
| --- | --- | --- | --- |
| Surface point | [surface_point.py](src/laserttm/surface_point.py) | 0D electron and lattice temperatures at the surface, with inter-pulse depth diffusion | fastest pulse-accumulation studies and sweeps |
| Depth profile | [depth_profile.py](src/laserttm/depth_profile.py) | 1D depth-resolved Te(z,t) and Tl(z,t), per-pulse peaks, inversion metrics | main multi-pulse workflow, resolves the surface inversion |
| Radial profile | [radial_profile.py](src/laserttm/radial_profile.py) | radial surface temperature under a Gaussian spot, in 'scale' or 'independent' mode | melt-radius and footprint studies |
| Single pulse | [single_pulse.py](src/laserttm/single_pulse.py) | one pulse with spatial snapshots at chosen delays | early-time inspection and teaching figures |
| Inversion analysis | [inversion_quantifier.py](src/laserttm/inversion_quantifier.py) | per-pulse inversion magnitude, onset, and duration statistics | quantifying the Tl > Te inversion across pulses |
| Scanning beam | [scanning_beam.py](src/laserttm/scanning_beam.py) | 2D surface temperature under a moving beam | translating stationary results to a scanned process |

All six return a results dict with the shared v1 contract fields: `solver`, `solverId`, `contractVersion`, `material`, `outputFile`, `outputDir`, and `inputConfig`, plus `nPulses` and `wallTime_s` where meaningful. The contract is identical to the MATLAB toolbox's.

**Long runs:** the radial solver accepts `storeHistory: False` to drop the per-pulse time histories, which are needed only for the timeline figures. This bounds memory for accumulation studies past 100,000 pulses while leaving every physical result unchanged.

**Progress popup:** the multi-pulse solvers show a waitbar with a live time estimate during interactive runs, matching the MATLAB toolbox. Set `showProgress: True` or `False` in the config to force it either way. When unset, the popup appears only for terminal runs with a display, so test suites, batch pipelines, and the MCP server stay headless. Setting the `LASERTTM_NO_PROGRESS` environment variable also disables it.

## Command line

The package installs a `laserttm` console script for batch and scripted use:

```bash
laserttm list                              # available solver ids
laserttm run study.json --out results.json # run a config (.npz also supported)
laserttm version
```

A config file is the solver's cfg dict plus a `solver` key, with the same field names as the Python and MATLAB interfaces:

```json
{
  "solver": "radial_profile",
  "material": "W",
  "Pavg": 70,
  "f_rep": 40e6,
  "spotRadius": 150e-6,
  "simDuration": 2.5e-5,
  "storeHistory": false
}
```

Plots are off by default in CLI runs. Pass `--plots` or set `makePlots` or `saveFigures` in the config.

## MCP server

Install the `mcp` extra with `pip install laserttm[mcp]`. The `laserttm-mcp` script then serves every solver as [Model Context Protocol](https://modelcontextprotocol.io) tools over stdio, so AI assistants can drive simulations directly. Register it with Claude Code:

```bash
claude mcp add laserttm -- laserttm-mcp
```

Because multi-pulse runs can take minutes, the server uses a job pattern. `start_run` launches a solver in a background worker process and returns a run id, `check_run` polls status with a log tail, and `get_results` returns the results summary once the run finishes. Full arrays land in `~/.laserttm/runs/<run_id>/results.npz`, and the `LASERTTM_RUNS_DIR` environment variable overrides that location. `run_quick` wraps the pattern for short runs, and `cancel_run` terminates a job.

## Validation against the MATLAB reference

`validation/fixtures/` holds golden fixtures produced by running every solver in MATLAB R2026a, via `validation/generate_fixtures.m`, at fixed configurations that include the MATLAB repo's baseline examples. The test suite re-runs each configuration in Python and asserts agreement:

- The solvers built on the hand-rolled, step-identical kernels are surface point, radial profile, and scanning beam. These agree to round-off: relative errors near 1e-15, and at most 1.6e-10 K across a 36,000-pulse scan.
- The solvers built on a stiff integrator are depth profile, single pulse, and inversion quantifier. These run MATLAB `ode15s` against SciPy `BDF`, the same solver family at the same tolerances, and agree at integrator-tolerance level with relative errors near 1e-4 on peak temperatures.

```bash
python -m pytest                 # fast suite (~1 min)
python -m pytest -m slow         # + the 36,000-pulse scanning baseline (~9 min)
python validation/benchmark.py   # wall-time comparison vs MATLAB
```

## Performance

Warm-run wall times on the same machine. The MATLAB times were recorded when the fixtures were generated, with details in `validation/fixtures/manifest.json`:

| Case | MATLAB | Python | Speedup |
| --- | ---: | ---: | ---: |
| surface_point_baseline, 50 pulses | 7.11 s | 0.07 s | 108× |
| single_pulse_baseline | 1.36 s | 0.04 s | 32× |
| radial_profile_baseline, 100 pulses | 1.41 s | 0.10 s | 15× |
| depth_profile_baseline, 100 pulses | 17.60 s | 12.9 s | 1.4× |
| scanning_small, 3,600 pulses | 29.96 s | 22.9 s | 1.3× |
| scanning_baseline, 36,000 pulses | 927.2 s | 543.6 s | 1.7× |
| inversion_baseline, 20 pulses | 4.35 s | 6.1 s | 0.7× |

The depth-solver family currently mirrors the MATLAB integration windows exactly. The source term is identically zero beyond 10·τ from each pulse center, so relaxing the step cap outside that window is a planned optimization for the stiff phase.

## Repository Layout

```text
ultrafast-laser-ttm-py/
  src/laserttm/     solver package: shared Numba kernels and six solver modules
  examples/         editable single-run baseline scripts
  tests/            golden-fixture validation suite, run with pytest
  validation/       MATLAB golden fixtures, generator, and benchmark harness
  docs/assets/      banner and gallery figures with their generating script
  outputs/          default destination for generated results, untracked
```

## Documentation

- [examples/README.md](examples/README.md): example selection and editing pattern
- [validation/README.md](validation/README.md): how the fixture validation works
- [MATLAB reference repository](https://github.com/dfieser/ultrafast-laser-ttm-toolbox) and its [wiki](https://github.com/dfieser/ultrafast-laser-ttm-toolbox/wiki): model background and solver-by-solver reference

## How to Cite

If this toolbox contributes to published work, please cite the article:

```bibtex
@article{fieser2026twostage,
  author    = {Fieser, David and Dewanjee, Unmanaa Nileen and Hu, Anming},
  title     = {A Computationally Efficient Two-Stage Two-Temperature Model for
               Multi-pulse Femtosecond Laser Heat Accumulation in Tungsten},
  journal   = {Journal of Materials Engineering and Performance},
  publisher = {Springer},
  year      = {2026},
  doi       = {10.1007/s11665-026-14738-6},
}
```

To cite the software itself, use the version DOI from the Zenodo record or the metadata in [CITATION.cff](CITATION.cff). The concept DOI [10.5281/zenodo.22210435](https://doi.org/10.5281/zenodo.22210435) always resolves to the latest release.

```bibtex
@software{fieser_ttm_toolbox_py,
  author = {Fieser, David},
  title  = {Ultrafast Laser TTM Toolbox (Python)},
  year   = {2026},
  doi    = {10.5281/zenodo.22210435},
  url    = {https://github.com/dfieser/ultrafast-laser-ttm-py},
}
```

The MATLAB reference implementation has its own record under concept DOI [10.5281/zenodo.20389305](https://doi.org/10.5281/zenodo.20389305).

## Versioning and Releases

The `VERSION` file at the repository root is the single source of truth. The package version is read from it at build time through `[tool.hatch.version]` in `pyproject.toml` and exposed at runtime as `laserttm.__version__`. Every push to `main` is automatically published as a GitHub release. The [release workflow](.github/workflows/release.yml) bumps the patch number in `VERSION`, tags the commit, and publishes. Zenodo then archives the release under a new version DOI, and the same workflow builds the sdist and wheel and publishes them to [PyPI](https://pypi.org/project/laserttm/) through [Trusted Publishing](https://docs.pypi.org/trusted-publishers/), with no API tokens stored anywhere. To jump to a new minor or major version, edit `VERSION` yourself in your push and that exact version is released instead.

## License

Released under the MIT License. See [LICENSE](LICENSE).

## Acknowledgments and Funding

This work was supported by the National Science Foundation under Award No. CMMI-2412544, Collaborative Research: Additive Manufacturing of Crack-Free Tungsten Using Ultrashort Pulsed Lasers. PI: Dr. Anming Hu, Division of Civil, Mechanical, and Manufacturing Innovation, NSF Program: AM-Advanced Manufacturing.

The authors gratefully acknowledge Drs. Yanfei Gao, Wenda Tan, and Seungha Shin for their contributions and collaboration on this project.

Additional support was provided by the University of Tennessee, Knoxville, through a hiring package. D.F. gratefully acknowledges support from the UTK 100 Talented PhD Scholarship.

Support for the Center for Materials Processing from the State of Tennessee and the Tennessee Higher Education Commission is also gratefully acknowledged.

## Contributing

Bug fixes, portability improvements, and documentation polish are welcome. See [CONTRIBUTING.md](CONTRIBUTING.md) for scope guidance.
