laserttm 0.1.5__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. laserttm-0.1.5/.github/workflows/ci.yml +24 -0
  2. laserttm-0.1.5/.github/workflows/release.yml +110 -0
  3. laserttm-0.1.5/.gitignore +21 -0
  4. laserttm-0.1.5/.zenodo.json +40 -0
  5. laserttm-0.1.5/CITATION.cff +46 -0
  6. laserttm-0.1.5/CONTRIBUTING.md +33 -0
  7. laserttm-0.1.5/LICENSE +21 -0
  8. laserttm-0.1.5/PKG-INFO +324 -0
  9. laserttm-0.1.5/README.md +275 -0
  10. laserttm-0.1.5/VERSION +1 -0
  11. laserttm-0.1.5/docs/assets/banner-dark.png +0 -0
  12. laserttm-0.1.5/docs/assets/banner.png +0 -0
  13. laserttm-0.1.5/docs/assets/fig_heat_accumulation-dark.png +0 -0
  14. laserttm-0.1.5/docs/assets/fig_heat_accumulation.png +0 -0
  15. laserttm-0.1.5/docs/assets/fig_radial_profile-dark.png +0 -0
  16. laserttm-0.1.5/docs/assets/fig_radial_profile.png +0 -0
  17. laserttm-0.1.5/docs/assets/fig_scanning_map-dark.png +0 -0
  18. laserttm-0.1.5/docs/assets/fig_scanning_map.png +0 -0
  19. laserttm-0.1.5/docs/assets/fig_single_pulse-dark.png +0 -0
  20. laserttm-0.1.5/docs/assets/fig_single_pulse.png +0 -0
  21. laserttm-0.1.5/docs/assets/generate_gallery.py +337 -0
  22. laserttm-0.1.5/docs/assets/social_preview.png +0 -0
  23. laserttm-0.1.5/examples/README.md +24 -0
  24. laserttm-0.1.5/examples/run_depth_profile.py +30 -0
  25. laserttm-0.1.5/examples/run_inversion_quantifier.py +29 -0
  26. laserttm-0.1.5/examples/run_radial_profile.py +32 -0
  27. laserttm-0.1.5/examples/run_scanning_beam.py +47 -0
  28. laserttm-0.1.5/examples/run_single_pulse.py +28 -0
  29. laserttm-0.1.5/examples/run_surface_point.py +29 -0
  30. laserttm-0.1.5/outputs/.gitkeep +0 -0
  31. laserttm-0.1.5/outputs/README.md +11 -0
  32. laserttm-0.1.5/pyproject.toml +66 -0
  33. laserttm-0.1.5/src/laserttm/__init__.py +40 -0
  34. laserttm-0.1.5/src/laserttm/cli.py +126 -0
  35. laserttm-0.1.5/src/laserttm/config.py +26 -0
  36. laserttm-0.1.5/src/laserttm/depth_profile.py +643 -0
  37. laserttm-0.1.5/src/laserttm/inversion_quantifier.py +298 -0
  38. laserttm-0.1.5/src/laserttm/kernels.py +758 -0
  39. laserttm-0.1.5/src/laserttm/mcp_server.py +233 -0
  40. laserttm-0.1.5/src/laserttm/plotting.py +865 -0
  41. laserttm-0.1.5/src/laserttm/radial_profile.py +592 -0
  42. laserttm-0.1.5/src/laserttm/runtools.py +98 -0
  43. laserttm-0.1.5/src/laserttm/scanning_beam.py +293 -0
  44. laserttm-0.1.5/src/laserttm/single_pulse.py +425 -0
  45. laserttm-0.1.5/src/laserttm/surface_point.py +460 -0
  46. laserttm-0.1.5/src/laserttm/units.py +64 -0
  47. laserttm-0.1.5/tests/__init__.py +0 -0
  48. laserttm-0.1.5/tests/conftest.py +35 -0
  49. laserttm-0.1.5/tests/test_cli.py +66 -0
  50. laserttm-0.1.5/tests/test_depth_profile.py +90 -0
  51. laserttm-0.1.5/tests/test_inversion_quantifier.py +91 -0
  52. laserttm-0.1.5/tests/test_mcp_server.py +67 -0
  53. laserttm-0.1.5/tests/test_radial_profile.py +76 -0
  54. laserttm-0.1.5/tests/test_scanning_beam.py +77 -0
  55. laserttm-0.1.5/tests/test_single_pulse.py +41 -0
  56. laserttm-0.1.5/tests/test_store_history.py +48 -0
  57. laserttm-0.1.5/tests/test_surface_point.py +84 -0
  58. laserttm-0.1.5/validation/README.md +32 -0
  59. laserttm-0.1.5/validation/benchmark.py +99 -0
  60. laserttm-0.1.5/validation/fixtures/depth_profile_baseline.json +1182 -0
  61. laserttm-0.1.5/validation/fixtures/depth_profile_baseline.mat +0 -0
  62. laserttm-0.1.5/validation/fixtures/depth_profile_small.json +192 -0
  63. laserttm-0.1.5/validation/fixtures/depth_profile_small.mat +0 -0
  64. laserttm-0.1.5/validation/fixtures/inversion_baseline.json +576 -0
  65. laserttm-0.1.5/validation/fixtures/inversion_baseline.mat +0 -0
  66. laserttm-0.1.5/validation/fixtures/manifest.json +100 -0
  67. laserttm-0.1.5/validation/fixtures/radial_profile_baseline.json +214 -0
  68. laserttm-0.1.5/validation/fixtures/radial_profile_baseline.mat +0 -0
  69. laserttm-0.1.5/validation/fixtures/radial_profile_independent.json +134 -0
  70. laserttm-0.1.5/validation/fixtures/radial_profile_independent.mat +0 -0
  71. laserttm-0.1.5/validation/fixtures/radial_profile_small.json +134 -0
  72. laserttm-0.1.5/validation/fixtures/radial_profile_small.mat +0 -0
  73. laserttm-0.1.5/validation/fixtures/scanning_baseline.json +50926 -0
  74. laserttm-0.1.5/validation/fixtures/scanning_baseline.mat +0 -0
  75. laserttm-0.1.5/validation/fixtures/scanning_small.json +7516 -0
  76. laserttm-0.1.5/validation/fixtures/scanning_small.mat +0 -0
  77. laserttm-0.1.5/validation/fixtures/single_pulse_baseline.json +43 -0
  78. laserttm-0.1.5/validation/fixtures/single_pulse_baseline.mat +0 -0
  79. laserttm-0.1.5/validation/fixtures/surface_point_au.json +126123 -0
  80. laserttm-0.1.5/validation/fixtures/surface_point_au.mat +0 -0
  81. laserttm-0.1.5/validation/fixtures/surface_point_baseline.json +138558 -0
  82. laserttm-0.1.5/validation/fixtures/surface_point_baseline.mat +0 -0
  83. laserttm-0.1.5/validation/fixtures/surface_point_cu.json +140798 -0
  84. laserttm-0.1.5/validation/fixtures/surface_point_cu.mat +0 -0
  85. laserttm-0.1.5/validation/fixtures/surface_point_square.json +138560 -0
  86. laserttm-0.1.5/validation/fixtures/surface_point_square.mat +0 -0
  87. laserttm-0.1.5/validation/generate_fixtures.m +252 -0
