atmorad-py 0.1.1__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.
- atmorad_py-0.1.1/.github/workflows/ci.yml +47 -0
- atmorad_py-0.1.1/.gitignore +12 -0
- atmorad_py-0.1.1/.python-version +1 -0
- atmorad_py-0.1.1/LICENSE +21 -0
- atmorad_py-0.1.1/PKG-INFO +165 -0
- atmorad_py-0.1.1/README.md +146 -0
- atmorad_py-0.1.1/TODO.md +34 -0
- atmorad_py-0.1.1/example.py +54 -0
- atmorad_py-0.1.1/examples/adjacency_effect.toml +36 -0
- atmorad_py-0.1.1/pyproject.toml +51 -0
- atmorad_py-0.1.1/simulation.toml +98 -0
- atmorad_py-0.1.1/src/atmorad/__init__.py +18 -0
- atmorad_py-0.1.1/src/atmorad/builder.py +108 -0
- atmorad_py-0.1.1/src/atmorad/cli.py +121 -0
- atmorad_py-0.1.1/src/atmorad/config/__init__.py +19 -0
- atmorad_py-0.1.1/src/atmorad/config/defaults.toml +94 -0
- atmorad_py-0.1.1/src/atmorad/config/models.py +59 -0
- atmorad_py-0.1.1/src/atmorad/config/parser.py +55 -0
- atmorad_py-0.1.1/src/atmorad/config/simulation.toml +98 -0
- atmorad_py-0.1.1/src/atmorad/constants.py +13 -0
- atmorad_py-0.1.1/src/atmorad/detectors/__init__.py +5 -0
- atmorad_py-0.1.1/src/atmorad/detectors/base.py +24 -0
- atmorad_py-0.1.1/src/atmorad/detectors/boundary_flux.py +80 -0
- atmorad_py-0.1.1/src/atmorad/detectors/builder.py +31 -0
- atmorad_py-0.1.1/src/atmorad/detectors/fate.py +42 -0
- atmorad_py-0.1.1/src/atmorad/detectors/flux.py +59 -0
- atmorad_py-0.1.1/src/atmorad/detectors/heating.py +42 -0
- atmorad_py-0.1.1/src/atmorad/detectors/paths.py +59 -0
- atmorad_py-0.1.1/src/atmorad/detectors/plane_flux.py +120 -0
- atmorad_py-0.1.1/src/atmorad/detectors/results.py +37 -0
- atmorad_py-0.1.1/src/atmorad/engine/__init__.py +7 -0
- atmorad_py-0.1.1/src/atmorad/engine/core.py +130 -0
- atmorad_py-0.1.1/src/atmorad/engine/runner.py +91 -0
- atmorad_py-0.1.1/src/atmorad/environment/__init__.py +27 -0
- atmorad_py-0.1.1/src/atmorad/environment/atmosphere.py +186 -0
- atmorad_py-0.1.1/src/atmorad/environment/scene.py +89 -0
- atmorad_py-0.1.1/src/atmorad/environment/surface.py +175 -0
- atmorad_py-0.1.1/src/atmorad/models/__init__.py +7 -0
- atmorad_py-0.1.1/src/atmorad/models/batch.py +31 -0
- atmorad_py-0.1.1/src/atmorad/models/context.py +10 -0
- atmorad_py-0.1.1/src/atmorad/output/__init__.py +4 -0
- atmorad_py-0.1.1/src/atmorad/output/analyzer.py +201 -0
- atmorad_py-0.1.1/src/atmorad/output/data_io.py +123 -0
- atmorad_py-0.1.1/src/atmorad/physics/__init__.py +14 -0
- atmorad_py-0.1.1/src/atmorad/physics/geometry.py +53 -0
- atmorad_py-0.1.1/src/atmorad/physics/reflection.py +64 -0
- atmorad_py-0.1.1/src/atmorad/physics/registry.py +18 -0
- atmorad_py-0.1.1/src/atmorad/physics/scattering.py +73 -0
- atmorad_py-0.1.1/tests/__init__.py +0 -0
- atmorad_py-0.1.1/tests/configs/demo_config.toml +79 -0
- atmorad_py-0.1.1/tests/configs/test_config.toml +48 -0
- atmorad_py-0.1.1/tests/conftest.py +12 -0
- atmorad_py-0.1.1/tests/test_basic.py +44 -0
- atmorad_py-0.1.1/uv.lock +1106 -0
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [ "main" ]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [ "main" ]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
test-and-lint:
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
strategy:
|
|
13
|
+
matrix:
|
|
14
|
+
resolution: ["locked", "lowest-direct"]
|
|
15
|
+
|
|
16
|
+
steps:
|
|
17
|
+
- name: Checkout Repository
|
|
18
|
+
uses: actions/checkout@v6
|
|
19
|
+
|
|
20
|
+
- name: Install uv
|
|
21
|
+
uses: astral-sh/setup-uv@v8.1.0
|
|
22
|
+
with:
|
|
23
|
+
enable-cache: true
|
|
24
|
+
cache-dependency-glob: "uv.lock"
|
|
25
|
+
|
|
26
|
+
- name: Set up Python
|
|
27
|
+
uses: actions/setup-python@v6
|
|
28
|
+
with:
|
|
29
|
+
python-version-file: "pyproject.toml"
|
|
30
|
+
|
|
31
|
+
- name: Install Dependencies
|
|
32
|
+
run: |
|
|
33
|
+
if [ "${{ matrix.resolution }}" = "lowest-direct" ]; then
|
|
34
|
+
uv venv
|
|
35
|
+
uv pip install -e ".[dev]" --resolution lowest-direct
|
|
36
|
+
else
|
|
37
|
+
uv sync --all-extras --dev
|
|
38
|
+
fi
|
|
39
|
+
|
|
40
|
+
- name: Ruff
|
|
41
|
+
if: matrix.resolution == 'locked'
|
|
42
|
+
run: |
|
|
43
|
+
uv run ruff check .
|
|
44
|
+
uv run ruff format --check .
|
|
45
|
+
|
|
46
|
+
- name: Pytests
|
|
47
|
+
run: uv run pytest tests/
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.12
|
atmorad_py-0.1.1/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Karol Dąbrowski
|
|
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,165 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: atmorad-py
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: A 3D monte carlo simulation of Atmospheric Radiative Transfer
|
|
5
|
+
Author-email: Karol Dąbrowski <atmorad@kdabr.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
License-File: LICENSE
|
|
8
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
9
|
+
Classifier: Operating System :: OS Independent
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Topic :: Scientific/Engineering :: Physics
|
|
12
|
+
Requires-Python: >=3.10
|
|
13
|
+
Requires-Dist: cmocean>=2.0.0
|
|
14
|
+
Requires-Dist: matplotlib>=3.5.0
|
|
15
|
+
Requires-Dist: numpy>=1.26.0
|
|
16
|
+
Requires-Dist: seaborn>=0.11.0
|
|
17
|
+
Requires-Dist: tqdm>=4.60.0
|
|
18
|
+
Description-Content-Type: text/markdown
|
|
19
|
+
|
|
20
|
+
# AtmoRad
|
|
21
|
+
## A vectorized Monte Carlo simulation of atmospheric radiative transfer.
|
|
22
|
+
|
|
23
|
+
[](https://www.python.org/downloads/)
|
|
24
|
+
[](https://opensource.org/licenses/MIT)
|
|
25
|
+
|
|
26
|
+
| **2D Surface absorption map** | **Sample photon paths** |
|
|
27
|
+
| :--- | :--- |
|
|
28
|
+
|  |  |
|
|
29
|
+
| **Vertical flux profile** | **Vertical absorption profile** |
|
|
30
|
+
| |  |
|
|
31
|
+
|
|
32
|
+
## Overview:
|
|
33
|
+
|
|
34
|
+
This project simulates the propagation of light through a plane-parallel atmosphere over a horizontally mixed surface and its interactions with the ground boundary. Developed as a student project, created to learn computational physics and software development.
|
|
35
|
+
|
|
36
|
+
### Physical model
|
|
37
|
+
- **Analog Monte Carlo Approach**: Light is simulated using discrete photon packets. Final flux is calculated as a fraction of the total detected packets.
|
|
38
|
+
- **Plane-parallel approximation**: The atmosphere consists of horizontally uniform layers.
|
|
39
|
+
- **Multi-material atmospheric layers**: Layers can consist of multiple atmospheric materials simultaneously. A photon is assigned a material randomly when it is initialized and again when it crosses into a new layer. Each material has its own extinction coefficient, SSA and phase function.
|
|
40
|
+
- **Custom Phase Functions**: Henyey-Greenstein and Rayleigh phase functions are already implemented in the simulation, but any custom user-defined function can be constructed using the `Scattering` class.
|
|
41
|
+
- **Surface Reflections**: The surface consists of materials, each with its own albedo, a predefined BRDF reflection model (`Lambertian`, `Mirror`), and a `ProceduralMap` that outputs material ID based on spatial coordinates.
|
|
42
|
+
- **Photon Properties**: Light is treated as monochromatic, non-polarized particles. During the simulation they can be scattered, reflected, or absorbed.
|
|
43
|
+
- **Incident Irradiance & Adjacency Effect**: Custom detectors allow measuring downward/upward incident flux at any arbitrary altitude - helpful for visualizing the adjacency effect.
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
## Technical implementation:
|
|
47
|
+
- The simulation uses `numpy` to simulate photons simultaneously in large batches.
|
|
48
|
+
- The results are plotted using `matplotlib` and `seaborn` (e.g., photon paths, flux profile, 2D ground flux maps)
|
|
49
|
+
- The code uses multiprocessing to run batches in parallel.
|
|
50
|
+
|
|
51
|
+
## Installation:
|
|
52
|
+
- Using `uv` ([install uv](https://docs.astral.sh/uv/getting-started/installation/)):
|
|
53
|
+
```bash
|
|
54
|
+
uv tool install atmorad-py
|
|
55
|
+
```
|
|
56
|
+
- Using `pip`:
|
|
57
|
+
```bash
|
|
58
|
+
pip install atmorad-py
|
|
59
|
+
```
|
|
60
|
+
- Run the simulation:
|
|
61
|
+
```bash
|
|
62
|
+
atmorad --init
|
|
63
|
+
atmorad simulation.toml
|
|
64
|
+
```
|
|
65
|
+
- Check `results/` directory for simulation artifacts.
|
|
66
|
+
|
|
67
|
+
## Project Structure
|
|
68
|
+
- `engine/`: Divides photons into batches and runs the simulation.
|
|
69
|
+
- `physics/`: Contains a rotation function, scattering phase functions, reflection functions.
|
|
70
|
+
- `environment/`: Keeps track of the environment. Contains `Scene`, `Atmosphere` and `Surface` classes.
|
|
71
|
+
- `detectors/`: Provides functionality for tracking photons during the simulation and generates results.
|
|
72
|
+
- `output/`: Handles results and figure generation.
|
|
73
|
+
- `config/` and `builder.py`: Parses `.toml` configuration file and generates simulation context.
|
|
74
|
+
- `cli.py`: Provides CLI for `atmorad`.
|
|
75
|
+
|
|
76
|
+
## Customization
|
|
77
|
+
You can define your own surface reflection algorithms and scattering phase functions using decorators as shown below:
|
|
78
|
+
|
|
79
|
+
```python
|
|
80
|
+
import numpy as np
|
|
81
|
+
from atmorad import build_context, MCRadiationRunner, DataIO, ResultAnalyzer
|
|
82
|
+
from atmorad import SurfaceReflection, register_reflection, orientation
|
|
83
|
+
from atmorad import Scattering, register_scattering
|
|
84
|
+
|
|
85
|
+
# 1. Register a custom surface reflection
|
|
86
|
+
@register_reflection("custom-reflection")
|
|
87
|
+
class CustomReflection(SurfaceReflection):
|
|
88
|
+
# Specify arbitrary custom parameters
|
|
89
|
+
def __init__(self, param_1, param_2):
|
|
90
|
+
self.param_1 = param_1
|
|
91
|
+
self.param_2 = param_2
|
|
92
|
+
def reflect(self, direction, rand_1, rand_2):
|
|
93
|
+
# Your custom reflection physics here
|
|
94
|
+
cos_theta = np.sqrt(rand_1)
|
|
95
|
+
sin_theta = np.sqrt(1.0 - rand_1)
|
|
96
|
+
|
|
97
|
+
# you can also use specified parameters
|
|
98
|
+
# self.param_1, self.param_2
|
|
99
|
+
|
|
100
|
+
phi = rand_2 * 2 * np.pi
|
|
101
|
+
cos_phi, sin_phi = np.cos(phi), np.sin(phi)
|
|
102
|
+
|
|
103
|
+
return orientation(cos_theta, sin_theta, cos_phi, sin_phi)
|
|
104
|
+
|
|
105
|
+
# 2. Register a custom scattering phase function
|
|
106
|
+
@register_scattering("custom-scattering")
|
|
107
|
+
class CustomScattering(Scattering):
|
|
108
|
+
# Specify arbitrary custom parameters
|
|
109
|
+
def __init__(self, g, resolution=1000):
|
|
110
|
+
# Define a pdf array
|
|
111
|
+
self.g = g
|
|
112
|
+
cos_grid = np.linspace(-1, 1, resolution)
|
|
113
|
+
|
|
114
|
+
pdf = (1 - g**2) / (2 * (1 + g**2 - 2 * g * cos_grid) ** 1.5)
|
|
115
|
+
|
|
116
|
+
super().__init__(pdf_array=pdf, resolution=resolution)
|
|
117
|
+
|
|
118
|
+
# 3. Run the simulation using custom names in your config
|
|
119
|
+
if __name__ == "__main__":
|
|
120
|
+
context = build_context("simulation.toml")
|
|
121
|
+
runner = MCRadiationRunner(context)
|
|
122
|
+
runner.run()
|
|
123
|
+
|
|
124
|
+
# 4. Save and analyze results
|
|
125
|
+
results = runner.get_results()
|
|
126
|
+
outputs = DataIO(context.config)
|
|
127
|
+
analyzer = ResultAnalyzer(results, context.config)
|
|
128
|
+
|
|
129
|
+
outputs.save_all_artifacts(analyzer, results)
|
|
130
|
+
outputs.save_metadata(context.config, results)
|
|
131
|
+
outputs.save_results(results)
|
|
132
|
+
```
|
|
133
|
+
In `simulation.toml` you can specify your defined scatterings and reflections:
|
|
134
|
+
```toml
|
|
135
|
+
[atmosphere_materials.custom-atm-material]
|
|
136
|
+
ssa = 0.9
|
|
137
|
+
scattering = {type = "custom-scattering", g=0.8}
|
|
138
|
+
|
|
139
|
+
[surface_materials.custom-surf-material]
|
|
140
|
+
albedo = 0.5
|
|
141
|
+
reflection = {type = "custom-reflection", param_1=2, param_2=1.3} # match param names defined in python
|
|
142
|
+
```
|
|
143
|
+
Then you can use your defined materials for atmospheric layers and surface maps:
|
|
144
|
+
```toml
|
|
145
|
+
[[layer]]
|
|
146
|
+
z_range_km = [0, 2]
|
|
147
|
+
materials = [{type = "custom-atm-material", weight = 1.0}]
|
|
148
|
+
|
|
149
|
+
# ...
|
|
150
|
+
|
|
151
|
+
[surface]
|
|
152
|
+
name = "uniform"
|
|
153
|
+
material = "custom-surf-material"
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
## References and Literature
|
|
157
|
+
- (in Polish) Script for Lecture about [Radiative Processes in the Atmosphere](https://www.igf.fuw.edu.pl/~kmark/stacja/wyklady/ProcesyRadiacyjne/2013/WykladRadiacjaKlimat.pdf), Prof. K. Markowicz, Faculty of Physics, University of Warsaw, 2013.
|
|
158
|
+
|
|
159
|
+
## Acknowledgments
|
|
160
|
+
- This project was inspired by the lectures on *Radiative Processes in the Atmosphere* by Prof. K. Markowicz, Faculty of Physics, University of Warsaw.
|
|
161
|
+
- Large Language Models were used for code debugging and learning best Python practices (e.g. `dataclasses`, `__init__.py` import interfaces, class responsibilities, config parsing).
|
|
162
|
+
|
|
163
|
+
## Contributing
|
|
164
|
+
Feel free to open an [Issue](https://github.com/dabrokarol/atmorad-py/issues) or submit a Pull Request if you'd like to contribute or report a bug.
|
|
165
|
+
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
# AtmoRad
|
|
2
|
+
## A vectorized Monte Carlo simulation of atmospheric radiative transfer.
|
|
3
|
+
|
|
4
|
+
[](https://www.python.org/downloads/)
|
|
5
|
+
[](https://opensource.org/licenses/MIT)
|
|
6
|
+
|
|
7
|
+
| **2D Surface absorption map** | **Sample photon paths** |
|
|
8
|
+
| :--- | :--- |
|
|
9
|
+
|  |  |
|
|
10
|
+
| **Vertical flux profile** | **Vertical absorption profile** |
|
|
11
|
+
| |  |
|
|
12
|
+
|
|
13
|
+
## Overview:
|
|
14
|
+
|
|
15
|
+
This project simulates the propagation of light through a plane-parallel atmosphere over a horizontally mixed surface and its interactions with the ground boundary. Developed as a student project, created to learn computational physics and software development.
|
|
16
|
+
|
|
17
|
+
### Physical model
|
|
18
|
+
- **Analog Monte Carlo Approach**: Light is simulated using discrete photon packets. Final flux is calculated as a fraction of the total detected packets.
|
|
19
|
+
- **Plane-parallel approximation**: The atmosphere consists of horizontally uniform layers.
|
|
20
|
+
- **Multi-material atmospheric layers**: Layers can consist of multiple atmospheric materials simultaneously. A photon is assigned a material randomly when it is initialized and again when it crosses into a new layer. Each material has its own extinction coefficient, SSA and phase function.
|
|
21
|
+
- **Custom Phase Functions**: Henyey-Greenstein and Rayleigh phase functions are already implemented in the simulation, but any custom user-defined function can be constructed using the `Scattering` class.
|
|
22
|
+
- **Surface Reflections**: The surface consists of materials, each with its own albedo, a predefined BRDF reflection model (`Lambertian`, `Mirror`), and a `ProceduralMap` that outputs material ID based on spatial coordinates.
|
|
23
|
+
- **Photon Properties**: Light is treated as monochromatic, non-polarized particles. During the simulation they can be scattered, reflected, or absorbed.
|
|
24
|
+
- **Incident Irradiance & Adjacency Effect**: Custom detectors allow measuring downward/upward incident flux at any arbitrary altitude - helpful for visualizing the adjacency effect.
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
## Technical implementation:
|
|
28
|
+
- The simulation uses `numpy` to simulate photons simultaneously in large batches.
|
|
29
|
+
- The results are plotted using `matplotlib` and `seaborn` (e.g., photon paths, flux profile, 2D ground flux maps)
|
|
30
|
+
- The code uses multiprocessing to run batches in parallel.
|
|
31
|
+
|
|
32
|
+
## Installation:
|
|
33
|
+
- Using `uv` ([install uv](https://docs.astral.sh/uv/getting-started/installation/)):
|
|
34
|
+
```bash
|
|
35
|
+
uv tool install atmorad-py
|
|
36
|
+
```
|
|
37
|
+
- Using `pip`:
|
|
38
|
+
```bash
|
|
39
|
+
pip install atmorad-py
|
|
40
|
+
```
|
|
41
|
+
- Run the simulation:
|
|
42
|
+
```bash
|
|
43
|
+
atmorad --init
|
|
44
|
+
atmorad simulation.toml
|
|
45
|
+
```
|
|
46
|
+
- Check `results/` directory for simulation artifacts.
|
|
47
|
+
|
|
48
|
+
## Project Structure
|
|
49
|
+
- `engine/`: Divides photons into batches and runs the simulation.
|
|
50
|
+
- `physics/`: Contains a rotation function, scattering phase functions, reflection functions.
|
|
51
|
+
- `environment/`: Keeps track of the environment. Contains `Scene`, `Atmosphere` and `Surface` classes.
|
|
52
|
+
- `detectors/`: Provides functionality for tracking photons during the simulation and generates results.
|
|
53
|
+
- `output/`: Handles results and figure generation.
|
|
54
|
+
- `config/` and `builder.py`: Parses `.toml` configuration file and generates simulation context.
|
|
55
|
+
- `cli.py`: Provides CLI for `atmorad`.
|
|
56
|
+
|
|
57
|
+
## Customization
|
|
58
|
+
You can define your own surface reflection algorithms and scattering phase functions using decorators as shown below:
|
|
59
|
+
|
|
60
|
+
```python
|
|
61
|
+
import numpy as np
|
|
62
|
+
from atmorad import build_context, MCRadiationRunner, DataIO, ResultAnalyzer
|
|
63
|
+
from atmorad import SurfaceReflection, register_reflection, orientation
|
|
64
|
+
from atmorad import Scattering, register_scattering
|
|
65
|
+
|
|
66
|
+
# 1. Register a custom surface reflection
|
|
67
|
+
@register_reflection("custom-reflection")
|
|
68
|
+
class CustomReflection(SurfaceReflection):
|
|
69
|
+
# Specify arbitrary custom parameters
|
|
70
|
+
def __init__(self, param_1, param_2):
|
|
71
|
+
self.param_1 = param_1
|
|
72
|
+
self.param_2 = param_2
|
|
73
|
+
def reflect(self, direction, rand_1, rand_2):
|
|
74
|
+
# Your custom reflection physics here
|
|
75
|
+
cos_theta = np.sqrt(rand_1)
|
|
76
|
+
sin_theta = np.sqrt(1.0 - rand_1)
|
|
77
|
+
|
|
78
|
+
# you can also use specified parameters
|
|
79
|
+
# self.param_1, self.param_2
|
|
80
|
+
|
|
81
|
+
phi = rand_2 * 2 * np.pi
|
|
82
|
+
cos_phi, sin_phi = np.cos(phi), np.sin(phi)
|
|
83
|
+
|
|
84
|
+
return orientation(cos_theta, sin_theta, cos_phi, sin_phi)
|
|
85
|
+
|
|
86
|
+
# 2. Register a custom scattering phase function
|
|
87
|
+
@register_scattering("custom-scattering")
|
|
88
|
+
class CustomScattering(Scattering):
|
|
89
|
+
# Specify arbitrary custom parameters
|
|
90
|
+
def __init__(self, g, resolution=1000):
|
|
91
|
+
# Define a pdf array
|
|
92
|
+
self.g = g
|
|
93
|
+
cos_grid = np.linspace(-1, 1, resolution)
|
|
94
|
+
|
|
95
|
+
pdf = (1 - g**2) / (2 * (1 + g**2 - 2 * g * cos_grid) ** 1.5)
|
|
96
|
+
|
|
97
|
+
super().__init__(pdf_array=pdf, resolution=resolution)
|
|
98
|
+
|
|
99
|
+
# 3. Run the simulation using custom names in your config
|
|
100
|
+
if __name__ == "__main__":
|
|
101
|
+
context = build_context("simulation.toml")
|
|
102
|
+
runner = MCRadiationRunner(context)
|
|
103
|
+
runner.run()
|
|
104
|
+
|
|
105
|
+
# 4. Save and analyze results
|
|
106
|
+
results = runner.get_results()
|
|
107
|
+
outputs = DataIO(context.config)
|
|
108
|
+
analyzer = ResultAnalyzer(results, context.config)
|
|
109
|
+
|
|
110
|
+
outputs.save_all_artifacts(analyzer, results)
|
|
111
|
+
outputs.save_metadata(context.config, results)
|
|
112
|
+
outputs.save_results(results)
|
|
113
|
+
```
|
|
114
|
+
In `simulation.toml` you can specify your defined scatterings and reflections:
|
|
115
|
+
```toml
|
|
116
|
+
[atmosphere_materials.custom-atm-material]
|
|
117
|
+
ssa = 0.9
|
|
118
|
+
scattering = {type = "custom-scattering", g=0.8}
|
|
119
|
+
|
|
120
|
+
[surface_materials.custom-surf-material]
|
|
121
|
+
albedo = 0.5
|
|
122
|
+
reflection = {type = "custom-reflection", param_1=2, param_2=1.3} # match param names defined in python
|
|
123
|
+
```
|
|
124
|
+
Then you can use your defined materials for atmospheric layers and surface maps:
|
|
125
|
+
```toml
|
|
126
|
+
[[layer]]
|
|
127
|
+
z_range_km = [0, 2]
|
|
128
|
+
materials = [{type = "custom-atm-material", weight = 1.0}]
|
|
129
|
+
|
|
130
|
+
# ...
|
|
131
|
+
|
|
132
|
+
[surface]
|
|
133
|
+
name = "uniform"
|
|
134
|
+
material = "custom-surf-material"
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
## References and Literature
|
|
138
|
+
- (in Polish) Script for Lecture about [Radiative Processes in the Atmosphere](https://www.igf.fuw.edu.pl/~kmark/stacja/wyklady/ProcesyRadiacyjne/2013/WykladRadiacjaKlimat.pdf), Prof. K. Markowicz, Faculty of Physics, University of Warsaw, 2013.
|
|
139
|
+
|
|
140
|
+
## Acknowledgments
|
|
141
|
+
- This project was inspired by the lectures on *Radiative Processes in the Atmosphere* by Prof. K. Markowicz, Faculty of Physics, University of Warsaw.
|
|
142
|
+
- Large Language Models were used for code debugging and learning best Python practices (e.g. `dataclasses`, `__init__.py` import interfaces, class responsibilities, config parsing).
|
|
143
|
+
|
|
144
|
+
## Contributing
|
|
145
|
+
Feel free to open an [Issue](https://github.com/dabrokarol/atmorad-py/issues) or submit a Pull Request if you'd like to contribute or report a bug.
|
|
146
|
+
|
atmorad_py-0.1.1/TODO.md
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
<!-- Robię teraz parsofanie configa. -->
|
|
2
|
+
<!-- Trzeba zrobić konstruktory stosownych klas i stworzyć rejestry mapujące -->
|
|
3
|
+
<!-- Gdy będą gotowe, trzeba zrobić parser configa -->
|
|
4
|
+
|
|
5
|
+
<!-- Drugie zadanie: Results: tworzenie results/data-nazwa/ dla każdego eksperymentu
|
|
6
|
+
w folderze będzie config, matadane, wyniki, fig/{wykresy} -->
|
|
7
|
+
|
|
8
|
+
<!-- Trzecie zadanie: argparse
|
|
9
|
+
- --help-config - wypluwa informacje o tym co trzeba do configa
|
|
10
|
+
- --config=path - ścieżka do configa żeby łatwo wywoływać program -->
|
|
11
|
+
|
|
12
|
+
Czwarte zadanie:
|
|
13
|
+
dodanie modeli odbicia i rozpraszania
|
|
14
|
+
- RoughSpecularReflection
|
|
15
|
+
|
|
16
|
+
Piąte zadanie:
|
|
17
|
+
<!-- - zwróć uwagę na dzielenie przez zero w scene.py (żeby było np.inf, a nie NaN) -->
|
|
18
|
+
- zaimplementuj wskaźnik ile fotonów przetwarza się na sekundę (może nawet do wykresów)
|
|
19
|
+
|
|
20
|
+
<!-- Szóste zadanie: -->
|
|
21
|
+
<!-- - detektory klasa basedetector -->
|
|
22
|
+
<!-- - w scenie (lub symulacji) znajdują się eventy -->
|
|
23
|
+
<!-- - każdy detektor dostaje pozycje i kierunki fotonów, które biorą udział w evencie -->
|
|
24
|
+
|
|
25
|
+
<!-- BUG:
|
|
26
|
+
energia się nie dodaje -->
|
|
27
|
+
|
|
28
|
+
Zadanie:
|
|
29
|
+
Napisać mega basic testy
|
|
30
|
+
|
|
31
|
+
Zadanie:
|
|
32
|
+
Pobawić się
|
|
33
|
+
|
|
34
|
+
Zadanie:
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
|
|
3
|
+
from atmorad import build_context, MCRadiationRunner, register_reflection, SurfaceReflection, orientation, Scattering, register_scattering, DataIO, ResultAnalyzer, rotate
|
|
4
|
+
|
|
5
|
+
@register_reflection("my-example-reflection")
|
|
6
|
+
class ExampleReflection(SurfaceReflection):
|
|
7
|
+
def reflect(self, direction, rand_1, rand_2):
|
|
8
|
+
# your reflection code
|
|
9
|
+
|
|
10
|
+
return orientation(cos_theta, sin_theta, cos_phi, sin_phi)
|
|
11
|
+
|
|
12
|
+
@register_reflection("lambertian")
|
|
13
|
+
class LambertianReflection(SurfaceReflection):
|
|
14
|
+
def reflect(self, direction, rand_1, rand_2):
|
|
15
|
+
phi = rand_2 * 2 * np.pi
|
|
16
|
+
|
|
17
|
+
cos_theta = np.sqrt(rand_1)
|
|
18
|
+
sin_theta = np.sqrt(1.0 - rand_1)
|
|
19
|
+
|
|
20
|
+
cos_phi = np.cos(phi)
|
|
21
|
+
sin_phi = np.sin(phi)
|
|
22
|
+
return (rotate())
|
|
23
|
+
return orientation(cos_theta, sin_theta, cos_phi, sin_phi)
|
|
24
|
+
|
|
25
|
+
@register_scattering("my-example-scattering")
|
|
26
|
+
class ExampleScattering(Scattering):
|
|
27
|
+
def __init__(self, example-param, )
|
|
28
|
+
|
|
29
|
+
@register_scattering("hg")
|
|
30
|
+
class HenyeyGreensteinScattering(Scattering):
|
|
31
|
+
def __init__(self, g: float, resolution=1000):
|
|
32
|
+
self.g = g
|
|
33
|
+
cos_grid = np.linspace(-1, 1, resolution)
|
|
34
|
+
|
|
35
|
+
if np.isclose(g, 1.0, atol=1e-9):
|
|
36
|
+
pdf = np.isclose(cos_grid, 1.0, atol=1e-9).astype(float)
|
|
37
|
+
elif np.isclose(g, -1.0, atol=1e-9):
|
|
38
|
+
pdf = np.isclose(cos_grid, -1.0, atol=1e-9).astype(float)
|
|
39
|
+
else:
|
|
40
|
+
pdf = (1 - g**2) / (2 * (1 + g**2 - 2 * g * cos_grid) ** 1.5)
|
|
41
|
+
|
|
42
|
+
super().__init__(pdf_array=pdf, resolution=resolution)
|
|
43
|
+
|
|
44
|
+
context = build_context("simulation.toml")
|
|
45
|
+
runner = MCRadiationRunner(context)
|
|
46
|
+
runner.run()
|
|
47
|
+
|
|
48
|
+
results = runner.get_results()
|
|
49
|
+
|
|
50
|
+
outputs = DataIO(context.config)
|
|
51
|
+
analyzer = ResultAnalyzer(results, context.config)
|
|
52
|
+
outputs.save_all_artifacts(analyzer, results)
|
|
53
|
+
outputs.save_metadata(context.config, results)
|
|
54
|
+
outputs.save_results(results)
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
[metadata]
|
|
2
|
+
experiment_name = "adjacency001"
|
|
3
|
+
description = "Demo showcasing adjacency effect"
|
|
4
|
+
|
|
5
|
+
[engine]
|
|
6
|
+
num_photons = 1_000_000
|
|
7
|
+
batch_size = 100_000
|
|
8
|
+
cpu_cores = 4
|
|
9
|
+
|
|
10
|
+
[source]
|
|
11
|
+
theta_sun_deg = 30
|
|
12
|
+
|
|
13
|
+
[geometry]
|
|
14
|
+
domain_size_x_km = 100
|
|
15
|
+
domain_size_y_km = 100
|
|
16
|
+
|
|
17
|
+
[detectors]
|
|
18
|
+
num_full_paths = 0
|
|
19
|
+
incident_flux_heights_km = [1e-5, 4.0]
|
|
20
|
+
|
|
21
|
+
[output]
|
|
22
|
+
save_incident_flux_maps = true
|
|
23
|
+
overwrite = true
|
|
24
|
+
save_plots = true
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
[[layer]]
|
|
28
|
+
z_range_km = [0, 4]
|
|
29
|
+
materials = [
|
|
30
|
+
{type = "dark-clouds", weight = 1.0}
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
[surface]
|
|
34
|
+
name = "split-half-x"
|
|
35
|
+
material_left = "snow"
|
|
36
|
+
material_right = "ocean"
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling", "hatch-vcs"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "atmorad-py"
|
|
7
|
+
dynamic = ["version"]
|
|
8
|
+
description = "A 3D monte carlo simulation of Atmospheric Radiative Transfer"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
authors = [
|
|
12
|
+
{ name = "Karol Dąbrowski", email = "atmorad@kdabr.com" }
|
|
13
|
+
]
|
|
14
|
+
license = { text = "MIT" }
|
|
15
|
+
classifiers = [
|
|
16
|
+
"Programming Language :: Python :: 3",
|
|
17
|
+
"License :: OSI Approved :: MIT License",
|
|
18
|
+
"Operating System :: OS Independent",
|
|
19
|
+
"Topic :: Scientific/Engineering :: Physics",
|
|
20
|
+
]
|
|
21
|
+
dependencies = [
|
|
22
|
+
"numpy>=1.26.0",
|
|
23
|
+
"matplotlib>=3.5.0",
|
|
24
|
+
"seaborn>=0.11.0",
|
|
25
|
+
"cmocean>=2.0.0",
|
|
26
|
+
"tqdm>=4.60.0",
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
[project.scripts]
|
|
30
|
+
atmorad = "atmorad.cli:main"
|
|
31
|
+
|
|
32
|
+
[tool.hatch.version]
|
|
33
|
+
source = "vcs"
|
|
34
|
+
|
|
35
|
+
[tool.hatch.build.targets.wheel]
|
|
36
|
+
packages = ["src/atmorad"]
|
|
37
|
+
include-package-data = true
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
[dependency-groups]
|
|
41
|
+
dev = [
|
|
42
|
+
"pytest>=9.0.3",
|
|
43
|
+
"ruff>=0.15.13",
|
|
44
|
+
]
|
|
45
|
+
|
|
46
|
+
[tool.ruff]
|
|
47
|
+
line-length = 100
|
|
48
|
+
|
|
49
|
+
[tool.ruff.lint]
|
|
50
|
+
select = ["E", "F", "I"]
|
|
51
|
+
ignore = ["E501"]
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
[metadata]
|
|
2
|
+
experiment_name = "demo001"
|
|
3
|
+
description = "A demo simulation."
|
|
4
|
+
|
|
5
|
+
[engine]
|
|
6
|
+
num_photons = 100_000
|
|
7
|
+
batch_size = 100_000
|
|
8
|
+
random_seed = 42
|
|
9
|
+
cpu_cores = 4
|
|
10
|
+
|
|
11
|
+
[source]
|
|
12
|
+
theta_sun_deg = 30
|
|
13
|
+
phi_sun_deg = 0
|
|
14
|
+
wavelength_nm = 530
|
|
15
|
+
|
|
16
|
+
[geometry]
|
|
17
|
+
domain_size_x_km = 100
|
|
18
|
+
domain_size_y_km = 100
|
|
19
|
+
boundary_condition = "periodic"
|
|
20
|
+
|
|
21
|
+
[detectors]
|
|
22
|
+
vertical_flux_resolution_km = 1.0
|
|
23
|
+
map2d_resolution_km = 1.0
|
|
24
|
+
num_full_paths = 200
|
|
25
|
+
incident_flux_heights_km = [1e-5, 4.0]
|
|
26
|
+
|
|
27
|
+
[output]
|
|
28
|
+
save_absorption_maps = true
|
|
29
|
+
save_incident_flux_maps = true
|
|
30
|
+
save_vertical_profile = true
|
|
31
|
+
save_photon_paths = true
|
|
32
|
+
save_heating_rates = true
|
|
33
|
+
save_plots = true
|
|
34
|
+
overwrite = true
|
|
35
|
+
path = 'results'
|
|
36
|
+
|
|
37
|
+
[surface_materials]
|
|
38
|
+
[surface_materials.snow]
|
|
39
|
+
albedo = 0.85
|
|
40
|
+
reflection = {type = "lambertian"}
|
|
41
|
+
[surface_materials.ocean]
|
|
42
|
+
albedo = 0.01
|
|
43
|
+
reflection = {type = "mirror", roughness = 0.0}
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
[atmosphere_materials]
|
|
47
|
+
[atmosphere_materials.air]
|
|
48
|
+
extinction_coeff_per_km = 0.01
|
|
49
|
+
ssa = 0.9
|
|
50
|
+
scattering = {type = "rayleigh"} # rayleigh, hg, isotropic
|
|
51
|
+
[atmosphere_materials.light-clouds]
|
|
52
|
+
extinction_coeff_per_km = 1
|
|
53
|
+
ssa = 0.999999
|
|
54
|
+
scattering = {type = "hg", g = 0.85}
|
|
55
|
+
[atmosphere_materials.dark-clouds]
|
|
56
|
+
extinction_coeff_per_km = 5
|
|
57
|
+
ssa = 0.999999
|
|
58
|
+
scattering = {type = "hg", g = 0.85}
|
|
59
|
+
|
|
60
|
+
# Define atmospheric structure from bottom to top
|
|
61
|
+
[[layer]]
|
|
62
|
+
z_range_km = [0, 2]
|
|
63
|
+
materials = [{type = "air", weight = 1.0}]
|
|
64
|
+
|
|
65
|
+
[[layer]]
|
|
66
|
+
z_range_km = [2, 6]
|
|
67
|
+
materials = [
|
|
68
|
+
{type = "air", weight = 0.1},
|
|
69
|
+
{type = "dark-clouds", weight = 0.9}
|
|
70
|
+
]
|
|
71
|
+
|
|
72
|
+
[[layer]]
|
|
73
|
+
z_range_km = [6, 10]
|
|
74
|
+
materials = [{type = "air", weight = 1.0}]
|
|
75
|
+
|
|
76
|
+
# --- Surface Map Configuration ---
|
|
77
|
+
# Choose one surface map by commenting out the others
|
|
78
|
+
|
|
79
|
+
# [surface]
|
|
80
|
+
# name = "uniform"
|
|
81
|
+
# material = "snow"
|
|
82
|
+
|
|
83
|
+
# [surface]
|
|
84
|
+
# name = "split-half-x"
|
|
85
|
+
# material_left = "snow"
|
|
86
|
+
# material_right = "ocean"
|
|
87
|
+
|
|
88
|
+
# [surface]
|
|
89
|
+
# name = "checkerboard"
|
|
90
|
+
# tile_size_km = 10
|
|
91
|
+
# material_a = "snow"
|
|
92
|
+
# material_b = "ocean"
|
|
93
|
+
|
|
94
|
+
[surface]
|
|
95
|
+
name = "circle"
|
|
96
|
+
radius_km = 20
|
|
97
|
+
material_in = "snow"
|
|
98
|
+
material_out = "ocean"
|