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 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
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) [![Tests](https://github.com/mmmaxii/PA3Py/actions/workflows/tests.yml/badge.svg)](https://github.com/mmmaxii/PA3Py/actions) [![Documentation Status](https://readthedocs.org/projects/pa3py/badge/?version=latest)](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
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) [![Tests](https://github.com/mmmaxii/PA3Py/actions/workflows/tests.yml/badge.svg)](https://github.com/mmmaxii/PA3Py/actions) [![Documentation Status](https://readthedocs.org/projects/pa3py/badge/?version=latest)](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,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -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
+ )