@@ -0,0 +1,24 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+
8
+ jobs:
9
+ test:
10
+ runs-on: ${{ matrix.os }}
11
+ strategy:
12
+ fail-fast: false
13
+ matrix:
14
+ os: [ubuntu-latest, windows-latest]
15
+ python-version: ["3.10", "3.12"]
16
+ steps:
17
+ - uses: actions/checkout@v4
18
+ - uses: actions/setup-python@v5
19
+ with:
20
+ python-version: ${{ matrix.python-version }}
21
+ - name: Install
22
+ run: python -m pip install -e ".[dev]"
23
+ - name: Run tests (validates against MATLAB golden fixtures)
24
+ run: python -m pytest -v
@@ -0,0 +1,110 @@
1
+ name: Release on push
2
+
3
+ # Every push to main is published as a GitHub release, which the
4
+ # GitHub-Zenodo integration archives under a new version DOI.
5
+ #
6
+ # Versioning: the VERSION file at the repository root is the single source
7
+ # of truth. On an ordinary push the workflow bumps its patch number
8
+ # (0.1.4 -> 0.1.5), commits the bump, and releases that version. To jump to
9
+ # a new minor or major version, edit VERSION yourself in your push
10
+ # (e.g. set it to 0.2.0) and the workflow releases exactly that instead.
11
+ #
12
+ # The bump commit is pushed with the workflow's GITHUB_TOKEN, which GitHub
13
+ # never triggers new workflow runs for, so no release loop is possible;
14
+ # the head-commit guard below is a second layer of protection. Tags follow
15
+ # the repository's existing bare X.Y.Z convention (no "v" prefix).
16
+
17
+ on:
18
+ push:
19
+ branches: [main]
20
+
21
+ concurrency:
22
+ group: release
23
+ cancel-in-progress: false
24
+
25
+ jobs:
26
+ release:
27
+ runs-on: ubuntu-latest
28
+ if: "!startsWith(github.event.head_commit.message, 'Release ')"
29
+ permissions:
30
+ contents: write
31
+ outputs:
32
+ version: ${{ steps.v.outputs.version }}
33
+ steps:
34
+ - uses: actions/checkout@v4
35
+ with:
36
+ fetch-depth: 0 # full history + tags for version computation
37
+
38
+ - name: Determine release version
39
+ id: v
40
+ run: |
41
+ set -e
42
+ base=$(tr -d ' \n\r' < VERSION)
43
+ if ! git rev-parse -q --verify "refs/tags/$base" >/dev/null; then
44
+ # VERSION was bumped by hand: release exactly that version
45
+ v="$base"
46
+ else
47
+ latest=$(git tag --list '[0-9]*.[0-9]*.[0-9]*' --sort=-v:refname | head -1)
48
+ IFS=. read -r ma mi pa <<< "$latest"
49
+ v="$ma.$mi.$((pa+1))"
50
+ fi
51
+ echo "version=$v" >> "$GITHUB_OUTPUT"
52
+ echo "Releasing $v (VERSION file had $base, latest tag $(git tag --sort=-v:refname | head -1))"
53
+
54
+ - name: Sync VERSION and commit if needed
55
+ run: |
56
+ v="${{ steps.v.outputs.version }}"
57
+ echo "$v" > VERSION
58
+ if ! git diff --quiet; then
59
+ git config user.name "dfieser"
60
+ git config user.email "dfieser9@gmail.com"
61
+ git commit -am "Release $v"
62
+ git push origin main
63
+ fi
64
+
65
+ - name: Tag and publish release
66
+ env:
67
+ GH_TOKEN: ${{ github.token }}
68
+ run: |
69
+ v="${{ steps.v.outputs.version }}"
70
+ git tag "$v"
71
+ git push origin "$v"
72
+ gh release create "$v" --repo "$GITHUB_REPOSITORY" --title "$v" --generate-notes
73
+
74
+ # Publish the released version to PyPI via Trusted Publishing (OIDC):
75
+ # no API token is stored anywhere. PyPI must be configured once with a
76
+ # trusted publisher for this repository + workflow + the "pypi"
77
+ # environment (https://docs.pypi.org/trusted-publishers/).
78
+ publish-pypi:
79
+ needs: release
80
+ runs-on: ubuntu-latest
81
+ environment:
82
+ name: pypi
83
+ url: https://pypi.org/p/laserttm
84
+ permissions:
85
+ id-token: write # OIDC token for PyPI Trusted Publishing
86
+ contents: write # attach the built distributions to the release
87
+ steps:
88
+ - uses: actions/checkout@v4
89
+ with:
90
+ ref: ${{ needs.release.outputs.version }} # the release tag
91
+
92
+ - uses: actions/setup-python@v5
93
+ with:
94
+ python-version: "3.12"
95
+
96
+ - name: Build sdist and wheel
97
+ run: |
98
+ python -m pip install --upgrade build
99
+ python -m build
100
+ ls -l dist/
101
+
102
+ - name: Publish to PyPI
103
+ uses: pypa/gh-action-pypi-publish@release/v1
104
+
105
+ - name: Attach distributions to the GitHub release
106
+ env:
107
+ GH_TOKEN: ${{ github.token }}
108
+ run: |
109
+ gh release upload "${{ needs.release.outputs.version }}" dist/* \
110
+ --repo "$GITHUB_REPOSITORY" --clobber
@@ -0,0 +1,21 @@
1
+ __pycache__/
2
+ *.pyc
3
+ *.egg-info/
4
+ .venv/
5
+ venv/
6
+ dist/
7
+ build/
8
+ .pytest_cache/
9
+ .ruff_cache/
10
+
11
+ # Generated results (keep the folder itself visible in a clean clone)
12
+ /outputs/*
13
+ !/outputs/.gitkeep
14
+ !/outputs/README.md
15
+
16
+ validation/fixtures/solver_outputs/
17
+ validation/fixtures/matlab_run.log
18
+
19
+ # OS cruft
20
+ .DS_Store
21
+ Thumbs.db
@@ -0,0 +1,40 @@
1
+ {
2
+ "title": "Ultrafast Laser TTM Toolbox (Python)",
3
+ "upload_type": "software",
4
+ "description": "Python solvers (package laserttm) for ultrafast pulsed-laser heating and two-temperature-model studies, including surface-point, depth-profile, radial-profile, single-pulse, inversion-analysis, and scanning-beam workflows. Port of the MATLAB Ultrafast Laser TTM Toolbox, validated solver-by-solver against that reference implementation.",
5
+ "creators": [
6
+ {
7
+ "name": "Fieser, David",
8
+ "affiliation": "Department of Mechanical and Aerospace Engineering, University of Tennessee, Knoxville, 1512 Middle Drive, Knoxville, TN 37996, USA",
9
+ "orcid": "0009-0007-5754-4331"
10
+ }
11
+ ],
12
+ "license": "MIT",
13
+ "access_right": "open",
14
+ "keywords": [
15
+ "Python",
16
+ "two-temperature model",
17
+ "ultrafast laser",
18
+ "pulsed laser",
19
+ "thermal modeling",
20
+ "heat transfer"
21
+ ],
22
+ "related_identifiers": [
23
+ {
24
+ "identifier": "10.1007/s11665-026-14738-6",
25
+ "relation": "isSupplementTo",
26
+ "resource_type": "publication-article"
27
+ },
28
+ {
29
+ "identifier": "10.5281/zenodo.20389305",
30
+ "relation": "isDerivedFrom",
31
+ "resource_type": "software"
32
+ },
33
+ {
34
+ "identifier": "https://github.com/dfieser/ultrafast-laser-ttm-py",
35
+ "relation": "isSupplementTo",
36
+ "resource_type": "software"
37
+ }
38
+ ],
39
+ "notes": "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."
40
+ }
@@ -0,0 +1,46 @@
1
+ cff-version: 1.2.0
2
+ message: "If you use this software, please cite the article it implements (preferred-citation) and, where appropriate, the software record below."
3
+ title: "Ultrafast Laser TTM Toolbox (Python)"
4
+ type: software
5
+ authors:
6
+ - family-names: "Fieser"
7
+ given-names: "David"
8
+ affiliation: "Department of Mechanical and Aerospace Engineering, University of Tennessee, Knoxville, 1512 Middle Drive, Knoxville, TN 37996, USA"
9
+ orcid: "https://orcid.org/0009-0007-5754-4331"
10
+ doi: "10.5281/zenodo.22210435"
11
+ license: "MIT"
12
+ repository-code: "https://github.com/dfieser/ultrafast-laser-ttm-py"
13
+ url: "https://github.com/dfieser/ultrafast-laser-ttm-py"
14
+ abstract: >-
15
+ Python solvers (package laserttm) for ultrafast pulsed-laser heating and
16
+ two-temperature-model studies, including surface-point, depth-profile,
17
+ radial-profile, single-pulse, inversion-analysis, and scanning-beam
18
+ workflows. Port of the MATLAB Ultrafast Laser TTM Toolbox, validated
19
+ solver-by-solver against that reference implementation.
20
+ keywords:
21
+ - "Python"
22
+ - "two-temperature model"
23
+ - "ultrafast laser"
24
+ - "femtosecond laser"
25
+ - "pulsed laser"
26
+ - "thermal modeling"
27
+ - "heat accumulation"
28
+ - "heat transfer"
29
+ - "tungsten"
30
+ preferred-citation:
31
+ type: article
32
+ title: "A Computationally Efficient Two-Stage Two-Temperature Model for Multi-pulse Femtosecond Laser Heat Accumulation in Tungsten"
33
+ authors:
34
+ - family-names: "Fieser"
35
+ given-names: "David"
36
+ orcid: "https://orcid.org/0009-0007-5754-4331"
37
+ - family-names: "Dewanjee"
38
+ given-names: "Unmanaa Nileen"
39
+ - family-names: "Hu"
40
+ given-names: "Anming"
41
+ journal: "Journal of Materials Engineering and Performance"
42
+ year: 2026
43
+ month: 8
44
+ doi: "10.1007/s11665-026-14738-6"
45
+ publisher:
46
+ name: "Springer"
@@ -0,0 +1,33 @@
1
+ # Contributing
2
+
3
+ Thanks for your interest in improving this repository.
4
+
5
+ ## Good contributions
6
+
7
+ - bug fixes tied to a clear issue
8
+ - portability improvements
9
+ - example and documentation clarification
10
+ - small solver-interface improvements that preserve current behavior
11
+ - validation additions that stay generic rather than manuscript-specific
12
+
13
+ ## Before opening a larger change
14
+
15
+ - prefer starting from the canonical examples in `examples/`
16
+ - keep reusable solver logic in `src/laserttm/`
17
+ - keep generated outputs out of version control
18
+ - preserve the shared result-contract fields where practical
19
+ - solver behavior is pinned by the MATLAB golden fixtures in
20
+ `validation/fixtures/`; a change that shifts results outside the stated
21
+ test tolerances needs a strong physical justification
22
+
23
+ ## Suggested workflow
24
+
25
+ 1. make a focused change
26
+ 2. run `python -m pytest` (add `-m slow` for the long scanning baseline)
27
+ and `ruff check src tests examples validation`
28
+ 3. update `README.md` or `docs/` if user-facing behavior changed
29
+ 4. keep pull requests small enough that solver behavior changes are easy to review
30
+
31
+ ## Scope guidance
32
+
33
+ This repository stays centered on reusable pulsed-laser thermal modeling workflows. Changes that mainly serve one historical study, or one manuscript-specific validation campaign, generally belong outside the public-facing copy.
laserttm-0.1.5/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 David Fieser
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,324 @@
1
+ Metadata-Version: 2.5
2
+ Name: laserttm
3
+ Version: 0.1.5
4
+ Summary: Two-temperature model solvers for ultrafast pulsed-laser heating of metals (Python port of the Ultrafast Laser TTM Toolbox)
5
+ Project-URL: Homepage, https://github.com/dfieser/ultrafast-laser-ttm-py
6
+ Project-URL: MATLAB reference implementation, https://github.com/dfieser/ultrafast-laser-ttm-toolbox
7
+ Project-URL: Paper, https://doi.org/10.1007/s11665-026-14738-6
8
+ Author-email: David Fieser <dfieser9@gmail.com>
9
+ License: MIT License
10
+
11
+ Copyright (c) 2026 David Fieser
12
+
13
+ Permission is hereby granted, free of charge, to any person obtaining a copy
14
+ of this software and associated documentation files (the "Software"), to deal
15
+ in the Software without restriction, including without limitation the rights
16
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17
+ copies of the Software, and to permit persons to whom the Software is
18
+ furnished to do so, subject to the following conditions:
19
+
20
+ The above copyright notice and this permission notice shall be included in all
21
+ copies or substantial portions of the Software.
22
+
23
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
29
+ SOFTWARE.
30
+ License-File: LICENSE
31
+ Keywords: femtosecond,heat-accumulation,laser-heating,tungsten,two-temperature-model,ultrafast-laser
32
+ Classifier: Development Status :: 3 - Alpha
33
+ Classifier: Intended Audience :: Science/Research
34
+ Classifier: License :: OSI Approved :: MIT License
35
+ Classifier: Programming Language :: Python :: 3
36
+ Classifier: Topic :: Scientific/Engineering :: Physics
37
+ Requires-Python: >=3.10
38
+ Requires-Dist: matplotlib>=3.7
39
+ Requires-Dist: numba>=0.58
40
+ Requires-Dist: numpy>=1.24
41
+ Requires-Dist: scipy>=1.10
42
+ Provides-Extra: dev
43
+ Requires-Dist: mcp>=1.2; extra == 'dev'
44
+ Requires-Dist: pytest>=7.0; extra == 'dev'
45
+ Requires-Dist: ruff; extra == 'dev'
46
+ Provides-Extra: mcp
47
+ Requires-Dist: mcp>=1.2; extra == 'mcp'
48
+ Description-Content-Type: text/markdown
49
+
50
+ <p align="center">
51
+ <picture>
52
+ <source media="(prefers-color-scheme: dark)" srcset="docs/assets/banner-dark.png">
53
+ <img src="docs/assets/banner.png" alt="Ultrafast Laser TTM Toolbox: two-temperature model solvers for femtosecond laser heating of metals" width="100%">
54
+ </picture>
55
+ </p>
56
+
57
+ <p align="center">
58
+ <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>
59
+ <a href="https://doi.org/10.5281/zenodo.22210435"><img src="https://zenodo.org/badge/1352513618.svg" alt="DOI"></a>
60
+ <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>
61
+ <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>
62
+ <a href="LICENSE"><img src="https://img.shields.io/badge/License-MIT-yellow.svg" alt="MIT License"></a>
63
+ <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">
64
+ </p>
65
+
66
+ Python solvers (package **`laserttm`**) for ultrafast pulsed-laser heating of metals, built on the two-temperature model (TTM). 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 — no MATLAB required.
67
+
68
+ 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:
69
+
70
+ > 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)
71
+
72
+ The defaults reflect the tungsten work in the paper, but material presets (W, Cu, Au, Al) and a `custom` mode support other metals, pulse widths, spot sizes, repetition rates, and scanning conditions.
73
+
74
+ ## Highlights
75
+
76
+ - **Two-stage multi-pulse strategy.** Each pulse period splits into a full electron-lattice TTM solve (stiff BDF, or step-identical adaptive RK4 in the 0D solvers) during the pulse and relaxation, then Crank-Nicolson thermal diffusion for the inter-pulse gap. The baseline 50-pulse accumulation run finishes in well under a second.
77
+ - **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.
78
+ - **Validated against the MATLAB reference.** Every solver is tested against golden fixtures generated by the MATLAB toolbox: round-off-level agreement (~1e-15 relative) for the kernel-based solvers, integrator-tolerance agreement for the stiff ones. See [Validation](#validation-against-the-matlab-reference).
79
+ - **Captures the surface temperature inversion** (lattice hotter than electrons after the pulse), which requires depth resolution and is a focus of the companion paper.
80
+ - **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.
81
+ - **Pure scientific Python.** NumPy + SciPy + Numba (kernels are cached after first compile); figures in matplotlib.
82
+
83
+ ## Gallery
84
+
85
+ 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).
86
+
87
+ <p align="center">
88
+ <picture>
89
+ <source media="(prefers-color-scheme: dark)" srcset="docs/assets/fig_single_pulse-dark.png">
90
+ <img src="docs/assets/fig_single_pulse.png" alt="Electron and lattice surface temperature during one femtosecond pulse in tungsten" width="90%">
91
+ </picture>
92
+ </p>
93
+ <p align="center"><em>Single-pulse electron-lattice dynamics at the tungsten surface (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>
94
+
95
+ <p align="center">
96
+ <picture>
97
+ <source media="(prefers-color-scheme: dark)" srcset="docs/assets/fig_heat_accumulation-dark.png">
98
+ <img src="docs/assets/fig_heat_accumulation.png" alt="Multi-pulse heat accumulation in tungsten over 600 pulses" width="90%">
99
+ </picture>
100
+ </p>
101
+ <p align="center"><em>Multi-pulse heat accumulation (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>
102
+
103
+ <table align="center">
104
+ <tr>
105
+ <td align="center" width="50%">
106
+ <picture>
107
+ <source media="(prefers-color-scheme: dark)" srcset="docs/assets/fig_scanning_map-dark.png">
108
+ <img src="docs/assets/fig_scanning_map.png" alt="Peak surface temperature map for a scanning femtosecond laser beam" width="100%">
109
+ </picture><br>
110
+ <em>Scanning-beam peak-temperature footprint (40 W, 18 MHz, 1 m/s): accumulation along the scan carries the peak past tungsten's 3422 &deg;C melt point.</em>
111
+ </td>
112
+ <td align="center" width="50%">
113
+ <picture>
114
+ <source media="(prefers-color-scheme: dark)" srcset="docs/assets/fig_radial_profile-dark.png">
115
+ <img src="docs/assets/fig_radial_profile.png" alt="Radial surface temperature profile under a Gaussian femtosecond laser spot" width="100%">
116
+ </picture><br>
117
+ <em>Residual radial temperature profile after 100 pulses under a Gaussian spot.</em>
118
+ </td>
119
+ </tr>
120
+ </table>
121
+
122
+ ## Getting Started
123
+
124
+ **Requirements:** Python ≥ 3.10. NumPy, SciPy, Numba, and matplotlib are installed automatically.
125
+
126
+ Install the latest release from PyPI:
127
+
128
+ ```bash
129
+ pip install laserttm # add "laserttm[mcp]" for the MCP server
130
+ ```
131
+
132
+ Or, for development, clone the repository and install editable:
133
+
134
+ ```bash
135
+ git clone https://github.com/dfieser/ultrafast-laser-ttm-py.git
136
+ cd ultrafast-laser-ttm-py
137
+ pip install -e ".[dev]" # pytest + ruff + the MCP extra
138
+ ```
139
+
140
+ Pin a released version (`pip install laserttm==X.Y.Z`) in environments used for real studies, and keep the editable install for development work only.
141
+
142
+ 2. Run a baseline example:
143
+
144
+ ```bash
145
+ python examples/run_surface_point.py
146
+ ```
147
+
148
+ Or call a solver directly with your own parameters:
149
+
150
+ ```python
151
+ from laserttm import surface_point_solver
152
+
153
+ cfg = {
154
+ "material": "W", # tungsten preset ('Cu', 'Au', 'Al', 'custom')
155
+ "Pavg": 10, # average power [W]
156
+ "spotRadius": 100e-6, # 1/e^2 spot radius [m]
157
+ "f_rep": 5e6, # repetition rate [Hz]
158
+ "tau_FWHM": 500e-15, # pulse width, FWHM [s]
159
+ }
160
+ cfg["simDuration"] = 50 / cfg["f_rep"] # simulate 50 pulses
161
+
162
+ results = surface_point_solver(cfg)
163
+ ```
164
+
165
+ 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.
166
+
167
+ **Suggested progression:**
168
+
169
+ 1. `examples/run_surface_point.py` for the simplest pulse-accumulation case
170
+ 2. `examples/run_depth_profile.py` for the main 1D depth-resolved model
171
+ 3. `examples/run_radial_profile.py` for radial spread under a Gaussian spot
172
+ 4. `examples/run_scanning_beam.py` for a moving-beam process
173
+
174
+ 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.
175
+
176
+ ## Solvers
177
+
178
+ | Solver | Module | What it computes | Best use |
179
+ | --- | --- | --- | --- |
180
+ | 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 |
181
+ | 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 |
182
+ | Radial profile | [radial_profile.py](src/laserttm/radial_profile.py) | radial surface temperature under a Gaussian spot ('scale' and 'independent' modes) | melt-radius and footprint studies |
183
+ | Single pulse | [single_pulse.py](src/laserttm/single_pulse.py) | one pulse with spatial snapshots at chosen delays | early-time inspection and teaching figures |
184
+ | 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 |
185
+ | Scanning beam | [scanning_beam.py](src/laserttm/scanning_beam.py) | 2D surface temperature under a moving beam | translating stationary results to a scanned process |
186
+
187
+ 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 — identical to the MATLAB toolbox's contract.
188
+
189
+ **Long runs:** the radial solver accepts `storeHistory: False` to drop the per-pulse time histories (needed only for the timeline figures), which bounds memory for 10⁵-pulse-scale accumulation studies while leaving every physical result unchanged.
190
+
191
+ ## Command line
192
+
193
+ The package installs a `laserttm` console script for batch and scripted use:
194
+
195
+ ```bash
196
+ laserttm list # available solver ids
197
+ laserttm run study.json --out results.json # run a config; also .npz output
198
+ laserttm version
199
+ ```
200
+
201
+ A config file is the solver's cfg dict plus a `solver` key, with the same field names as the Python and MATLAB interfaces:
202
+
203
+ ```json
204
+ {
205
+ "solver": "radial_profile",
206
+ "material": "W",
207
+ "Pavg": 70,
208
+ "f_rep": 40e6,
209
+ "spotRadius": 150e-6,
210
+ "simDuration": 2.5e-5,
211
+ "storeHistory": false
212
+ }
213
+ ```
214
+
215
+ Plots are off by default in CLI runs; pass `--plots` or set `makePlots`/`saveFigures` in the config.
216
+
217
+ ## MCP server
218
+
219
+ With the `mcp` extra installed (`pip install laserttm[mcp]`), the `laserttm-mcp` script 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:
220
+
221
+ ```bash
222
+ claude mcp add laserttm -- laserttm-mcp
223
+ ```
224
+
225
+ 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 finished (full arrays land in `~/.laserttm/runs/<run_id>/results.npz`; override the root with `LASERTTM_RUNS_DIR`). `run_quick` wraps the pattern for short runs, and `cancel_run` terminates a job.
226
+
227
+ ## Validation against the MATLAB reference
228
+
229
+ `validation/fixtures/` holds golden fixtures produced by running every solver in MATLAB R2026a (`validation/generate_fixtures.m`) at fixed configurations, including the MATLAB repo's baseline examples. The test suite re-runs each configuration in Python and asserts agreement:
230
+
231
+ - Solvers built on the hand-rolled, step-identical kernels (surface point, radial profile, scanning beam) agree to round-off: relative errors of ~1e-15, and at most 1.6e-10 K across a 36,000-pulse scan.
232
+ - Solvers built on a stiff integrator (depth profile, single pulse, inversion quantifier) run MATLAB `ode15s` vs SciPy `BDF` — the same solver family at the same tolerances — and agree at integrator-tolerance level (relative errors ~1e-4 on peak temperatures).
233
+
234
+ ```bash
235
+ python -m pytest # fast suite (~1 min)
236
+ python -m pytest -m slow # + the 36,000-pulse scanning baseline (~9 min)
237
+ python validation/benchmark.py # wall-time comparison vs MATLAB
238
+ ```
239
+
240
+ ## Performance
241
+
242
+ Warm-run wall times on the same machine (MATLAB times recorded when the fixtures were generated; details in `validation/fixtures/manifest.json`):
243
+
244
+ | Case | MATLAB | Python | Speedup |
245
+ | --- | ---: | ---: | ---: |
246
+ | surface_point_baseline (50 pulses) | 7.11 s | 0.07 s | 108× |
247
+ | single_pulse_baseline | 1.36 s | 0.04 s | 32× |
248
+ | radial_profile_baseline (100 pulses) | 1.41 s | 0.10 s | 15× |
249
+ | depth_profile_baseline (100 pulses) | 17.60 s | 12.9 s | 1.4× |
250
+ | scanning_small (3,600 pulses) | 29.96 s | 22.9 s | 1.3× |
251
+ | scanning_baseline (36,000 pulses) | 927.2 s | 543.6 s | 1.7× |
252
+ | inversion_baseline (20 pulses) | 4.35 s | 6.1 s | 0.7× |
253
+
254
+ The depth-solver family currently mirrors the MATLAB integration windows exactly; relaxing the step cap outside the source window (the source is identically zero beyond 10·τ from each pulse center) is a planned optimization for the stiff phase.
255
+
256
+ ## Repository Layout
257
+
258
+ ```text
259
+ ultrafast-laser-ttm-py/
260
+ src/laserttm/ solver package (shared Numba kernels + six solver modules)
261
+ examples/ editable single-run baseline scripts
262
+ tests/ golden-fixture validation suite (pytest)
263
+ validation/ MATLAB golden fixtures, generator, and benchmark harness
264
+ docs/assets/ banner and gallery figures + generating script
265
+ outputs/ default destination for generated results (untracked)
266
+ ```
267
+
268
+ ## Documentation
269
+
270
+ - [examples/README.md](examples/README.md): example selection and editing pattern
271
+ - [validation/README.md](validation/README.md): how the fixture validation works
272
+ - [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
273
+
274
+ ## How to Cite
275
+
276
+ If this toolbox contributes to published work, please cite the article:
277
+
278
+ ```bibtex
279
+ @article{fieser2026twostage,
280
+ author = {Fieser, David and Dewanjee, Unmanaa Nileen and Hu, Anming},
281
+ title = {A Computationally Efficient Two-Stage Two-Temperature Model for
282
+ Multi-pulse Femtosecond Laser Heat Accumulation in Tungsten},
283
+ journal = {Journal of Materials Engineering and Performance},
284
+ publisher = {Springer},
285
+ year = {2026},
286
+ doi = {10.1007/s11665-026-14738-6},
287
+ }
288
+ ```
289
+
290
+ To cite the software itself, use the version DOI from the Zenodo record (concept DOI [10.5281/zenodo.22210435](https://doi.org/10.5281/zenodo.22210435) always resolves to the latest release) or the metadata in [CITATION.cff](CITATION.cff):
291
+
292
+ ```bibtex
293
+ @software{fieser_ttm_toolbox_py,
294
+ author = {Fieser, David},
295
+ title = {Ultrafast Laser TTM Toolbox (Python)},
296
+ year = {2026},
297
+ doi = {10.5281/zenodo.22210435},
298
+ url = {https://github.com/dfieser/ultrafast-laser-ttm-py},
299
+ }
300
+ ```
301
+
302
+ The MATLAB reference implementation has its own record (concept DOI [10.5281/zenodo.20389305](https://doi.org/10.5281/zenodo.20389305)).
303
+
304
+ ## Versioning and Releases
305
+
306
+ The `VERSION` file at the repository root is the single source of truth: the package version is read from it at build time (`[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/p/laserttm) via [Trusted Publishing](https://docs.pypi.org/trusted-publishers/) (OIDC — no API tokens stored). To jump to a new minor or major version, edit `VERSION` yourself in your push and that exact version is released instead.
307
+
308
+ ## License
309
+
310
+ Released under the MIT License. See [LICENSE](LICENSE).
311
+
312
+ ## Acknowledgments and Funding
313
+
314
+ 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).
315
+
316
+ The authors gratefully acknowledge Drs. Yanfei Gao, Wenda Tan, and Seungha Shin for their contributions and collaboration on this project.
317
+
318
+ 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.
319
+
320
+ Support for the Center for Materials Processing from the State of Tennessee and the Tennessee Higher Education Commission is also gratefully acknowledged.
321
+
322
+ ## Contributing
323
+
324
+ Bug fixes, portability improvements, and documentation polish are welcome. See [CONTRIBUTING.md](CONTRIBUTING.md) for scope guidance.