metablate 0.3.0__tar.gz → 0.4.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.
Files changed (50) hide show
  1. {metablate-0.3.0 → metablate-0.4.0}/PKG-INFO +3 -1
  2. {metablate-0.3.0 → metablate-0.4.0}/pyproject.toml +3 -2
  3. {metablate-0.3.0 → metablate-0.4.0}/src/metablate/__init__.py +3 -5
  4. {metablate-0.3.0 → metablate-0.4.0}/src/metablate/atmosphere/atm_msise00.py +20 -13
  5. {metablate-0.3.0 → metablate-0.4.0}/src/metablate/atmosphere/atm_pymsis.py +36 -31
  6. {metablate-0.3.0 → metablate-0.4.0}/src/metablate/atmosphere/atmosphere.py +12 -13
  7. {metablate-0.3.0 → metablate-0.4.0}/src/metablate/material.py +32 -8
  8. {metablate-0.3.0 → metablate-0.4.0}/src/metablate/models/alpha_beta_2026.py +73 -59
  9. {metablate-0.3.0 → metablate-0.4.0}/src/metablate/models/dimant_oppenheim_2017.py +60 -39
  10. {metablate-0.3.0 → metablate-0.4.0}/src/metablate/models/kero_szasz_2008.py +16 -14
  11. {metablate-0.3.0 → metablate-0.4.0}/src/metablate/models/model.py +1 -1
  12. {metablate-0.3.0 → metablate-0.4.0}/src/metablate/models/simple_ablation_2026.py +60 -141
  13. {metablate-0.3.0 → metablate-0.4.0}/src/metablate/models/simple_motion.py +9 -8
  14. {metablate-0.3.0 → metablate-0.4.0}/src/metablate/models/stulov_mirskii_vislyi_1995.py +12 -11
  15. {metablate-0.3.0 → metablate-0.4.0}/src/metablate/physics/aerodynamics.py +20 -11
  16. {metablate-0.3.0 → metablate-0.4.0}/src/metablate/physics/alpha_beta.py +200 -140
  17. {metablate-0.3.0 → metablate-0.4.0}/src/metablate/physics/ballistic.py +60 -30
  18. {metablate-0.3.0 → metablate-0.4.0}/src/metablate/physics/luminosity.py +3 -1
  19. metablate-0.4.0/src/metablate/physics/plasma_production.py +281 -0
  20. {metablate-0.3.0 → metablate-0.4.0}/src/metablate/physics/spheres.py +4 -2
  21. {metablate-0.3.0 → metablate-0.4.0}/src/metablate/physics/sputtering.py +8 -3
  22. {metablate-0.3.0 → metablate-0.4.0}/src/metablate/physics/thermal_ablation.py +49 -50
  23. {metablate-0.3.0 → metablate-0.4.0}/src/metablate/types.py +30 -1
  24. {metablate-0.3.0 → metablate-0.4.0}/src/metablate.egg-info/PKG-INFO +3 -1
  25. {metablate-0.3.0 → metablate-0.4.0}/src/metablate.egg-info/SOURCES.txt +2 -0
  26. {metablate-0.3.0 → metablate-0.4.0}/src/metablate.egg-info/requires.txt +2 -0
  27. metablate-0.4.0/tests/test_ballistic.py +26 -0
  28. {metablate-0.3.0 → metablate-0.4.0}/tests/test_models.py +10 -1
  29. metablate-0.4.0/tests/test_plasma_production.py +25 -0
  30. {metablate-0.3.0 → metablate-0.4.0}/tests/test_sputtering.py +1 -1
  31. metablate-0.3.0/src/metablate/physics/plasma_production.py +0 -109
  32. {metablate-0.3.0 → metablate-0.4.0}/LICENSE +0 -0
  33. {metablate-0.3.0 → metablate-0.4.0}/README.md +0 -0
  34. {metablate-0.3.0 → metablate-0.4.0}/setup.cfg +0 -0
  35. {metablate-0.3.0 → metablate-0.4.0}/src/metablate/atmosphere/__init__.py +0 -0
  36. {metablate-0.3.0 → metablate-0.4.0}/src/metablate/models/__init__.py +0 -0
  37. {metablate-0.3.0 → metablate-0.4.0}/src/metablate/physics/__init__.py +0 -0
  38. {metablate-0.3.0 → metablate-0.4.0}/src/metablate/py.typed +0 -0
  39. {metablate-0.3.0 → metablate-0.4.0}/src/metablate/version.py +0 -0
  40. {metablate-0.3.0 → metablate-0.4.0}/src/metablate.egg-info/dependency_links.txt +0 -0
  41. {metablate-0.3.0 → metablate-0.4.0}/src/metablate.egg-info/top_level.txt +0 -0
  42. {metablate-0.3.0 → metablate-0.4.0}/tests/test_aerodynamics.py +0 -0
  43. {metablate-0.3.0 → metablate-0.4.0}/tests/test_alpha_beta.py +0 -0
  44. {metablate-0.3.0 → metablate-0.4.0}/tests/test_atmosphere.py +0 -0
  45. {metablate-0.3.0 → metablate-0.4.0}/tests/test_dimant_oppenheim.py +0 -0
  46. {metablate-0.3.0 → metablate-0.4.0}/tests/test_dimant_oppenheim_paper.py +0 -0
  47. {metablate-0.3.0 → metablate-0.4.0}/tests/test_dimant_oppenheim_points.py +0 -0
  48. {metablate-0.3.0 → metablate-0.4.0}/tests/test_import.py +0 -0
  49. {metablate-0.3.0 → metablate-0.4.0}/tests/test_material.py +0 -0
  50. {metablate-0.3.0 → metablate-0.4.0}/tests/test_thermal_ablation.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: metablate
