metablate 0.1.0__py3-none-any.whl
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.
- metablate/__init__.py +16 -0
- metablate/ablation_model.py +40 -0
- metablate/atmosphere/__init__.py +8 -0
- metablate/atmosphere/atm_msise00.py +67 -0
- metablate/atmosphere/atm_pymsis.py +114 -0
- metablate/atmosphere/atmosphere.py +80 -0
- metablate/coordinates.py +149 -0
- metablate/material.py +155 -0
- metablate/models/__init__.py +5 -0
- metablate/models/dimant_oppenheim_2017.py +293 -0
- metablate/models/kero_szasz_2008.py +348 -0
- metablate/ode.py +82 -0
- metablate/physics/__init__.py +5 -0
- metablate/physics/alpha_beta.py +679 -0
- metablate/physics/ballistic.py +104 -0
- metablate/physics/luminosity.py +78 -0
- metablate/physics/sputtering.py +219 -0
- metablate/physics/thermal_ablation.py +616 -0
- metablate/py.typed +0 -0
- metablate/version.py +3 -0
- metablate-0.1.0.dist-info/METADATA +766 -0
- metablate-0.1.0.dist-info/RECORD +25 -0
- metablate-0.1.0.dist-info/WHEEL +5 -0
- metablate-0.1.0.dist-info/licenses/LICENSE +674 -0
- metablate-0.1.0.dist-info/top_level.txt +1 -0
metablate/__init__.py
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
'''
|
|
2
|
+
# Meteoroid Ablation Models
|
|
3
|
+
'''
|
|
4
|
+
|
|
5
|
+
from .models import *
|
|
6
|
+
|
|
7
|
+
from . import physics
|
|
8
|
+
from . import models
|
|
9
|
+
from . import atmosphere
|
|
10
|
+
from . import material
|
|
11
|
+
|
|
12
|
+
from .atmosphere import Atmosphere
|
|
13
|
+
from .ablation_model import AblationModel
|
|
14
|
+
from .ode import ScipyODESolve
|
|
15
|
+
|
|
16
|
+
from .version import __version__
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"""The basic class structures for implementing a ablation model.
|
|
2
|
+
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from abc import abstractmethod
|
|
6
|
+
import logging
|
|
7
|
+
from configparser import ConfigParser
|
|
8
|
+
from typing import Union
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
logger = logging.getLogger(__name__)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class AblationModel:
|
|
15
|
+
"""Base-class for ablation models
|
|
16
|
+
|
|
17
|
+
The `run` method is expected to return the results from the ablation modelling
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
DEFAULT_CONFIG = {}
|
|
21
|
+
|
|
22
|
+
def __init__(self, config: Union[dict, str, Path, ConfigParser]):
|
|
23
|
+
self.config = ConfigParser(allow_no_value=True)
|
|
24
|
+
self.config.read_dict(self.DEFAULT_CONFIG)
|
|
25
|
+
|
|
26
|
+
if isinstance(config, ConfigParser):
|
|
27
|
+
for section in config.sections():
|
|
28
|
+
config.read_dict({
|
|
29
|
+
section: {key: val for key, val in config.items(section=section)}
|
|
30
|
+
})
|
|
31
|
+
elif isinstance(config, dict):
|
|
32
|
+
self.config.read_dict(config)
|
|
33
|
+
elif isinstance(config, (str, Path)):
|
|
34
|
+
self.config.read_file(config)
|
|
35
|
+
else:
|
|
36
|
+
raise TypeError(f"Unexpected config type: {type(config)}")
|
|
37
|
+
|
|
38
|
+
@abstractmethod
|
|
39
|
+
def run(self, *args, **kwargs):
|
|
40
|
+
pass
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
|
|
3
|
+
"""msis00
|
|
4
|
+
|
|
5
|
+
"""
|
|
6
|
+
import scipy.constants
|
|
7
|
+
from .atmosphere import Atmosphere
|
|
8
|
+
import numpy.typing as npt
|
|
9
|
+
|
|
10
|
+
try:
|
|
11
|
+
import msise00
|
|
12
|
+
except ImportError:
|
|
13
|
+
msise00 = None
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class AtmMSISE00(Atmosphere):
|
|
17
|
+
def __init__(self):
|
|
18
|
+
super().__init__(
|
|
19
|
+
supported_species=["He", "O", "N2", "O2", "Ar", "H", "N"],
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
def density(
|
|
23
|
+
self,
|
|
24
|
+
time: npt.ArrayLike,
|
|
25
|
+
lat: npt.ArrayLike,
|
|
26
|
+
lon: npt.ArrayLike,
|
|
27
|
+
alt: npt.ArrayLike,
|
|
28
|
+
f107: float = 80.0,
|
|
29
|
+
f107s: float = 80.0,
|
|
30
|
+
Ap: float = 4.0,
|
|
31
|
+
mass_densities: bool = False,
|
|
32
|
+
**kwargs
|
|
33
|
+
):
|
|
34
|
+
"""TODO: Write docstring
|
|
35
|
+
|
|
36
|
+
returns density in [m^-3]
|
|
37
|
+
|
|
38
|
+
"""
|
|
39
|
+
if msise00 is None:
|
|
40
|
+
raise ImportError("msis00 is not installed")
|
|
41
|
+
|
|
42
|
+
result = msise00.run(
|
|
43
|
+
time=time,
|
|
44
|
+
altkm=alt * 1e-3,
|
|
45
|
+
glat=lat,
|
|
46
|
+
glon=lon,
|
|
47
|
+
indices=dict(
|
|
48
|
+
f107=f107,
|
|
49
|
+
f107s=f107s,
|
|
50
|
+
Ap=Ap,
|
|
51
|
+
),
|
|
52
|
+
**kwargs
|
|
53
|
+
)
|
|
54
|
+
result["alt_km"] = result["alt_km"]*1e3
|
|
55
|
+
result = result.rename({
|
|
56
|
+
"alt_km": "alt",
|
|
57
|
+
"Tn": "Temperature",
|
|
58
|
+
})
|
|
59
|
+
result = result.transpose("time", "lon", "lat", "alt")
|
|
60
|
+
result.attrs["mass_densities"] = mass_densities
|
|
61
|
+
|
|
62
|
+
if mass_densities:
|
|
63
|
+
for symbol, s in self.species.items():
|
|
64
|
+
weight = scipy.constants.u * s.A
|
|
65
|
+
result[symbol] = result[symbol] * weight
|
|
66
|
+
|
|
67
|
+
return result
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
|
|
3
|
+
"""pymsis
|
|
4
|
+
|
|
5
|
+
"""
|
|
6
|
+
from typing import Union
|
|
7
|
+
import scipy.constants
|
|
8
|
+
from .atmosphere import Atmosphere
|
|
9
|
+
import numpy as np
|
|
10
|
+
import numpy.typing as npt
|
|
11
|
+
import xarray as xr
|
|
12
|
+
|
|
13
|
+
try:
|
|
14
|
+
from pymsis import msis
|
|
15
|
+
except ImportError:
|
|
16
|
+
msis = None
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class AtmPymsis(Atmosphere):
|
|
20
|
+
"""
|
|
21
|
+
# TODO: docstring
|
|
22
|
+
|
|
23
|
+
ndarray (ndates, nlons, nlats, nalts, 11) or (ndates, 11) |
|
|
24
|
+
The data calculated at each grid point:
|
|
25
|
+
| [Total mass density (kg/m3) | N2 # density (m-3), | O2 # density (m-3), |
|
|
26
|
+
O # density (m-3), | He # density (m-3), | H # density (m-3), |
|
|
27
|
+
Ar # density (m-3), | N # density (m-3), |
|
|
28
|
+
Anomalous oxygen # density (m-3), | NO # density (m-3),
|
|
29
|
+
| Temperature (K)]
|
|
30
|
+
"""
|
|
31
|
+
def __init__(self):
|
|
32
|
+
super().__init__(
|
|
33
|
+
supported_species=["He", "O", "N2", "O2", "Ar", "H", "N"],
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
def density(
|
|
37
|
+
self,
|
|
38
|
+
time: npt.ArrayLike,
|
|
39
|
+
lat: npt.ArrayLike,
|
|
40
|
+
lon: npt.ArrayLike,
|
|
41
|
+
alt: npt.ArrayLike,
|
|
42
|
+
f107: npt.ArrayLike = None,
|
|
43
|
+
f107s: npt.ArrayLike = None,
|
|
44
|
+
aps: npt.ArrayLike = None,
|
|
45
|
+
mass_densities: bool = False,
|
|
46
|
+
version: Union[float, str] = 2.1,
|
|
47
|
+
**kwargs
|
|
48
|
+
):
|
|
49
|
+
"""TODO: Write docstring
|
|
50
|
+
|
|
51
|
+
returns density in [m^-3]
|
|
52
|
+
|
|
53
|
+
"""
|
|
54
|
+
if msis is None:
|
|
55
|
+
raise ImportError("pymsis is not installed")
|
|
56
|
+
|
|
57
|
+
time = np.array(time) if not isinstance(time, np.ndarray) else time
|
|
58
|
+
lon = np.array(lon) if not isinstance(lon, np.ndarray) else lon
|
|
59
|
+
lat = np.array(lat) if not isinstance(lat, np.ndarray) else lat
|
|
60
|
+
alt = np.array(alt) if not isinstance(alt, np.ndarray) else alt
|
|
61
|
+
|
|
62
|
+
for inp in [time, lon, lat, alt]:
|
|
63
|
+
if len(inp.shape) == 0:
|
|
64
|
+
inp.shape = (1,)
|
|
65
|
+
|
|
66
|
+
result = msis.run(
|
|
67
|
+
dates=time,
|
|
68
|
+
lons=lon,
|
|
69
|
+
lats=lat,
|
|
70
|
+
alts=alt * 1e-3,
|
|
71
|
+
f107s=f107,
|
|
72
|
+
f107as=f107s,
|
|
73
|
+
aps=aps,
|
|
74
|
+
version=version,
|
|
75
|
+
**kwargs,
|
|
76
|
+
)
|
|
77
|
+
if len(result.shape) == 2:
|
|
78
|
+
result = result.reshape(result.shape[0], 1, 1, 1, result.shape[1])
|
|
79
|
+
|
|
80
|
+
result = xr.Dataset(
|
|
81
|
+
{
|
|
82
|
+
"Total": (["time", "lon", "lat", "alt"], result[:, :, :, :, 0]),
|
|
83
|
+
"N2": (["time", "lon", "lat", "alt"], result[:, :, :, :, 1]),
|
|
84
|
+
"O2": (["time", "lon", "lat", "alt"], result[:, :, :, :, 2]),
|
|
85
|
+
"O": (["time", "lon", "lat", "alt"], result[:, :, :, :, 3]),
|
|
86
|
+
"He": (["time", "lon", "lat", "alt"], result[:, :, :, :, 4]),
|
|
87
|
+
"H": (["time", "lon", "lat", "alt"], result[:, :, :, :, 5]),
|
|
88
|
+
"Ar": (["time", "lon", "lat", "alt"], result[:, :, :, :, 6]),
|
|
89
|
+
"N": (["time", "lon", "lat", "alt"], result[:, :, :, :, 7]),
|
|
90
|
+
"AnomalousO": (["time", "lon", "lat", "alt"], result[:, :, :, :, 8]),
|
|
91
|
+
"NO": (["time", "lon", "lat", "alt"], result[:, :, :, :, 9]),
|
|
92
|
+
"Temperature": (["time", "lon", "lat", "alt"], result[:, :, :, :, 10]),
|
|
93
|
+
},
|
|
94
|
+
coords={
|
|
95
|
+
"time": time,
|
|
96
|
+
"lon": lon,
|
|
97
|
+
"lat": lat,
|
|
98
|
+
"alt": alt,
|
|
99
|
+
},
|
|
100
|
+
attrs=dict(
|
|
101
|
+
f107 = f107,
|
|
102
|
+
f107s = f107s,
|
|
103
|
+
aps = aps,
|
|
104
|
+
mass_densities = mass_densities,
|
|
105
|
+
version = version,
|
|
106
|
+
)
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
if mass_densities:
|
|
110
|
+
for symbol, s in self.species.items():
|
|
111
|
+
weight = scipy.constants.u * s.A
|
|
112
|
+
result[symbol] = result[symbol] * weight
|
|
113
|
+
|
|
114
|
+
return result
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
from abc import abstractmethod
|
|
2
|
+
from dataclasses import dataclass
|
|
3
|
+
import logging
|
|
4
|
+
from copy import copy
|
|
5
|
+
|
|
6
|
+
import scipy.constants as constants
|
|
7
|
+
import numpy as np
|
|
8
|
+
import numpy.typing as npt
|
|
9
|
+
|
|
10
|
+
logger = logging.getLogger(__name__)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@dataclass
|
|
14
|
+
class Species:
|
|
15
|
+
symbol: str
|
|
16
|
+
A: float
|
|
17
|
+
Z: float
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
SPECIES = {
|
|
21
|
+
"He": Species(
|
|
22
|
+
symbol="He",
|
|
23
|
+
A=4.002602,
|
|
24
|
+
Z=2,
|
|
25
|
+
),
|
|
26
|
+
"O": Species(
|
|
27
|
+
symbol="O",
|
|
28
|
+
A=15.999,
|
|
29
|
+
Z=8,
|
|
30
|
+
),
|
|
31
|
+
"N2": Species(
|
|
32
|
+
symbol="N2",
|
|
33
|
+
A=14.007 * 2,
|
|
34
|
+
Z=14,
|
|
35
|
+
),
|
|
36
|
+
"O2": Species(
|
|
37
|
+
symbol="O2",
|
|
38
|
+
A=15.999 * 2,
|
|
39
|
+
Z=16,
|
|
40
|
+
),
|
|
41
|
+
"Ar": Species(
|
|
42
|
+
symbol="Ar",
|
|
43
|
+
A=39.948,
|
|
44
|
+
Z=18,
|
|
45
|
+
),
|
|
46
|
+
"H": Species(
|
|
47
|
+
symbol="H",
|
|
48
|
+
A=1.0079,
|
|
49
|
+
Z=1,
|
|
50
|
+
),
|
|
51
|
+
"N": Species(
|
|
52
|
+
symbol="N",
|
|
53
|
+
A=14.007,
|
|
54
|
+
Z=7,
|
|
55
|
+
),
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
# TODO: get a a hold of other atm models to figure out how to generalize interface better
|
|
60
|
+
class Atmosphere:
|
|
61
|
+
def __init__(self, supported_species):
|
|
62
|
+
self.species = {}
|
|
63
|
+
for s in supported_species:
|
|
64
|
+
if isinstance(s, Species):
|
|
65
|
+
self.species[s.symbol] = s
|
|
66
|
+
else:
|
|
67
|
+
self.species[s] = copy(SPECIES[s])
|
|
68
|
+
|
|
69
|
+
self.mean_mass = np.array([x.A for _, x in self.species.items()]).mean() * constants.u # [kg]
|
|
70
|
+
|
|
71
|
+
@abstractmethod
|
|
72
|
+
def density(
|
|
73
|
+
self,
|
|
74
|
+
time: npt.ArrayLike,
|
|
75
|
+
lat: npt.ArrayLike,
|
|
76
|
+
lon: npt.ArrayLike,
|
|
77
|
+
alt: npt.ArrayLike,
|
|
78
|
+
**kwargs
|
|
79
|
+
):
|
|
80
|
+
pass
|
metablate/coordinates.py
ADDED
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
"""
|
|
2
|
+
# Coordinate functions
|
|
3
|
+
"""
|
|
4
|
+
import logging
|
|
5
|
+
import numpy as np
|
|
6
|
+
|
|
7
|
+
logger = logging.getLogger(__name__)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
# Constants defined by the World Geodetic System 1984 (WGS84)
|
|
11
|
+
WGS84_a = 6378.137 * 1e3
|
|
12
|
+
WGS84_b = 6356.7523142 * 1e3
|
|
13
|
+
WGS84_esq = 6.69437999014 * 0.001
|
|
14
|
+
WGS84_e1sq = 6.73949674228 * 0.001
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def geodetic2ecef(lat, lon, alt):
|
|
18
|
+
"""Convert WGS84 geodetic coordinates to ECEF coordinates [^1].
|
|
19
|
+
|
|
20
|
+
[^1]: J. Zhu, "Conversion of Earth-centered Earth-fixed coordinates to geodetic coordinates,"
|
|
21
|
+
IEEE Transactions on Aerospace and Electronic Systems, vol. 30, pp. 957-961, 1994.
|
|
22
|
+
|
|
23
|
+
Parameters
|
|
24
|
+
----------
|
|
25
|
+
lat : float
|
|
26
|
+
Geographic latitude [deg]
|
|
27
|
+
lon : float
|
|
28
|
+
Geographic longitude [deg]
|
|
29
|
+
alt : float
|
|
30
|
+
Geographic altitude [deg]
|
|
31
|
+
|
|
32
|
+
Returns
|
|
33
|
+
-------
|
|
34
|
+
numpy.ndarray
|
|
35
|
+
[x, y, z] in ECEF coordinates
|
|
36
|
+
|
|
37
|
+
"""
|
|
38
|
+
lat, lon = np.radians(lat), np.radians(lon)
|
|
39
|
+
xi = np.sqrt(1 - WGS84_esq * np.sin(lat) ** 2)
|
|
40
|
+
x = (WGS84_a / xi + alt) * np.cos(lat) * np.cos(lon)
|
|
41
|
+
y = (WGS84_a / xi + alt) * np.cos(lat) * np.sin(lon)
|
|
42
|
+
z = (WGS84_a / xi * (1 - WGS84_esq) + alt) * np.sin(lat)
|
|
43
|
+
return np.array([x, y, z])
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def ecef2geodetic(x, y, z):
|
|
47
|
+
"""Convert ECEF coordinates to WGS84 geodetic coordinates [^1].
|
|
48
|
+
|
|
49
|
+
[^1]: J. Zhu, "Conversion of Earth-centered Earth-fixed coordinates to geodetic coordinates,"
|
|
50
|
+
IEEE Transactions on Aerospace and Electronic Systems, vol. 30, pp. 957-961, 1994.
|
|
51
|
+
|
|
52
|
+
Parameters
|
|
53
|
+
----------
|
|
54
|
+
x : float
|
|
55
|
+
Position along prime meridian [m]
|
|
56
|
+
y : float
|
|
57
|
+
Position along prime meridian + 90 degrees [m]
|
|
58
|
+
z : float
|
|
59
|
+
Position along earth rotation axis [m]
|
|
60
|
+
|
|
61
|
+
Returns
|
|
62
|
+
-------
|
|
63
|
+
numpy.ndarray
|
|
64
|
+
[lat [deg], lon [deg], alt [m]] in WGS84 geodetic coordinates
|
|
65
|
+
|
|
66
|
+
"""
|
|
67
|
+
r = np.sqrt(x * x + y * y)
|
|
68
|
+
if r < 1e-9:
|
|
69
|
+
h = np.abs(z) - WGS84_b
|
|
70
|
+
lat = np.sign(z) * np.pi / 2
|
|
71
|
+
lon = 0.0
|
|
72
|
+
else:
|
|
73
|
+
Esq = WGS84_a * WGS84_a - WGS84_b * WGS84_b
|
|
74
|
+
F = 54 * WGS84_b * WGS84_b * z * z
|
|
75
|
+
G = r * r + (1 - WGS84_esq) * z * z - WGS84_esq * Esq
|
|
76
|
+
C = (WGS84_esq * WGS84_esq * F * r * r) / (np.power(G, 3))
|
|
77
|
+
S = np.cbrt(1 + C + np.sqrt(C * C + 2 * C))
|
|
78
|
+
P = F / (3 * np.power((S + 1 / S + 1), 2) * G * G)
|
|
79
|
+
Q = np.sqrt(1 + 2 * WGS84_esq * WGS84_esq * P)
|
|
80
|
+
r_0 = -(P * WGS84_esq * r) / (1 + Q) + np.sqrt(
|
|
81
|
+
0.5 * WGS84_a * WGS84_a * (1 + 1.0 / Q)
|
|
82
|
+
- P * (1 - WGS84_esq) * z * z / (Q * (1 + Q))
|
|
83
|
+
- 0.5 * P * r * r
|
|
84
|
+
)
|
|
85
|
+
U = np.sqrt(np.power((r - WGS84_esq * r_0), 2) + z * z)
|
|
86
|
+
V = np.sqrt(np.power((r - WGS84_esq * r_0), 2) + (1 - WGS84_esq) * z * z)
|
|
87
|
+
Z_0 = WGS84_b * WGS84_b * z / (WGS84_a * V)
|
|
88
|
+
h = U * (1 - WGS84_b * WGS84_b / (WGS84_a * V))
|
|
89
|
+
lat = np.arctan((z + WGS84_e1sq * Z_0) / r)
|
|
90
|
+
lon = np.arctan2(y, x)
|
|
91
|
+
|
|
92
|
+
return np.array([np.degrees(lat), np.degrees(lon), h])
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def enu2ecef(lat, lon, alt, e, n, u):
|
|
96
|
+
"""NEU (north/east/up) to ECEF coordinate system conversion. Degrees are used."""
|
|
97
|
+
return ned2ecef(lat, lon, alt, n, e, -u)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def ned2ecef(lat, lon, alt, n, e, d):
|
|
101
|
+
"""NED (north/east/down) to ECEF coordinate system conversion. Degrees are used."""
|
|
102
|
+
lat, lon = np.radians(lat), np.radians(lon)
|
|
103
|
+
mx = np.array(
|
|
104
|
+
[
|
|
105
|
+
[-np.sin(lon), -np.sin(lat) * np.cos(lon), np.cos(lat) * np.cos(lon)],
|
|
106
|
+
[np.cos(lon), -np.sin(lat) * np.sin(lon), np.cos(lat) * np.sin(lon)],
|
|
107
|
+
[0, np.cos(lat), np.sin(lat)],
|
|
108
|
+
]
|
|
109
|
+
)
|
|
110
|
+
enu = np.array([e, n, -1.0 * d])
|
|
111
|
+
res = np.dot(mx, enu)
|
|
112
|
+
return res
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def ecef2ned(lat, lon, alt, x, y, z):
|
|
116
|
+
"""NED (east,north,up) from ECEF coordinate system conversion."""
|
|
117
|
+
lat, lon = np.radians(lat), np.radians(lon)
|
|
118
|
+
mx = np.array(
|
|
119
|
+
[
|
|
120
|
+
[-np.sin(lon), -np.sin(lat) * np.cos(lon), np.cos(lat) * np.cos(lon)],
|
|
121
|
+
[np.cos(lon), -np.sin(lat) * np.sin(lon), np.cos(lat) * np.sin(lon)],
|
|
122
|
+
[0, np.cos(lat), np.sin(lat)],
|
|
123
|
+
]
|
|
124
|
+
)
|
|
125
|
+
enu = np.array([x, y, z])
|
|
126
|
+
res = np.dot(np.linalg.inv(mx), enu)
|
|
127
|
+
return res
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def cart_to_azel(vec):
|
|
131
|
+
"""Convert from Cartesian coordinates to spherical in a degrees east of north and elevation fashion"""
|
|
132
|
+
x = vec[0]
|
|
133
|
+
y = vec[1]
|
|
134
|
+
z = vec[2]
|
|
135
|
+
r_ = np.sqrt(x**2 + y**2)
|
|
136
|
+
if r_ < 1e-9:
|
|
137
|
+
el = np.sign(z) * np.pi * 0.5
|
|
138
|
+
az = 0.0
|
|
139
|
+
else:
|
|
140
|
+
el = np.arctan(z / r_)
|
|
141
|
+
az = np.pi / 2 - np.arctan2(y, x)
|
|
142
|
+
return np.degrees(az), np.degrees(el), np.sqrt(x**2 + y**2 + z**2)
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def azel_to_cart(az, el, r):
|
|
146
|
+
"""Convert from spherical coordinates to Cartesian in a degrees east of north and elevation fashion"""
|
|
147
|
+
_az = np.radians(az)
|
|
148
|
+
_el = np.radians(el)
|
|
149
|
+
return r * np.array([np.sin(_az) * np.cos(_el), np.cos(_az) * np.cos(_el), np.sin(_el)])
|
metablate/material.py
ADDED
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
'''
|
|
2
|
+
# Meteoroid material physics
|
|
3
|
+
|
|
4
|
+
List of properties:
|
|
5
|
+
- `m2`: mean atomic mass [1]
|
|
6
|
+
- `u0`: surface binding energy [eV]
|
|
7
|
+
- `k`: average atomic number [1]
|
|
8
|
+
- `CA`, CB: Clausius-Clapeyron coeff [K]
|
|
9
|
+
- `mu`: mean molecular mass of ablated vapour [kg]
|
|
10
|
+
- `k`: Botzmann constant [J/K]
|
|
11
|
+
- `rho_m`: meteoroid density [kg/m3]
|
|
12
|
+
- `c`: specific heat of meteoroid [J/(K*kg)]
|
|
13
|
+
- `L`: latent heat of fusion + vapourization [J/kg]
|
|
14
|
+
|
|
15
|
+
# TODO: list references for material data
|
|
16
|
+
|
|
17
|
+
[Fe SiO2 C C] mean molecular mass per target atoms corresponding
|
|
18
|
+
to the different meteoroid densities, see Rogers p. 1344 values
|
|
19
|
+
are from Tielens et al.
|
|
20
|
+
|
|
21
|
+
Mean molecular mass of ablated vapor assuming that all products
|
|
22
|
+
liberated from the meteoroid by thermal ablation were released
|
|
23
|
+
in complete molecular form.
|
|
24
|
+
|
|
25
|
+
'''
|
|
26
|
+
from dataclasses import dataclass, asdict
|
|
27
|
+
import logging
|
|
28
|
+
from copy import copy
|
|
29
|
+
from scipy import constants
|
|
30
|
+
|
|
31
|
+
logger = logging.getLogger(__name__)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@dataclass
|
|
35
|
+
class Material:
|
|
36
|
+
"""Container for material properties
|
|
37
|
+
|
|
38
|
+
TODO: proper names for all vars
|
|
39
|
+
|
|
40
|
+
List of properties:
|
|
41
|
+
- `m2`: mean atomic mass [1]
|
|
42
|
+
- `u0`: surface binding energy [eV]
|
|
43
|
+
- `k`: average atomic number [1]
|
|
44
|
+
- `CA`, CB: Clausius-Clapeyron coeff [K]
|
|
45
|
+
- `mu`: mean molecular mass of ablated vapour [kg]
|
|
46
|
+
- `k`: Botzmann constant [J/K]
|
|
47
|
+
- `rho_m`: meteoroid density [kg/m3]
|
|
48
|
+
- `c`: specific heat of meteoroid [J/(K*kg)]
|
|
49
|
+
- `L`: latent heat of fusion + vapourization [J/kg]
|
|
50
|
+
"""
|
|
51
|
+
rho_m: float
|
|
52
|
+
mu: float
|
|
53
|
+
m2: float
|
|
54
|
+
CA: float
|
|
55
|
+
CB: float
|
|
56
|
+
u0: float
|
|
57
|
+
k: float
|
|
58
|
+
z2: float
|
|
59
|
+
c: float
|
|
60
|
+
L: float
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
MATERIALS = {}
|
|
64
|
+
|
|
65
|
+
iron = Material(
|
|
66
|
+
rho_m = 7800.0,
|
|
67
|
+
mu = 56.0*constants.u,
|
|
68
|
+
m2 = 56.0*constants.u,
|
|
69
|
+
CA = 10.6,
|
|
70
|
+
CB = 16120.,
|
|
71
|
+
u0 = 4.1,
|
|
72
|
+
k = 0.35,
|
|
73
|
+
z2 = 26,
|
|
74
|
+
c = 1200,
|
|
75
|
+
L = 6.0E6,
|
|
76
|
+
)
|
|
77
|
+
MATERIALS["iron"] = iron
|
|
78
|
+
|
|
79
|
+
asteroidal = Material(
|
|
80
|
+
rho_m = 3300.0,
|
|
81
|
+
mu = 50.0*constants.u,
|
|
82
|
+
m2 = 20.0*constants.u,
|
|
83
|
+
CA = 10.6,
|
|
84
|
+
CB = 13500.,
|
|
85
|
+
u0 = 6.4,
|
|
86
|
+
k = 0.1,
|
|
87
|
+
z2 = 10,
|
|
88
|
+
c = 1200,
|
|
89
|
+
L = 6.0E6,
|
|
90
|
+
)
|
|
91
|
+
MATERIALS["asteroidal"] = asteroidal
|
|
92
|
+
|
|
93
|
+
cometary = Material(
|
|
94
|
+
rho_m = 1000.0,
|
|
95
|
+
mu = 20.0*constants.u,
|
|
96
|
+
m2 = 12.0*constants.u,
|
|
97
|
+
CA = 10.6,
|
|
98
|
+
CB = 13500.,
|
|
99
|
+
u0 = 4.0,
|
|
100
|
+
k = 0.65,
|
|
101
|
+
z2 = 6,
|
|
102
|
+
c = 1200,
|
|
103
|
+
L = 6.0E6,
|
|
104
|
+
)
|
|
105
|
+
MATERIALS["cometary"] = cometary
|
|
106
|
+
|
|
107
|
+
porous = Material(
|
|
108
|
+
rho_m = 300.0,
|
|
109
|
+
mu = 20.0*constants.u,
|
|
110
|
+
m2 = 12.0*constants.u,
|
|
111
|
+
CA = 10.6,
|
|
112
|
+
CB = 13500.,
|
|
113
|
+
u0 = 4.0,
|
|
114
|
+
k = 0.65,
|
|
115
|
+
z2 = 6,
|
|
116
|
+
c = 1200,
|
|
117
|
+
L = 6.0E6,
|
|
118
|
+
)
|
|
119
|
+
MATERIALS["porous"] = porous
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def available():
|
|
123
|
+
'''List currently available meteoroid materials.
|
|
124
|
+
'''
|
|
125
|
+
return list(MATERIALS.keys())
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def get(material, as_dict=True):
|
|
129
|
+
'''Returns the physical parameters of the meteoroid based on its material.
|
|
130
|
+
|
|
131
|
+
# TODO: figure out if we keep as dict or keep as material???
|
|
132
|
+
|
|
133
|
+
Parameters
|
|
134
|
+
----------
|
|
135
|
+
material : str
|
|
136
|
+
name of material
|
|
137
|
+
as_dict : bool
|
|
138
|
+
Return data as dict instead of as a Material instance
|
|
139
|
+
|
|
140
|
+
Returns
|
|
141
|
+
-------
|
|
142
|
+
Material or dict
|
|
143
|
+
Dictionary of all available material parameters (float or None)
|
|
144
|
+
'''
|
|
145
|
+
|
|
146
|
+
_material = material.lower().strip()
|
|
147
|
+
if _material not in MATERIALS:
|
|
148
|
+
raise ValueError(
|
|
149
|
+
f'No data exists for material "{_material}"\n'
|
|
150
|
+
f'Avalible materials are: {available()}'
|
|
151
|
+
)
|
|
152
|
+
if as_dict:
|
|
153
|
+
return asdict(MATERIALS[_material])
|
|
154
|
+
else:
|
|
155
|
+
return copy(MATERIALS[_material])
|