pa3py 1.0.0__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.
- pa3py-1.0.0/LICENSE +21 -0
- pa3py-1.0.0/PKG-INFO +99 -0
- pa3py-1.0.0/README.md +73 -0
- pa3py-1.0.0/pyproject.toml +38 -0
- pa3py-1.0.0/setup.cfg +4 -0
- pa3py-1.0.0/src/pa3py/__init__.py +65 -0
- pa3py-1.0.0/src/pa3py/composition.py +117 -0
- pa3py-1.0.0/src/pa3py/constants.py +19 -0
- pa3py-1.0.0/src/pa3py/core.py +109 -0
- pa3py-1.0.0/src/pa3py/data.py +122 -0
- pa3py-1.0.0/src/pa3py/pebble_accretion.py +228 -0
- pa3py-1.0.0/src/pa3py/plotting.py +164 -0
- pa3py-1.0.0/src/pa3py/snowline.py +68 -0
- pa3py-1.0.0/src/pa3py.egg-info/PKG-INFO +99 -0
- pa3py-1.0.0/src/pa3py.egg-info/SOURCES.txt +22 -0
- pa3py-1.0.0/src/pa3py.egg-info/dependency_links.txt +1 -0
- pa3py-1.0.0/src/pa3py.egg-info/requires.txt +8 -0
- pa3py-1.0.0/src/pa3py.egg-info/top_level.txt +1 -0
- pa3py-1.0.0/tests/test_composition.py +102 -0
- pa3py-1.0.0/tests/test_constants.py +30 -0
- pa3py-1.0.0/tests/test_io.py +63 -0
- pa3py-1.0.0/tests/test_pebble_accretion.py +239 -0
- pa3py-1.0.0/tests/test_plotting.py +33 -0
- pa3py-1.0.0/tests/test_snowline.py +56 -0
pa3py-1.0.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Maximiliano Valderrama
|
|
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.
|
pa3py-1.0.0/PKG-INFO
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pa3py
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Post-processing tool for pebble accretion onto planetary embryos from TripodPy hydrodynamic simulations.
|
|
5
|
+
Author-email: Maximiliano Valderrama <mvalderrav@uc.cl>
|
|
6
|
+
License: MIT License
|
|
7
|
+
Project-URL: Homepage, https://github.com/mmmaxii/PA3Py
|
|
8
|
+
Project-URL: Bug Tracker, https://github.com/mmmaxii/PA3Py/issues
|
|
9
|
+
Project-URL: Documentation, https://pa3py.readthedocs.io
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Operating System :: OS Independent
|
|
13
|
+
Classifier: Intended Audience :: Science/Research
|
|
14
|
+
Classifier: Topic :: Scientific/Engineering :: Astronomy
|
|
15
|
+
Requires-Python: >=3.9
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
License-File: LICENSE
|
|
18
|
+
Requires-Dist: numpy>=1.20
|
|
19
|
+
Requires-Dist: scipy>=1.7
|
|
20
|
+
Requires-Dist: h5py>=3.0
|
|
21
|
+
Requires-Dist: matplotlib>=3.4
|
|
22
|
+
Provides-Extra: dev
|
|
23
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
24
|
+
Requires-Dist: jupyter>=1.0; extra == "dev"
|
|
25
|
+
Dynamic: license-file
|
|
26
|
+
|
|
27
|
+
# PA3Py — Pebble Accretion Post-Processing for TripodPy
|
|
28
|
+
|
|
29
|
+
[](https://opensource.org/licenses/MIT) [](https://github.com/mmmaxii/PA3Py/actions) [](https://pa3py.readthedocs.io/en/latest/?badge=latest)
|
|
30
|
+
|
|
31
|
+
**PA3Py** is a Python post-processing package for computing pebble accretion growth of planetary embryos from [TripodPy](https://github.com/tripod-code/tripodpy) hydrodynamic disk simulations. It is **not** a standalone disk evolution code — it reads TripodPy HDF5 output snapshots and computes embryo growth on top of them.
|
|
32
|
+
|
|
33
|
+
The physics follows Ormel (2017) and Drążkowska et al. (2023), including headwind/shear regime switching, 2D–3D turbulence transition, Safronov ballistic onset, and a dynamic isolation mass cap.
|
|
34
|
+
|
|
35
|
+
Please read the [documentation](https://pa3py.readthedocs.io) for a full description.
|
|
36
|
+
|
|
37
|
+
## Installation
|
|
38
|
+
|
|
39
|
+
Clone the repository and install via pip:
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
git clone https://github.com/mmmaxii/PA3Py.git
|
|
43
|
+
cd PA3Py
|
|
44
|
+
pip install .
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
For an editable installation (recommended during development):
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
pip install -e ".[dev]"
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Requirements
|
|
54
|
+
|
|
55
|
+
- Python ≥ 3.9
|
|
56
|
+
- [TripodPy](https://github.com/tripod-code/tripodpy) simulation output (HDF5 snapshots)
|
|
57
|
+
- `numpy`, `scipy`, `h5py`, `matplotlib`
|
|
58
|
+
|
|
59
|
+
## Quick Start
|
|
60
|
+
|
|
61
|
+
```python
|
|
62
|
+
from pa3py import PA3Py
|
|
63
|
+
|
|
64
|
+
sim = PA3Py("path/to/tripodpy/output/")
|
|
65
|
+
|
|
66
|
+
# Run pebble accretion for embryos at 2, 5, 10, 20 AU
|
|
67
|
+
results = sim.run_growth([2.0, 5.0, 10.0, 20.0])
|
|
68
|
+
|
|
69
|
+
# Hovmöller diagram (dust-to-gas ratio vs time)
|
|
70
|
+
sim.plot_hovmoller(field='epsilon')
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
For custom chemistry (multi-snowline, 4-species):
|
|
74
|
+
|
|
75
|
+
```python
|
|
76
|
+
from pa3py import PA3Py, FunctionComposition
|
|
77
|
+
from pa3py import constants as c
|
|
78
|
+
|
|
79
|
+
def my_chemistry(r_cm, t_sec):
|
|
80
|
+
if r_cm < 3.0 * c.AU:
|
|
81
|
+
return {'silicates': 1.0}
|
|
82
|
+
elif r_cm < 5.0 * c.AU:
|
|
83
|
+
return {'silicates': 0.5, 'H2O': 0.5}
|
|
84
|
+
else:
|
|
85
|
+
return {'silicates': 0.3, 'H2O': 0.3, 'CO2': 0.4}
|
|
86
|
+
|
|
87
|
+
sim = PA3Py("path/to/output/", comp_model=FunctionComposition(my_chemistry))
|
|
88
|
+
results = sim.run_growth([5.0, 10.0, 20.0])
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## Physics & References
|
|
92
|
+
|
|
93
|
+
- **Ormel (2017)**: *The Emerging Paradigm of Pebble Accretion*
|
|
94
|
+
- **Drążkowska et al. (2023)**: *Planet Formation Theory in the Era of ALMA and Kepler*
|
|
95
|
+
|
|
96
|
+
## Acknowledgements
|
|
97
|
+
|
|
98
|
+
PA3Py was developed at the Pontificia Universidad Católica de Chile.
|
|
99
|
+
Contact: [mvalderrav@uc.cl](mailto:mvalderrav@uc.cl)
|
pa3py-1.0.0/README.md
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# PA3Py — Pebble Accretion Post-Processing for TripodPy
|
|
2
|
+
|
|
3
|
+
[](https://opensource.org/licenses/MIT) [](https://github.com/mmmaxii/PA3Py/actions) [](https://pa3py.readthedocs.io/en/latest/?badge=latest)
|
|
4
|
+
|
|
5
|
+
**PA3Py** is a Python post-processing package for computing pebble accretion growth of planetary embryos from [TripodPy](https://github.com/tripod-code/tripodpy) hydrodynamic disk simulations. It is **not** a standalone disk evolution code — it reads TripodPy HDF5 output snapshots and computes embryo growth on top of them.
|
|
6
|
+
|
|
7
|
+
The physics follows Ormel (2017) and Drążkowska et al. (2023), including headwind/shear regime switching, 2D–3D turbulence transition, Safronov ballistic onset, and a dynamic isolation mass cap.
|
|
8
|
+
|
|
9
|
+
Please read the [documentation](https://pa3py.readthedocs.io) for a full description.
|
|
10
|
+
|
|
11
|
+
## Installation
|
|
12
|
+
|
|
13
|
+
Clone the repository and install via pip:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
git clone https://github.com/mmmaxii/PA3Py.git
|
|
17
|
+
cd PA3Py
|
|
18
|
+
pip install .
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
For an editable installation (recommended during development):
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
pip install -e ".[dev]"
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Requirements
|
|
28
|
+
|
|
29
|
+
- Python ≥ 3.9
|
|
30
|
+
- [TripodPy](https://github.com/tripod-code/tripodpy) simulation output (HDF5 snapshots)
|
|
31
|
+
- `numpy`, `scipy`, `h5py`, `matplotlib`
|
|
32
|
+
|
|
33
|
+
## Quick Start
|
|
34
|
+
|
|
35
|
+
```python
|
|
36
|
+
from pa3py import PA3Py
|
|
37
|
+
|
|
38
|
+
sim = PA3Py("path/to/tripodpy/output/")
|
|
39
|
+
|
|
40
|
+
# Run pebble accretion for embryos at 2, 5, 10, 20 AU
|
|
41
|
+
results = sim.run_growth([2.0, 5.0, 10.0, 20.0])
|
|
42
|
+
|
|
43
|
+
# Hovmöller diagram (dust-to-gas ratio vs time)
|
|
44
|
+
sim.plot_hovmoller(field='epsilon')
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
For custom chemistry (multi-snowline, 4-species):
|
|
48
|
+
|
|
49
|
+
```python
|
|
50
|
+
from pa3py import PA3Py, FunctionComposition
|
|
51
|
+
from pa3py import constants as c
|
|
52
|
+
|
|
53
|
+
def my_chemistry(r_cm, t_sec):
|
|
54
|
+
if r_cm < 3.0 * c.AU:
|
|
55
|
+
return {'silicates': 1.0}
|
|
56
|
+
elif r_cm < 5.0 * c.AU:
|
|
57
|
+
return {'silicates': 0.5, 'H2O': 0.5}
|
|
58
|
+
else:
|
|
59
|
+
return {'silicates': 0.3, 'H2O': 0.3, 'CO2': 0.4}
|
|
60
|
+
|
|
61
|
+
sim = PA3Py("path/to/output/", comp_model=FunctionComposition(my_chemistry))
|
|
62
|
+
results = sim.run_growth([5.0, 10.0, 20.0])
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Physics & References
|
|
66
|
+
|
|
67
|
+
- **Ormel (2017)**: *The Emerging Paradigm of Pebble Accretion*
|
|
68
|
+
- **Drążkowska et al. (2023)**: *Planet Formation Theory in the Era of ALMA and Kepler*
|
|
69
|
+
|
|
70
|
+
## Acknowledgements
|
|
71
|
+
|
|
72
|
+
PA3Py was developed at the Pontificia Universidad Católica de Chile.
|
|
73
|
+
Contact: [mvalderrav@uc.cl](mailto:mvalderrav@uc.cl)
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "pa3py"
|
|
7
|
+
version = "1.0.0"
|
|
8
|
+
authors = [
|
|
9
|
+
{ name="Maximiliano Valderrama", email="mvalderrav@uc.cl" },
|
|
10
|
+
]
|
|
11
|
+
description = "Post-processing tool for pebble accretion onto planetary embryos from TripodPy hydrodynamic simulations."
|
|
12
|
+
readme = "README.md"
|
|
13
|
+
requires-python = ">=3.9"
|
|
14
|
+
license = { text = "MIT License" }
|
|
15
|
+
classifiers = [
|
|
16
|
+
"Programming Language :: Python :: 3",
|
|
17
|
+
"License :: OSI Approved :: MIT License",
|
|
18
|
+
"Operating System :: OS Independent",
|
|
19
|
+
"Intended Audience :: Science/Research",
|
|
20
|
+
"Topic :: Scientific/Engineering :: Astronomy",
|
|
21
|
+
]
|
|
22
|
+
dependencies = [
|
|
23
|
+
"numpy>=1.20",
|
|
24
|
+
"scipy>=1.7",
|
|
25
|
+
"h5py>=3.0",
|
|
26
|
+
"matplotlib>=3.4",
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
[project.optional-dependencies]
|
|
30
|
+
dev = ["pytest>=7.0", "jupyter>=1.0"]
|
|
31
|
+
|
|
32
|
+
[project.urls]
|
|
33
|
+
"Homepage" = "https://github.com/mmmaxii/PA3Py"
|
|
34
|
+
"Bug Tracker" = "https://github.com/mmmaxii/PA3Py/issues"
|
|
35
|
+
"Documentation" = "https://pa3py.readthedocs.io"
|
|
36
|
+
|
|
37
|
+
[tool.pytest.ini_options]
|
|
38
|
+
testpaths = ["tests"]
|
pa3py-1.0.0/setup.cfg
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"""
|
|
2
|
+
PA3Py - Pebble Accretion 3 in Python
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
__version__ = "1.0.0"
|
|
6
|
+
|
|
7
|
+
from .data import load_tripodpy_hdf5, DiskData
|
|
8
|
+
from .composition import CompositionModel, SimpleWaterComposition, MultiSnowlineComposition, FunctionComposition
|
|
9
|
+
from .pebble_accretion import PebbleAccretionModule3
|
|
10
|
+
from .snowline import generate_rsnow_array, get_rsnow_from_mdot_au, mdot_time, r_snow_time_cgs
|
|
11
|
+
from .plotting import plot_hovmoller
|
|
12
|
+
from .core import PA3Py
|
|
13
|
+
from . import constants
|
|
14
|
+
|
|
15
|
+
def easy_run(data_dir: str, embryos_au: list, m_seed_me: float = 1e-4) -> dict:
|
|
16
|
+
"""
|
|
17
|
+
Ruta rápida para correr el simulador PA3Py con el modelo por defecto.
|
|
18
|
+
|
|
19
|
+
Asume:
|
|
20
|
+
- Snowline dinámica basada en Oka et al. (2011) y Hartmann.
|
|
21
|
+
- Composición: 100% Silicatos (dentro de snowline) y 50% H2O / 50% Silicatos (fuera).
|
|
22
|
+
|
|
23
|
+
Parámetros:
|
|
24
|
+
-----------
|
|
25
|
+
data_dir : str
|
|
26
|
+
Ruta al directorio con los HDF5 de TripodPy.
|
|
27
|
+
embryos_au : list
|
|
28
|
+
Lista con las posiciones iniciales de los embriones en AU (ej: [3.0, 5.0, 10.0]).
|
|
29
|
+
m_seed_me : float
|
|
30
|
+
Masa semilla inicial de los embriones en Masas Terrestres.
|
|
31
|
+
|
|
32
|
+
Retorna:
|
|
33
|
+
--------
|
|
34
|
+
dict
|
|
35
|
+
Diccionario con la historia de crecimiento de cada embrión.
|
|
36
|
+
"""
|
|
37
|
+
# 1. Cargar datos
|
|
38
|
+
disk = load_tripodpy_hdf5(data_dir)
|
|
39
|
+
|
|
40
|
+
# 2. Configurar snowline y composición clásica
|
|
41
|
+
rsnow = generate_rsnow_array(disk.times)
|
|
42
|
+
comp = SimpleWaterComposition(rsnow)
|
|
43
|
+
|
|
44
|
+
# 3. Inicializar simulador y correr
|
|
45
|
+
sim = PebbleAccretionModule3(disk, comp_model=comp)
|
|
46
|
+
results = sim.run_growth(embryos_au, M0_g=m_seed_me * sim.M_EARTH)
|
|
47
|
+
|
|
48
|
+
# 4. Mostrar resumen automáticamente
|
|
49
|
+
sim.summary(results)
|
|
50
|
+
return results
|
|
51
|
+
|
|
52
|
+
__all__ = [
|
|
53
|
+
"load_tripodpy_hdf5",
|
|
54
|
+
"DiskData",
|
|
55
|
+
"CompositionModel",
|
|
56
|
+
"SimpleWaterComposition",
|
|
57
|
+
"MultiSnowlineComposition",
|
|
58
|
+
"FunctionComposition",
|
|
59
|
+
"PebbleAccretionModule3",
|
|
60
|
+
"generate_rsnow_array",
|
|
61
|
+
"easy_run",
|
|
62
|
+
"plot_hovmoller",
|
|
63
|
+
"PA3Py",
|
|
64
|
+
"constants"
|
|
65
|
+
]
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
"""
|
|
2
|
+
composition.py - Modelos de composición química.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from typing import Dict, List, Optional, Callable
|
|
6
|
+
import numpy as np
|
|
7
|
+
from abc import ABC, abstractmethod
|
|
8
|
+
|
|
9
|
+
class CompositionModel(ABC):
|
|
10
|
+
"""Clase base para modelos de composición."""
|
|
11
|
+
|
|
12
|
+
@abstractmethod
|
|
13
|
+
def get_fractions(self, r: float, t_sec: float, t_idx: int) -> Dict[str, float]:
|
|
14
|
+
"""
|
|
15
|
+
Dado un radio orbital `r` (en cm), el tiempo físico `t_sec` (s), y el índice temporal `t_idx`,
|
|
16
|
+
devuelve un diccionario con las fracciones de masa de cada elemento químico que se está acretando.
|
|
17
|
+
Las fracciones deben sumar 1.0.
|
|
18
|
+
"""
|
|
19
|
+
pass
|
|
20
|
+
|
|
21
|
+
@abstractmethod
|
|
22
|
+
def get_species(self) -> List[str]:
|
|
23
|
+
"""Devuelve la lista de nombres de las especies químicas rastreadas."""
|
|
24
|
+
pass
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class SimpleWaterComposition(CompositionModel):
|
|
28
|
+
"""
|
|
29
|
+
Modelo clásico PA3Py:
|
|
30
|
+
- 100% silicatos interior a la snowline.
|
|
31
|
+
- 50% silicatos, 50% H2O exterior a la snowline.
|
|
32
|
+
"""
|
|
33
|
+
def __init__(self, rsnow_h2o_array: np.ndarray):
|
|
34
|
+
self.rsnow = rsnow_h2o_array
|
|
35
|
+
|
|
36
|
+
def get_fractions(self, r: float, t_sec: float, t_idx: int) -> Dict[str, float]:
|
|
37
|
+
r_snow = float(self.rsnow[t_idx]) if not np.isnan(self.rsnow[t_idx]) else 0.0
|
|
38
|
+
|
|
39
|
+
if r >= r_snow:
|
|
40
|
+
return {"silicates": 0.5, "H2O": 0.5}
|
|
41
|
+
else:
|
|
42
|
+
return {"silicates": 1.0, "H2O": 0.0}
|
|
43
|
+
|
|
44
|
+
def get_species(self) -> List[str]:
|
|
45
|
+
return ["silicates", "H2O"]
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class MultiSnowlineComposition(CompositionModel):
|
|
49
|
+
"""Zonas estáticas separadas por snowlines pre-calculadas."""
|
|
50
|
+
def __init__(self, snowlines: Dict[str, np.ndarray], zone_abundances: List[Dict[str, float]]):
|
|
51
|
+
self.snowline_names = list(snowlines.keys())
|
|
52
|
+
self.snowlines = snowlines
|
|
53
|
+
self.zone_abundances = zone_abundances
|
|
54
|
+
|
|
55
|
+
if len(zone_abundances) != len(snowlines) + 1:
|
|
56
|
+
raise ValueError("Debes proveer exactamente N+1 diccionarios de abundancias para N snowlines.")
|
|
57
|
+
|
|
58
|
+
self.species = list(set().union(*(abund.keys() for abund in self.zone_abundances)))
|
|
59
|
+
|
|
60
|
+
def get_fractions(self, r: float, t_sec: float, t_idx: int) -> Dict[str, float]:
|
|
61
|
+
zone_idx = 0
|
|
62
|
+
for name in self.snowline_names:
|
|
63
|
+
r_snow = float(self.snowlines[name][t_idx]) if not np.isnan(self.snowlines[name][t_idx]) else 0.0
|
|
64
|
+
if r >= r_snow:
|
|
65
|
+
zone_idx += 1
|
|
66
|
+
else:
|
|
67
|
+
break
|
|
68
|
+
|
|
69
|
+
raw_fracs = self.zone_abundances[zone_idx]
|
|
70
|
+
total = sum(raw_fracs.values())
|
|
71
|
+
if total > 0:
|
|
72
|
+
return {k: v / total for k, v in raw_fracs.items()}
|
|
73
|
+
return {sp: 0.0 for sp in self.species}
|
|
74
|
+
|
|
75
|
+
def get_species(self) -> List[str]:
|
|
76
|
+
return self.species
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
class FunctionComposition(CompositionModel):
|
|
80
|
+
"""Química dinámica definida por el usuario mediante una función Python."""
|
|
81
|
+
def __init__(self, user_func: Callable[[float, float], Dict[str, float]], species: Optional[List[str]] = None):
|
|
82
|
+
"""
|
|
83
|
+
Parámetros:
|
|
84
|
+
-----------
|
|
85
|
+
user_func: callable
|
|
86
|
+
Función del usuario. Debe recibir `r` (en cm) y `t` (en segundos)
|
|
87
|
+
y retornar un diccionario con las fracciones, ej: {"silicates": 0.4, "H2O": 0.6}.
|
|
88
|
+
species: List[str], opcional
|
|
89
|
+
Lista explícita de las especies químicas. Si no se provee, PA3Py la deducirá
|
|
90
|
+
automáticamente ejecutando la función con un valor de prueba (r=1.0, t=0.0).
|
|
91
|
+
"""
|
|
92
|
+
self.user_func = user_func
|
|
93
|
+
|
|
94
|
+
if species is None:
|
|
95
|
+
try:
|
|
96
|
+
res_sets = [set(self.user_func(r, t).keys())
|
|
97
|
+
for r in [1e11, 1e12, 1e13, 1e14, 1e15, 1e16]
|
|
98
|
+
for t in [0.0, 1e10, 1e12, 1e14]]
|
|
99
|
+
self.species = list(set.union(*res_sets)) if res_sets else []
|
|
100
|
+
if not self.species:
|
|
101
|
+
raise ValueError("La función devolvió diccionarios vacíos en todos los tests.")
|
|
102
|
+
except AttributeError:
|
|
103
|
+
raise ValueError("La función debe retornar un dict (con el método .keys()).")
|
|
104
|
+
except Exception as e:
|
|
105
|
+
raise RuntimeError(f"Fallo en la auto-detección de especies: {e}\nProvee 'species' explícitamente.")
|
|
106
|
+
else:
|
|
107
|
+
self.species = species
|
|
108
|
+
|
|
109
|
+
def get_fractions(self, r: float, t_sec: float, t_idx: int) -> Dict[str, float]:
|
|
110
|
+
raw_fracs = self.user_func(r, t_sec)
|
|
111
|
+
total = sum(raw_fracs.values())
|
|
112
|
+
if total > 0:
|
|
113
|
+
return {sp: raw_fracs.get(sp, 0.0) / total for sp in self.species}
|
|
114
|
+
return {sp: 0.0 for sp in self.species}
|
|
115
|
+
|
|
116
|
+
def get_species(self) -> List[str]:
|
|
117
|
+
return self.species
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Constants used for Pebble Accretion calculations in PA3Py.
|
|
3
|
+
All values are in CGS units (cm, g, s).
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
# Gravitational constant [cm^3 g^-1 s^-2]
|
|
7
|
+
G = 6.6743e-8
|
|
8
|
+
|
|
9
|
+
# Solar mass [g]
|
|
10
|
+
M_SUN = 1.988409870698051e33
|
|
11
|
+
|
|
12
|
+
# Earth mass [g]
|
|
13
|
+
M_EARTH = 5.972167867791379e27
|
|
14
|
+
|
|
15
|
+
# Astronomical Unit [cm]
|
|
16
|
+
AU = 1.495978707e13
|
|
17
|
+
|
|
18
|
+
# Julian Year [s]
|
|
19
|
+
YEAR = 3.15576e7
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
"""
|
|
2
|
+
core.py - Interfaz principal (Facade).
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from typing import List, Callable, Optional
|
|
6
|
+
import h5py
|
|
7
|
+
|
|
8
|
+
from .data import load_tripodpy_hdf5
|
|
9
|
+
from .composition import CompositionModel, SimpleWaterComposition, FunctionComposition
|
|
10
|
+
from .pebble_accretion import PebbleAccretionModule3
|
|
11
|
+
from .snowline import generate_rsnow_array
|
|
12
|
+
from .plotting import plot_hovmoller
|
|
13
|
+
from . import constants as c
|
|
14
|
+
|
|
15
|
+
class PA3Py:
|
|
16
|
+
"""
|
|
17
|
+
Centraliza flujo de PA3Py: Datos HDF5, química, acreción y gráficas.
|
|
18
|
+
"""
|
|
19
|
+
def __init__(self, data_dir: str, comp_model: Optional[CompositionModel] = None):
|
|
20
|
+
"""
|
|
21
|
+
Inicializa la simulación y carga los datos.
|
|
22
|
+
|
|
23
|
+
Parámetros:
|
|
24
|
+
-----------
|
|
25
|
+
data_dir : str
|
|
26
|
+
Ruta a archivos HDF5.
|
|
27
|
+
comp_model : CompositionModel, opcional
|
|
28
|
+
Modelo de composición. Por defecto: Agua simple + migración snowline.
|
|
29
|
+
"""
|
|
30
|
+
self.data_dir = data_dir
|
|
31
|
+
self.disk = load_tripodpy_hdf5(data_dir)
|
|
32
|
+
self.comp = comp_model or SimpleWaterComposition(generate_rsnow_array(self.disk.times))
|
|
33
|
+
self.engine = PebbleAccretionModule3(self.disk, comp_model=self.comp)
|
|
34
|
+
|
|
35
|
+
def set_custom_chemistry(self, user_func: Callable, species: Optional[List[str]] = None):
|
|
36
|
+
"""
|
|
37
|
+
Atajo para redefinir la química con una función Python.
|
|
38
|
+
|
|
39
|
+
Ejemplo:
|
|
40
|
+
sim.set_custom_chemistry(mi_funcion, ["silicatos", "H2O"])
|
|
41
|
+
"""
|
|
42
|
+
self.comp = FunctionComposition(user_func, species)
|
|
43
|
+
self.engine = PebbleAccretionModule3(self.disk, comp_model=self.comp)
|
|
44
|
+
|
|
45
|
+
def run_growth(self, embryos_au: list, m_seed_me: float = 1e-3) -> dict:
|
|
46
|
+
"""
|
|
47
|
+
Corre la simulación de acreción para los embriones dados.
|
|
48
|
+
|
|
49
|
+
Parámetros:
|
|
50
|
+
-----------
|
|
51
|
+
embryos_au : list
|
|
52
|
+
Lista de radios iniciales en AU (ej: [1.0, 5.0, 15.0]).
|
|
53
|
+
m_seed_me : float
|
|
54
|
+
Masa semilla en Masas Terrestres. Default: 1e-3.
|
|
55
|
+
|
|
56
|
+
Retorna:
|
|
57
|
+
--------
|
|
58
|
+
dict
|
|
59
|
+
Resultados de la evolución de masa en el tiempo.
|
|
60
|
+
"""
|
|
61
|
+
results = self.engine.run_growth(embryos_au, M0_g=m_seed_me * c.M_EARTH)
|
|
62
|
+
self.engine.summary(results)
|
|
63
|
+
return results
|
|
64
|
+
|
|
65
|
+
def plot_hovmoller(self, field: str = 'dust_Sigma', show_snowlines: bool = True, **kwargs):
|
|
66
|
+
"""
|
|
67
|
+
Genera el diagrama de Hovmöller (Radio vs Tiempo).
|
|
68
|
+
field puede ser 'dust_Sigma', 'gas_Sigma', o 'epsilon'.
|
|
69
|
+
"""
|
|
70
|
+
return plot_hovmoller(self.disk, field=field, show_snowlines=show_snowlines, **kwargs)
|
|
71
|
+
|
|
72
|
+
def calculate_isolation_mass_map(self):
|
|
73
|
+
"""Calcula el mapa teórico de masa de aislamiento en todo el disco."""
|
|
74
|
+
return self.engine.calculate_isolation_mass_map()
|
|
75
|
+
|
|
76
|
+
def plot_population(self, results: dict, **kwargs):
|
|
77
|
+
"""
|
|
78
|
+
Grafica la población sintética de planetas (Masa Final vs Posición Inicial).
|
|
79
|
+
"""
|
|
80
|
+
from .plotting import plot_population
|
|
81
|
+
M_iso_map = self.calculate_isolation_mass_map()
|
|
82
|
+
return plot_population(self.disk, results, M_iso_map=M_iso_map, **kwargs)
|
|
83
|
+
|
|
84
|
+
def save_results(self, results: dict, filename: str):
|
|
85
|
+
"""Guarda tracks de crecimiento en HDF5."""
|
|
86
|
+
with h5py.File(filename, 'w') as f:
|
|
87
|
+
# Encode species como bytes para compatibilidad h5py
|
|
88
|
+
f.attrs['tracked_species'] = [s.encode('ascii') for s in self.engine.tracked_species]
|
|
89
|
+
f.attrs['M_EARTH'] = c.M_EARTH
|
|
90
|
+
|
|
91
|
+
tracks = f.create_group('tracks')
|
|
92
|
+
for r_au, track_data in results.items():
|
|
93
|
+
dset = tracks.create_dataset(f"embryo_{r_au:.4f}_AU", data=track_data)
|
|
94
|
+
dset.attrs['r_au'] = float(r_au)
|
|
95
|
+
|
|
96
|
+
@staticmethod
|
|
97
|
+
def load_results(filename: str) -> tuple[dict, list]:
|
|
98
|
+
"""Carga tracks de crecimiento desde HDF5. Retorna (results_dict, species_list)."""
|
|
99
|
+
results = {}
|
|
100
|
+
with h5py.File(filename, 'r') as f:
|
|
101
|
+
raw_species = list(f.attrs['tracked_species'])
|
|
102
|
+
tracked_species = [s.decode('ascii') if isinstance(s, bytes) else s for s in raw_species]
|
|
103
|
+
|
|
104
|
+
tracks = f['tracks']
|
|
105
|
+
for key in tracks.keys():
|
|
106
|
+
r_au = float(tracks[key].attrs['r_au'])
|
|
107
|
+
results[r_au] = tracks[key][:]
|
|
108
|
+
|
|
109
|
+
return results, tracked_species
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
"""
|
|
2
|
+
data.py - Módulo para la carga y manejo de datos del disco protoplanetario.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
import glob
|
|
7
|
+
import h5py
|
|
8
|
+
import numpy as np
|
|
9
|
+
from dataclasses import dataclass
|
|
10
|
+
from typing import Dict
|
|
11
|
+
from . import constants as c
|
|
12
|
+
|
|
13
|
+
@dataclass
|
|
14
|
+
class DiskData:
|
|
15
|
+
"""Contenedor de propiedades físicas del disco (gas y polvo)."""
|
|
16
|
+
times: np.ndarray # (Nt,) Tiempos en segundos
|
|
17
|
+
r: np.ndarray # (Nr,) Grilla radial en cm
|
|
18
|
+
|
|
19
|
+
# Gas: shape (Nt, Nr)
|
|
20
|
+
gas_Sigma: np.ndarray # Densidad superficial del gas
|
|
21
|
+
gas_T: np.ndarray # Temperatura del gas
|
|
22
|
+
gas_cs: np.ndarray # Velocidad del sonido
|
|
23
|
+
gas_eta: np.ndarray # Gradiente de presión
|
|
24
|
+
gas_nu: np.ndarray # Viscosidad cinemática
|
|
25
|
+
gas_alpha: np.ndarray # Parámetro alpha de Shakura-Sunyaev
|
|
26
|
+
gas_Hp: np.ndarray # Escala de altura del gas
|
|
27
|
+
|
|
28
|
+
# Dust: shape (Nt, Nr, Nd) donde Nd=5 (o Nd=1 si ya se colapsó)
|
|
29
|
+
dust_Sigma: np.ndarray # Densidad superficial del polvo
|
|
30
|
+
dust_vr: np.ndarray # Velocidad radial del polvo
|
|
31
|
+
dust_St: np.ndarray # Número de Stokes
|
|
32
|
+
dust_H: np.ndarray # Escala de altura del polvo
|
|
33
|
+
|
|
34
|
+
# Derivados / Auxiliares
|
|
35
|
+
Omega_K: np.ndarray # Frecuencia kepleriana local (Nt, Nr) o (Nr,)
|
|
36
|
+
M_star: float # Masa estelar en masas solares
|
|
37
|
+
|
|
38
|
+
# Snowlines pre-calculadas presentes en el HDF5 (Opcional, útil para fallback)
|
|
39
|
+
# Formato: {"H2O": array(Nt,)}
|
|
40
|
+
hdf5_snowlines: Dict[str, np.ndarray]
|
|
41
|
+
|
|
42
|
+
@property
|
|
43
|
+
def Nt(self):
|
|
44
|
+
return len(self.times)
|
|
45
|
+
|
|
46
|
+
@property
|
|
47
|
+
def Nr(self):
|
|
48
|
+
return len(self.r)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def load_tripodpy_hdf5(datadir: str, M_star: float = 1.0, t_min_yr: float = 0.0) -> DiskData:
|
|
52
|
+
"""Convierte archivos HDF5 de tripodpy a un objeto DiskData."""
|
|
53
|
+
|
|
54
|
+
files = sorted(glob.glob(os.path.join(datadir, 'data*.hdf5')))
|
|
55
|
+
if not files:
|
|
56
|
+
raise FileNotFoundError(f"No HDF5 files found in {datadir}")
|
|
57
|
+
|
|
58
|
+
print(f"[load_tripodpy_hdf5] Reading {len(files)} snapshots from {datadir}...")
|
|
59
|
+
|
|
60
|
+
times_list, OmegaK_list = [], []
|
|
61
|
+
rsnow = {'H2O': []}
|
|
62
|
+
r_grid = None
|
|
63
|
+
|
|
64
|
+
gas_keys = ['Sigma', 'T', 'cs', 'eta', 'nu', 'alpha', 'Hp']
|
|
65
|
+
dust_keys = ['Sigma', 'v/rad', 'St', 'H']
|
|
66
|
+
gas_data = {k: [] for k in gas_keys}
|
|
67
|
+
dust_data = {k: [] for k in dust_keys}
|
|
68
|
+
|
|
69
|
+
for fpath in files:
|
|
70
|
+
with h5py.File(fpath, 'r') as f:
|
|
71
|
+
t_s = float(f['t'][()])
|
|
72
|
+
if t_s < t_min_yr * c.YEAR:
|
|
73
|
+
continue
|
|
74
|
+
|
|
75
|
+
times_list.append(t_s)
|
|
76
|
+
|
|
77
|
+
if r_grid is None:
|
|
78
|
+
r_grid = f['grid/r'][:]
|
|
79
|
+
|
|
80
|
+
# Cargar arrays
|
|
81
|
+
for k in gas_keys: gas_data[k].append(f[f'gas/{k}'][:])
|
|
82
|
+
for k in dust_keys: dust_data[k].append(f[f'dust/{k}'][:])
|
|
83
|
+
|
|
84
|
+
# OmegaK
|
|
85
|
+
if 'grid/OmegaK' in f:
|
|
86
|
+
OmegaK_list.append(f['grid/OmegaK'][:])
|
|
87
|
+
|
|
88
|
+
# Snowline dinámica de H2O original de la corrida
|
|
89
|
+
if 'dust/r_snow' in f:
|
|
90
|
+
rsnow['H2O'].append(float(f['dust/r_snow'][()]))
|
|
91
|
+
else:
|
|
92
|
+
rsnow_key = 'grid/rsnow_H2O'
|
|
93
|
+
rsnow['H2O'].append(float(f[rsnow_key][()]) if rsnow_key in f else np.nan)
|
|
94
|
+
|
|
95
|
+
times = np.array(times_list)
|
|
96
|
+
|
|
97
|
+
# OmegaK: 2D del HDF5 o analítico 1D
|
|
98
|
+
if OmegaK_list:
|
|
99
|
+
Omega_K = np.array(OmegaK_list)
|
|
100
|
+
else:
|
|
101
|
+
Omega_K = np.sqrt(c.G * M_star * c.M_SUN / r_grid**3)
|
|
102
|
+
|
|
103
|
+
rsnow['H2O'] = np.array(rsnow['H2O'])
|
|
104
|
+
|
|
105
|
+
return DiskData(
|
|
106
|
+
times=times,
|
|
107
|
+
r=r_grid,
|
|
108
|
+
gas_Sigma=np.array(gas_data['Sigma']),
|
|
109
|
+
gas_T=np.array(gas_data['T']),
|
|
110
|
+
gas_cs=np.array(gas_data['cs']),
|
|
111
|
+
gas_eta=np.array(gas_data['eta']),
|
|
112
|
+
gas_nu=np.array(gas_data['nu']),
|
|
113
|
+
gas_alpha=np.array(gas_data['alpha']),
|
|
114
|
+
gas_Hp=np.array(gas_data['Hp']),
|
|
115
|
+
dust_Sigma=np.array(dust_data['Sigma']),
|
|
116
|
+
dust_vr=np.array(dust_data['v/rad']),
|
|
117
|
+
dust_St=np.array(dust_data['St']),
|
|
118
|
+
dust_H=np.array(dust_data['H']),
|
|
119
|
+
Omega_K=Omega_K,
|
|
120
|
+
M_star=M_star,
|
|
121
|
+
hdf5_snowlines=rsnow
|
|
122
|
+
)
|