3
- Version: 0.3.0
3
+ Version: 0.4.0
4
4
  Summary: A collection of meteor ablation models
5
5
  Author-email: Daniel Kastinen <daniel.kastinen@irf.se>, Johan Kero <kero@irf.se>
6
6
  Maintainer-email: Daniel Kastinen <daniel.kastinen@irf.se>
@@ -702,6 +702,7 @@ Requires-Dist: xarray
702
702
  Requires-Dist: spacecoords>0.2
703
703
  Provides-Extra: develop
704
704
  Requires-Dist: pytest; extra == "develop"
705
+ Requires-Dist: mypy; extra == "develop"
705
706
  Requires-Dist: flake8; extra == "develop"
706
707
  Requires-Dist: wheel; extra == "develop"
707
708
  Requires-Dist: build; extra == "develop"
@@ -724,6 +725,7 @@ Requires-Dist: mkdocs-section-index; extra == "develop"
724
725
  Provides-Extra: tests
725
726
  Requires-Dist: pytest; extra == "tests"
726
727
  Requires-Dist: pytest-cov; extra == "tests"
728
+ Requires-Dist: mypy; extra == "tests"
727
729
  Provides-Extra: visualization
728
730
  Requires-Dist: pyvista; extra == "visualization"
729
731
  Dynamic: license-file
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "metablate"
7
- version = "0.3.0"
7
+ version = "0.4.0"
8
8
  description = "A collection of meteor ablation models"
9
9
  readme = {file = "README.md", content-type = "text/markdown"}
10
10
  authors = [
@@ -45,6 +45,7 @@ where = ["src"]
45
45
  [project.optional-dependencies]
46
46
  develop = [
47
47
  "pytest",
48
+ "mypy",
48
49
  "flake8",
49
50
  "wheel",
50
51
  "build",
@@ -65,7 +66,7 @@ develop = [
65
66
  "mkdocs-literate-nav",
66
67
  "mkdocs-section-index",
67
68
  ]
68
- tests = ["pytest", "pytest-cov"]
69
+ tests = ["pytest", "pytest-cov", "mypy"]
69
70
  visualization = ["pyvista"]
70
71
 
71
72
 
@@ -1,19 +1,17 @@
1
1
  """
2
2
  # Meteoroid Ablation Models
3
3
  """
4
+ from __future__ import annotations
4
5
 
5
6
  import logging
6
7
  import sys
7
8
 
8
- from . import physics
9
- from . import models
10
- from . import material
11
- from . import atmosphere
9
+ from . import atmosphere, material, models, physics
12
10
  from .atmosphere import Atmosphere
13
11
  from .version import __version__
14
12
 
15
13
 
16
- def setup_logging(level=logging.INFO):
14
+ def setup_logging(level: int | str = logging.INFO) -> logging.Logger:
17
15
  logger = logging.getLogger("metablate")
18
16
  logger.setLevel(level)
19
17
 
@@ -1,37 +1,44 @@
1
- #!/usr/bin/env python
2
-
3
1
  """msis00
4
2
 
5
3
  """
4
+ from __future__ import annotations
5
+
6
+ from importlib import import_module
7
+ from types import ModuleType
8
+ from typing import Any
9
+
10
+ import numpy as np
6
11
  import scipy.constants
7
12
  import xarray as xr
8
- from .atmosphere import Atmosphere
9
- from ..types import NDArray_N
10
13
 
14
+ from ..types import ArrayLike
15
+ from .atmosphere import Atmosphere
11
16
 
17
+ msise00: ModuleType | None
12
18
  try:
13
- import msise00
19
+ msise00 = import_module("msise00")
14
20
  except ImportError:
15
21
  msise00 = None
16
22
 
17
23
 
18
24
  class AtmMSISE00(Atmosphere):
19
- def __init__(self):
25
+
26
+ def __init__(self) -> None:
20
27
  super().__init__(
21
28
  supported_species=["He", "O", "N2", "O2", "Ar", "H", "N"],
22
29
  )
23
30
 
24
31
  def density(
25
32
  self,
26
- time: NDArray_N,
27
- lat: NDArray_N,
28
- lon: NDArray_N,
29
- alt: NDArray_N,
33
+ time: ArrayLike,
34
+ lat: ArrayLike,
35
+ lon: ArrayLike,
36
+ alt: ArrayLike,
30
37
  f107: float = 80.0,
31
38
  f107s: float = 80.0,
32
39
  Ap: float = 4.0,
33
40
  mass_densities: bool = False,
34
- **kwargs
41
+ **kwargs: Any,
35
42
  ) -> xr.Dataset:
36
43
  """TODO: Write docstring
37
44
 
@@ -43,7 +50,7 @@ class AtmMSISE00(Atmosphere):
43
50
 
44
51
  result = msise00.run(
45
52
  time=time,
46
- altkm=alt * 1e-3,
53
+ altkm=np.asarray(alt) * 1e-3,
47
54
  glat=lat,
48
55
  glon=lon,
49
56
  indices=dict(
@@ -51,7 +58,7 @@ class AtmMSISE00(Atmosphere):
51
58
  f107s=f107s,
52
59
  Ap=Ap,
53
60
  ),
54
- **kwargs
61
+ **kwargs,
55
62
  )
56
63
  result["alt_km"] = result["alt_km"]*1e3
57
64
  result = result.rename({
@@ -1,15 +1,19 @@
1
- #!/usr/bin/env python
2
-
3
1
  """pymsis
4
2
 
5
3
  """
6
- from typing import Union
7
- import scipy.constants
8
- from .atmosphere import Atmosphere
4
+ from __future__ import annotations
5
+
6
+ from types import ModuleType
7
+ from typing import Any
8
+
9
9
  import numpy as np
10
- import numpy.typing as npt
10
+ import scipy.constants
11
11
  import xarray as xr
12
12
 
13
+ from ..types import ArrayLike, AtmosphereData
14
+ from .atmosphere import Atmosphere
15
+
16
+ msis: ModuleType | None
13
17
  try:
14
18
  from pymsis import msis
15
19
  except ImportError:
@@ -28,25 +32,25 @@ class AtmPymsis(Atmosphere):
28
32
  Anomalous oxygen # density (m-3), | NO # density (m-3),
29
33
  | Temperature (K)]
30
34
  """
31
- def __init__(self):
35
+ def __init__(self) -> None:
32
36
  super().__init__(
33
37
  supported_species=["He", "O", "N2", "O2", "Ar", "H", "N"],
34
38
  )
35
39
 
36
40
  def density(
37
41
  self,
38
- time: npt.ArrayLike,
39
- lat: npt.ArrayLike,
40
- lon: npt.ArrayLike,
41
- alt: npt.ArrayLike,
42
- f107: npt.ArrayLike | None = None,
43
- f107s: npt.ArrayLike | None = None,
44
- aps: npt.ArrayLike | None = None,
42
+ time: ArrayLike,
43
+ lat: ArrayLike,
44
+ lon: ArrayLike,
45
+ alt: ArrayLike,
46
+ f107: ArrayLike | None = None,
47
+ f107s: ArrayLike | None = None,
48
+ aps: ArrayLike | None = None,
45
49
  mass_densities: bool = False,
46
- version: Union[float, str] = 2.1,
50
+ version: float | str = 2.1,
47
51
  as_dataset: bool = True,
48
- **kwargs
49
- ) -> xr.Dataset | dict[str, xr.Variable]:
52
+ **kwargs: Any,
53
+ ) -> AtmosphereData:
50
54
  """Return species number densities [m^-3], or mass densities [kg/m^3].
51
55
 
52
56
  Set ``as_dataset=False`` in an ablation model's ``atmosphere_kwargs``
@@ -66,7 +70,7 @@ class AtmPymsis(Atmosphere):
66
70
  if len(inp.shape) == 0:
67
71
  inp.shape = (1,)
68
72
 
69
- result = msis.run(
73
+ data = msis.run(
70
74
  dates=time,
71
75
  lons=lon,
72
76
  lats=lat,
@@ -77,22 +81,23 @@ class AtmPymsis(Atmosphere):
77
81
  version=version,
78
82
  **kwargs,
79
83
  )
80
- if len(result.shape) == 2:
81
- result = result.reshape(result.shape[0], 1, 1, 1, result.shape[1])
84
+ if len(data.shape) == 2:
85
+ data = data.reshape(data.shape[0], 1, 1, 1, data.shape[1])
82
86
 
83
87
  variables = {
84
- "Total": (["time", "lon", "lat", "alt"], result[:, :, :, :, 0]),
85
- "N2": (["time", "lon", "lat", "alt"], result[:, :, :, :, 1]),
86
- "O2": (["time", "lon", "lat", "alt"], result[:, :, :, :, 2]),
87
- "O": (["time", "lon", "lat", "alt"], result[:, :, :, :, 3]),
88
- "He": (["time", "lon", "lat", "alt"], result[:, :, :, :, 4]),
89
- "H": (["time", "lon", "lat", "alt"], result[:, :, :, :, 5]),
90
- "Ar": (["time", "lon", "lat", "alt"], result[:, :, :, :, 6]),
91
- "N": (["time", "lon", "lat", "alt"], result[:, :, :, :, 7]),
92
- "AnomalousO": (["time", "lon", "lat", "alt"], result[:, :, :, :, 8]),
93
- "NO": (["time", "lon", "lat", "alt"], result[:, :, :, :, 9]),
94
- "Temperature": (["time", "lon", "lat", "alt"], result[:, :, :, :, 10]),
88
+ "Total": (["time", "lon", "lat", "alt"], data[:, :, :, :, 0]),
89
+ "N2": (["time", "lon", "lat", "alt"], data[:, :, :, :, 1]),
90
+ "O2": (["time", "lon", "lat", "alt"], data[:, :, :, :, 2]),
91
+ "O": (["time", "lon", "lat", "alt"], data[:, :, :, :, 3]),
92
+ "He": (["time", "lon", "lat", "alt"], data[:, :, :, :, 4]),
93
+ "H": (["time", "lon", "lat", "alt"], data[:, :, :, :, 5]),
94
+ "Ar": (["time", "lon", "lat", "alt"], data[:, :, :, :, 6]),
95
+ "N": (["time", "lon", "lat", "alt"], data[:, :, :, :, 7]),
96
+ "AnomalousO": (["time", "lon", "lat", "alt"], data[:, :, :, :, 8]),
97
+ "NO": (["time", "lon", "lat", "alt"], data[:, :, :, :, 9]),
98
+ "Temperature": (["time", "lon", "lat", "alt"], data[:, :, :, :, 10]),
95
99
  }
100
+ result: AtmosphereData
96
101
  if as_dataset:
97
102
  result = xr.Dataset(
98
103
  variables,
@@ -1,12 +1,15 @@
1
- from abc import abstractmethod
2
- from dataclasses import dataclass
1
+ from __future__ import annotations
2
+
3
3
  import logging
4
+ from abc import abstractmethod
4
5
  from copy import copy
6
+ from dataclasses import dataclass
7
+ from typing import Any
5
8
 
6
- import scipy.constants as constants
7
9
  import numpy as np
8
- import xarray as xr
9
- from ..types import NDArray_N
10
+ from scipy import constants
11
+
12
+ from ..types import ArrayLike, AtmosphereData
10
13
 
11
14
  logger = logging.getLogger(__name__)
12
15
 
@@ -59,7 +62,8 @@ SPECIES = {
59
62
 
60
63
  # TODO: get a a hold of other atm models to figure out how to generalize interface better
61
64
  class Atmosphere:
62
- def __init__(self, supported_species: list[Species] | list[str]):
65
+
66
+ def __init__(self, supported_species: list[Species] | list[str]) -> None:
63
67
  self.species = {}
64
68
  for s in supported_species:
65
69
  if isinstance(s, Species):
@@ -71,11 +75,6 @@ class Atmosphere:
71
75
 
72
76
  @abstractmethod
73
77
  def density(
74
- self,
75
- time: np.datetime64,
76
- lat: NDArray_N,
77
- lon: NDArray_N,
78
- alt: NDArray_N,
79
- **kwargs
80
- ) -> xr.Dataset:
78
+ self, time: ArrayLike, lat: ArrayLike, lon: ArrayLike, alt: ArrayLike, **kwargs: Any
79
+ ) -> AtmosphereData:
81
80
  pass
@@ -10,16 +10,24 @@ liberated from the meteoroid by thermal ablation were released
10
10
  in complete molecular form.
11
11
 
12
12
  """
13
+ from __future__ import annotations
13
14
 
14
- from dataclasses import asdict
15
15
  import logging
16
- from copy import copy
16
+ from copy import deepcopy
17
+ from dataclasses import asdict
18
+ from typing import Literal, overload
19
+
17
20
  from scipy import constants
18
- from .types import Material
21
+
22
+ from .types import Material, MaterialDict
19
23
 
20
24
  logger = logging.getLogger(__name__)
21
25
 
22
- MATERIALS = {}
26
+ MATERIALS: dict[str, Material] = {}
27
+
28
+ # Jones (1997), Table 1: abundances by atom number. As in the original
29
+ # ionization model
30
+ JONES_COMPOSITION = {"O": 0.617, "Fe": 0.059, "Mg": 0.082, "Si": 0.242}
23
31
 
24
32
  iron = Material(
25
33
  bulk_density=7800.0,
@@ -32,6 +40,7 @@ iron = Material(
32
40
  penetration_correction_parameter_K=0.35,
33
41
  clausius_clapeyron_coeff_A=10.6,
34
42
  clausius_clapeyron_coeff_B=16120.0,
43
+ atomic_composition={"Fe": 1.0},
35
44
  )
36
45
  MATERIALS["iron"] = iron
37
46
 
@@ -46,6 +55,7 @@ asteroidal = Material(
46
55
  penetration_correction_parameter_K=0.1,
47
56
  clausius_clapeyron_coeff_A=10.6,
48
57
  clausius_clapeyron_coeff_B=13500.0,
58
+ atomic_composition={}, # TODO: what should this be?
49
59
  )
50
60
  MATERIALS["asteroidal"] = asteroidal
51
61
 
@@ -60,6 +70,7 @@ cometary = Material(
60
70
  penetration_correction_parameter_K=0.65,
61
71
  clausius_clapeyron_coeff_A=10.6,
62
72
  clausius_clapeyron_coeff_B=13500.0,
73
+ atomic_composition=JONES_COMPOSITION.copy(),
63
74
  )
64
75
  MATERIALS["cometary"] = cometary
65
76
 
@@ -74,16 +85,29 @@ porous = Material(
74
85
  penetration_correction_parameter_K=0.65,
75
86
  clausius_clapeyron_coeff_A=10.6,
76
87
  clausius_clapeyron_coeff_B=13500.0,
88
+ atomic_composition=JONES_COMPOSITION.copy(),
77
89
  )
78
90
  MATERIALS["porous"] = porous
79
91
 
80
92
 
81
- def available():
93
+ def available() -> list[str]:
82
94
  """List currently available meteoroid materials."""
83
95
  return list(MATERIALS.keys())
84
96
 
85
97
 
86
- def get(material, as_dict=True):
98
+ @overload
99
+ def get(material: str, as_dict: Literal[True] = True) -> MaterialDict: ...
100
+
101
+
102
+ @overload
103
+ def get(material: str, as_dict: Literal[False]) -> Material: ...
104
+
105
+
106
+ @overload
107
+ def get(material: str, as_dict: bool) -> Material | MaterialDict: ...
108
+
109
+
110
+ def get(material: str, as_dict: bool = True) -> Material | MaterialDict:
87
111
  """Returns the physical parameters of the meteoroid based on its material.
88
112
 
89
113
  Parameters
@@ -96,7 +120,7 @@ def get(material, as_dict=True):
96
120
  Returns
97
121
  -------
98
122
  Material or dict
99
- Dictionary of all available material parameters (float or None)
123
+ Material properties, including the optional atomic composition.
100
124
  """
101
125
 
102
126
  _material = material.lower().strip()
@@ -107,4 +131,4 @@ def get(material, as_dict=True):
107
131
  if as_dict:
108
132
  return asdict(MATERIALS[_material])
109
133
  else:
110
- return copy(MATERIALS[_material])
134
+ return deepcopy(MATERIALS[_material])
@@ -5,7 +5,6 @@ from __future__ import annotations
5
5
  import logging
6
6
  import time
7
7
  from dataclasses import dataclass, field
8
- from typing import Any
9
8
 
10
9
  import numpy as np
11
10
  import scipy.interpolate as sci
@@ -16,10 +15,15 @@ from scipy.integrate import solve_ivp
16
15
  from .. import physics
17
16
  from ..atmosphere import Atmosphere
18
17
  from ..types import (
18
+ Bounds,
19
+ Kwargs,
20
+ NDArray,
19
21
  NDArray_2xN,
20
22
  NDArray_N,
23
+ Numeric,
21
24
  Options,
22
25
  Results,
26
+ StateVector,
23
27
  )
24
28
  from .model import MeteorModel
25
29
 
@@ -34,9 +38,9 @@ class AlphaBetaOptions(Options):
34
38
  max_time: float = 100.0 # [s]
35
39
  target_normalized_velocity: float | None = None # [1]
36
40
  method: str = "RK45"
37
- method_kwargs: dict[str, Any] = field(default_factory=lambda: dict())
41
+ method_kwargs: Kwargs = field(default_factory=dict)
38
42
  atmosphere: Atmosphere | None = None
39
- atmosphere_kwargs: dict[str, Any] = field(default_factory=lambda: dict())
43
+ atmosphere_kwargs: Kwargs = field(default_factory=dict)
40
44
  atmospheric_scale_height: float = 7610.0
41
45
 
42
46
 
@@ -62,24 +66,24 @@ class AlphaBetaResults(Results):
62
66
 
63
67
 
64
68
  def dmdt(
65
- v: NDArray_N | float,
66
- a: NDArray_N | float,
67
- beta: NDArray_N | float,
68
- mu: NDArray_N | float,
69
- ) -> NDArray_N | float:
69
+ v: Numeric,
70
+ a: Numeric,
71
+ beta: Numeric,
72
+ mu: Numeric,
73
+ ) -> Numeric:
70
74
  return 2 * beta * v * a / (1 - mu) * np.exp(-beta * (1 - v**2) / (1 - mu))
71
75
 
72
76
 
73
77
  def diff_eq_rhs(
74
78
  t: float,
75
- params: list[NDArray_N | float],
79
+ params: StateVector,
76
80
  alpha: float,
77
81
  beta: float,
78
82
  gamma: float,
79
83
  entry_velocity: float,
80
84
  atmospheric_scale_height: float,
81
85
  ) -> NDArray_N | NDArray_2xN:
82
- s, v = params
86
+ _, v = params
83
87
  delta = scs.expi(beta) - scs.expi(beta * v**2)
84
88
  coef = entry_velocity / atmospheric_scale_height
85
89
  return np.array(
@@ -91,7 +95,8 @@ def diff_eq_rhs(
91
95
 
92
96
 
93
97
  class AlphaBeta2026(MeteorModel[AlphaBetaInitialState, AlphaBetaOptions, AlphaBetaResults]):
94
- def __init__(self, options: AlphaBetaOptions = AlphaBetaOptions()):
98
+
99
+ def __init__(self, options: AlphaBetaOptions = AlphaBetaOptions()) -> None:
95
100
  self.options = options
96
101
  if options.atmosphere is not None:
97
102
  raise NotImplementedError(
@@ -133,12 +138,12 @@ class AlphaBeta2026(MeteorModel[AlphaBetaInitialState, AlphaBetaOptions, AlphaBe
133
138
  s0 = 0
134
139
  y0 = np.array([s0, v0_norm])
135
140
 
136
- def _low_velocity(t: float, y: NDArray_N, *args: tuple[Any]) -> float:
141
+ def _low_velocity(t: float, y: NDArray_N, *args: object) -> float:
137
142
  res = y[1] / v_final_norm - 1
138
143
  return res
139
144
 
140
- _low_velocity.terminal = True # type: ignore
141
- _low_velocity.direction = -1 # type: ignore
145
+ _low_velocity.terminal = True # type: ignore[attr-defined]
146
+ _low_velocity.direction = -1 # type: ignore[attr-defined]
142
147
 
143
148
  events = [_low_velocity]
144
149
 
@@ -179,40 +184,46 @@ class AlphaBeta2026(MeteorModel[AlphaBetaInitialState, AlphaBetaOptions, AlphaBe
179
184
  t=ivp_result.t,
180
185
  distance=s_vals,
181
186
  velocity=v_norm * parameters.entry_velocity,
182
- height=physics.alpha_beta.height_direct(
183
- v_norm,
184
- self.options.atmospheric_scale_height,
185
- parameters.alpha,
186
- parameters.beta,
187
+ height=np.asarray(
188
+ physics.alpha_beta.height_direct(
189
+ v_norm,
190
+ self.options.atmospheric_scale_height,
191
+ parameters.alpha,
192
+ parameters.beta,
193
+ )
187
194
  ),
188
- relative_mass=physics.alpha_beta.norm_mass_direct(
189
- v_norm,
190
- parameters.beta,
191
- parameters.shape_change_coefficient,
195
+ relative_mass=np.asarray(
196
+ physics.alpha_beta.norm_mass_direct(
197
+ v_norm,
198
+ parameters.beta,
199
+ parameters.shape_change_coefficient,
200
+ )
192
201
  ),
193
- massloss=dmdt(
194
- v_norm,
195
- diffs[1, :],
196
- parameters.beta,
197
- parameters.shape_change_coefficient,
202
+ massloss=np.asarray(
203
+ dmdt(
204
+ v_norm,
205
+ diffs[1, :],
206
+ parameters.beta,
207
+ parameters.shape_change_coefficient,
208
+ )
198
209
  ),
199
210
  )
200
211
 
201
212
 
202
213
  def logposterior_alpha_beta(
203
- alpha,
204
- beta,
205
- heights,
206
- heights_std,
207
- velocities,
208
- velocities_std,
209
- t_eval,
210
- initial_height,
211
- entry_velocity,
212
- entry_angle,
213
- shape_change_coefficient,
214
- model,
215
- ):
214
+ alpha: float,
215
+ beta: float,
216
+ heights: NDArray_N,
217
+ heights_std: Numeric,
218
+ velocities: NDArray_N,
219
+ velocities_std: Numeric,
220
+ t_eval: NDArray_N,
221
+ initial_height: float,
222
+ entry_velocity: float,
223
+ entry_angle: float,
224
+ shape_change_coefficient: float,
225
+ model: AlphaBeta2026,
226
+ ) -> float:
216
227
  p = AlphaBetaInitialState(
217
228
  epoch=None,
218
229
  alpha=alpha,
@@ -243,19 +254,19 @@ def logposterior_alpha_beta(
243
254
 
244
255
 
245
256
  def solve_alpha_beta_posterior(
246
- t,
247
- heights,
248
- heights_std,
249
- velocities,
250
- velocities_std,
251
- entry_angle,
252
- shape_change_coefficient,
253
- entry_velocity=None,
254
- options=None,
255
- start=None,
256
- bounds=((0.01, 1e8), (0.5e-1, 200.0), (0.0, None)),
257
- minimize_kwargs={},
258
- ):
257
+ t: NDArray_N,
258
+ heights: NDArray_N,
259
+ heights_std: Numeric,
260
+ velocities: NDArray_N,
261
+ velocities_std: Numeric,
262
+ entry_angle: float,
263
+ shape_change_coefficient: float,
264
+ entry_velocity: float | None = None,
265
+ options: AlphaBetaOptions | None = None,
266
+ start: NDArray | list[float | None] | None = None,
267
+ bounds: Bounds = ((0.01, 1e8), (0.5e-1, 200.0), (0.0, None)),
268
+ minimize_kwargs: Kwargs = {},
269
+ ) -> sco.OptimizeResult:
259
270
  """Solve for alpha and beta (and optionally velocity) using minimization of the posterior [^1].
260
271
 
261
272
  [^1]: Kastinen et al (2026) in preparation
@@ -272,10 +283,12 @@ def solve_alpha_beta_posterior(
272
283
  if start is None:
273
284
  start = [a0, b0, v0]
274
285
  else:
275
- if start[1] is None:
276
- start[1] = b0
286
+ beta_start = start[1]
287
+ if beta_start is None:
288
+ beta_start = b0
289
+ start[1] = beta_start
277
290
  if start[0] is None:
278
- start[0] = np.exp(Yvalues[-1]) / (2.0 * start[1])
291
+ start[0] = np.exp(Yvalues[-1]) / (2.0 * beta_start)
279
292
  if start[2] is None:
280
293
  start[2] = v0
281
294
 
@@ -286,7 +299,7 @@ def solve_alpha_beta_posterior(
286
299
  start = start[:2]
287
300
  bounds = (bounds[0], bounds[1])
288
301
 
289
- def wrap_func(x):
302
+ def wrap_func(x: NDArray_N) -> float:
290
303
  return -logposterior_alpha_beta(
291
304
  x[0],
292
305
  x[1],
@@ -301,9 +314,10 @@ def solve_alpha_beta_posterior(
301
314
  shape_change_coefficient,
302
315
  model,
303
316
  )
317
+
304
318
  else:
305
319
 
306
- def wrap_func(x):
320
+ def wrap_func(x: NDArray_N) -> float:
307
321
  return -logposterior_alpha_beta(
308
322
  x[0],
309
323
  x[1],