stellar-spice 0.3__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.
Potentially problematic release.
This version of stellar-spice might be problematic. Click here for more details.
- stellar_spice-0.3/PKG-INFO +40 -0
- stellar_spice-0.3/README.md +22 -0
- stellar_spice-0.3/pyproject.toml +34 -0
- stellar_spice-0.3/setup.cfg +4 -0
- stellar_spice-0.3/src/spice/__init__.py +0 -0
- stellar_spice-0.3/src/spice/geometry/__init__.py +2 -0
- stellar_spice-0.3/src/spice/geometry/sutherland_hodgman.py +142 -0
- stellar_spice-0.3/src/spice/geometry/utils.py +167 -0
- stellar_spice-0.3/src/spice/models/__init__.py +5 -0
- stellar_spice-0.3/src/spice/models/binary.py +123 -0
- stellar_spice-0.3/src/spice/models/mesh_generation.py +214 -0
- stellar_spice-0.3/src/spice/models/mesh_model.py +133 -0
- stellar_spice-0.3/src/spice/models/mesh_transform.py +93 -0
- stellar_spice-0.3/src/spice/models/mesh_view.py +187 -0
- stellar_spice-0.3/src/spice/models/orbit_utils.py +90 -0
- stellar_spice-0.3/src/spice/models/phoebe_utils.py +81 -0
- stellar_spice-0.3/src/spice/models/spots.py +87 -0
- stellar_spice-0.3/src/spice/models/utils.py +125 -0
- stellar_spice-0.3/src/spice/phoebe_model/DI-Her-meshes.py +135 -0
- stellar_spice-0.3/src/spice/plots/__init__.py +1 -0
- stellar_spice-0.3/src/spice/plots/plot_mesh.py +323 -0
- stellar_spice-0.3/src/spice/spectrum/__init__.py +2 -0
- stellar_spice-0.3/src/spice/spectrum/filter.py +180 -0
- stellar_spice-0.3/src/spice/spectrum/filter_data/__init__.py +0 -0
- stellar_spice-0.3/src/spice/spectrum/planck_law.py +57 -0
- stellar_spice-0.3/src/spice/spectrum/spectrum.py +265 -0
- stellar_spice-0.3/src/spice/spectrum/spectrum_korg.py +201 -0
- stellar_spice-0.3/src/spice/spectrum/spectrum_transformer.py +148 -0
- stellar_spice-0.3/src/spice/spectrum/utils.py +38 -0
- stellar_spice-0.3/src/spice/utils/__init__.py +1 -0
- stellar_spice-0.3/src/spice/utils/warnings.py +6 -0
- stellar_spice-0.3/src/stellar_spice.egg-info/PKG-INFO +40 -0
- stellar_spice-0.3/src/stellar_spice.egg-info/SOURCES.txt +34 -0
- stellar_spice-0.3/src/stellar_spice.egg-info/dependency_links.txt +1 -0
- stellar_spice-0.3/src/stellar_spice.egg-info/requires.txt +15 -0
- stellar_spice-0.3/src/stellar_spice.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: stellar-spice
|
|
3
|
+
Version: 0.3
|
|
4
|
+
Description-Content-Type: text/markdown
|
|
5
|
+
Requires-Dist: celluloid~=0.2.0
|
|
6
|
+
Requires-Dist: jax~=0.4.24
|
|
7
|
+
Requires-Dist: jaxlib~=0.4.24
|
|
8
|
+
Requires-Dist: numpy~=1.26.4
|
|
9
|
+
Requires-Dist: flax~=0.8.1
|
|
10
|
+
Requires-Dist: joblib~=1.3.2
|
|
11
|
+
Requires-Dist: transformer-payne~=0.5
|
|
12
|
+
Provides-Extra: huggingface
|
|
13
|
+
Requires-Dist: huggingface-hub~=0.21.3; extra == "huggingface"
|
|
14
|
+
Provides-Extra: dev
|
|
15
|
+
Requires-Dist: pytest~=8.0.0; extra == "dev"
|
|
16
|
+
Requires-Dist: pytest-datadir~=1.5.0; extra == "dev"
|
|
17
|
+
Requires-Dist: coverage~=7.4.3; extra == "dev"
|
|
18
|
+
|
|
19
|
+

|
|
20
|
+
|
|
21
|
+
A stellar spectra integration library.
|
|
22
|
+
|
|
23
|
+
The package can be installed using PyPI:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
pip install stellar-spice
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Paper is currently in prep.
|
|
30
|
+
|
|
31
|
+
# Features implemented
|
|
32
|
+
|
|
33
|
+
- [x] Rotation
|
|
34
|
+
- [x] Spots
|
|
35
|
+
- [ ] Pulsations
|
|
36
|
+
- [ ] PHOEBE integration
|
|
37
|
+
|
|
38
|
+
## Dev log
|
|
39
|
+
|
|
40
|
+
Notebooks are being updated one by one.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+

|
|
2
|
+
|
|
3
|
+
A stellar spectra integration library.
|
|
4
|
+
|
|
5
|
+
The package can be installed using PyPI:
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install stellar-spice
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Paper is currently in prep.
|
|
12
|
+
|
|
13
|
+
# Features implemented
|
|
14
|
+
|
|
15
|
+
- [x] Rotation
|
|
16
|
+
- [x] Spots
|
|
17
|
+
- [ ] Pulsations
|
|
18
|
+
- [ ] PHOEBE integration
|
|
19
|
+
|
|
20
|
+
## Dev log
|
|
21
|
+
|
|
22
|
+
Notebooks are being updated one by one.
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools >= 61.0"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "stellar-spice"
|
|
7
|
+
version = "0.3"
|
|
8
|
+
readme = "README.md"
|
|
9
|
+
dependencies = [
|
|
10
|
+
"celluloid~=0.2.0",
|
|
11
|
+
"jax~=0.4.24",
|
|
12
|
+
"jaxlib~=0.4.24",
|
|
13
|
+
"numpy~=1.26.4",
|
|
14
|
+
"flax~=0.8.1",
|
|
15
|
+
"joblib~=1.3.2",
|
|
16
|
+
"transformer-payne~=0.5"
|
|
17
|
+
]
|
|
18
|
+
|
|
19
|
+
[project.optional-dependencies]
|
|
20
|
+
huggingface = ["huggingface-hub~=0.21.3"]
|
|
21
|
+
dev = ["pytest~=8.0.0", "pytest-datadir~=1.5.0", "coverage~=7.4.3"]
|
|
22
|
+
|
|
23
|
+
[tool.setuptools.dynamic]
|
|
24
|
+
dependencies = {file = ["requirements.txt"]}
|
|
25
|
+
|
|
26
|
+
[tool.setuptools.packages.find]
|
|
27
|
+
where = ["src"]
|
|
28
|
+
|
|
29
|
+
[tool.pytest.ini_options]
|
|
30
|
+
python_classes = ["Test", "Describe"]
|
|
31
|
+
python_functions = ["test_", "it_", "and_", "but_", "they_"]
|
|
32
|
+
python_files = ["test_*.py",]
|
|
33
|
+
testpaths = ["tests",]
|
|
34
|
+
pythonpath = ["src"]
|
|
File without changes
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import jax
|
|
2
|
+
import jax.numpy as jnp
|
|
3
|
+
from jax.typing import ArrayLike
|
|
4
|
+
from spice.geometry.utils import inside, last_non_nan, append_to_last_nan, repeat_last, sort_xy
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@jax.jit
|
|
8
|
+
def x_y_first_line_vertical(p1, p2, q1, q2):
|
|
9
|
+
x = p1[0]
|
|
10
|
+
m2 = (q2[1]-q1[1])/(q2[0]-q1[0])
|
|
11
|
+
b2 = q1[1]-m2*q1[0]
|
|
12
|
+
return x, m2*x+b2
|
|
13
|
+
|
|
14
|
+
@jax.jit
|
|
15
|
+
def x_y_second_line_vertical(p1, p2, q1, q2):
|
|
16
|
+
x = q1[0]
|
|
17
|
+
m1 = (p2[1]-p1[1])/(p2[0]-p1[0])
|
|
18
|
+
b1 = p1[1]-m1*p1[0]
|
|
19
|
+
return x, m1*x+b1
|
|
20
|
+
|
|
21
|
+
@jax.jit
|
|
22
|
+
def x_y_no_vertical(p1, p2, q1, q2):
|
|
23
|
+
m1 = (p2[1]-p1[1])/(p2[0]-p1[0])
|
|
24
|
+
b1 = p1[1]-m1*p1[0]
|
|
25
|
+
|
|
26
|
+
m2 = (q2[1]-q1[1])/(q2[0]-q1[0])
|
|
27
|
+
b2 = q1[1]-m2*q1[0]
|
|
28
|
+
|
|
29
|
+
x = (b2-b1)/(m1-m2)
|
|
30
|
+
return x, m1*x+b1
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@jax.jit
|
|
34
|
+
def compute_intersection(p1: ArrayLike, p2: ArrayLike, q1: ArrayLike, q2: ArrayLike) -> ArrayLike:
|
|
35
|
+
"""Calculate the intersection of two lines
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
p1 (ArrayLike): point 1 of line 1
|
|
39
|
+
p2 (ArrayLike): point 2 of line 1
|
|
40
|
+
q1 (ArrayLike): point 1 of line 2
|
|
41
|
+
q2 (ArrayLike): point 2 of line 2
|
|
42
|
+
|
|
43
|
+
Returns:
|
|
44
|
+
ArrayLike: coordinates of the intersection of two lines
|
|
45
|
+
"""
|
|
46
|
+
x, y = jax.lax.cond(jnp.isclose(p1[0]-p2[0], 0),
|
|
47
|
+
x_y_first_line_vertical,
|
|
48
|
+
lambda a, b, c, d: jax.lax.cond(
|
|
49
|
+
jnp.isclose(c[0]-d[0], 0),
|
|
50
|
+
x_y_second_line_vertical,
|
|
51
|
+
x_y_no_vertical,
|
|
52
|
+
a, b, c, d),
|
|
53
|
+
p1, p2, q1, q2)
|
|
54
|
+
|
|
55
|
+
return jnp.array([[x, y]])
|
|
56
|
+
|
|
57
|
+
def s_only_edge_start_inside(final_polygon, c_edge_start, c_edge_end, s_edge_start, s_edge_end):
|
|
58
|
+
intersection = compute_intersection(s_edge_start,
|
|
59
|
+
s_edge_end,
|
|
60
|
+
c_edge_start,
|
|
61
|
+
c_edge_end)
|
|
62
|
+
intersection = jax.lax.cond(jnp.any(jnp.isnan(intersection)),
|
|
63
|
+
lambda: jnp.nan*intersection,
|
|
64
|
+
lambda: intersection)
|
|
65
|
+
return append_to_last_nan(final_polygon, intersection)
|
|
66
|
+
|
|
67
|
+
def s_only_edge_end_inside(final_polygon, c_edge_start, c_edge_end, s_edge_start, s_edge_end):
|
|
68
|
+
intersection = compute_intersection(s_edge_start,
|
|
69
|
+
s_edge_end,
|
|
70
|
+
c_edge_start,
|
|
71
|
+
c_edge_end)
|
|
72
|
+
intersection = jax.lax.cond(jnp.any(jnp.isnan(intersection)),
|
|
73
|
+
lambda: jnp.nan*intersection,
|
|
74
|
+
lambda: intersection)
|
|
75
|
+
polygon_with_intersection = append_to_last_nan(final_polygon, intersection)
|
|
76
|
+
return append_to_last_nan(polygon_with_intersection, s_edge_end)
|
|
77
|
+
|
|
78
|
+
def s_both_start_and_end_inside(final_polygon, c_edge_start, c_edge_end, s_edge_start, s_edge_end):
|
|
79
|
+
return append_to_last_nan(final_polygon, s_edge_end)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
@jax.jit
|
|
83
|
+
def clip(subject_polygon: ArrayLike, clipping_polygon: ArrayLike) -> ArrayLike:
|
|
84
|
+
"""Calculate the intersection of the triangles subject_polygon and clipping_polygon
|
|
85
|
+
|
|
86
|
+
Args:
|
|
87
|
+
subject_polygon (ArrayLike): x and y coordinates (3, 2)
|
|
88
|
+
clipping_polygon (ArrayLike): x and y coordinates (3, 2)
|
|
89
|
+
|
|
90
|
+
Returns:
|
|
91
|
+
ArrayLike: intersection between two triangles (6, 2)
|
|
92
|
+
"""
|
|
93
|
+
subject_polygon = sort_xy(subject_polygon)
|
|
94
|
+
clipping_polygon = sort_xy(clipping_polygon)
|
|
95
|
+
final_polygon = repeat_last(jnp.concatenate([jnp.copy(subject_polygon), jnp.nan*jnp.ones((6-subject_polygon.shape[0], 2))], axis=0))
|
|
96
|
+
|
|
97
|
+
def outer_iteration(i, final_polygon):
|
|
98
|
+
next_polygon = jnp.copy(final_polygon)
|
|
99
|
+
|
|
100
|
+
final_polygon = jnp.nan*jnp.ones((6, 2))
|
|
101
|
+
|
|
102
|
+
#c_edge_start = clipping_polygon[((i-1)%(clipping_polygon.shape[0])).astype(int)]
|
|
103
|
+
c_edge_start = jax.lax.cond(i==0,
|
|
104
|
+
lambda: last_non_nan(clipping_polygon),
|
|
105
|
+
lambda: clipping_polygon[i-1])
|
|
106
|
+
c_edge_end = clipping_polygon[i]
|
|
107
|
+
|
|
108
|
+
def inner_iteration(j, final_polygon):
|
|
109
|
+
s_edge_start = jax.lax.cond(j==0,
|
|
110
|
+
lambda: last_non_nan(next_polygon),
|
|
111
|
+
lambda: next_polygon[j-1])
|
|
112
|
+
#s_edge_start = next_polygon[((j-1)%(next_polygon.shape[0])).astype(int)]
|
|
113
|
+
s_edge_end = next_polygon[j]
|
|
114
|
+
|
|
115
|
+
return jax.lax.cond(
|
|
116
|
+
jnp.any(inside(c_edge_start, c_edge_end, s_edge_end)),
|
|
117
|
+
lambda: jax.lax.cond(
|
|
118
|
+
jnp.any(inside(c_edge_start, c_edge_end, s_edge_start)),
|
|
119
|
+
lambda: s_both_start_and_end_inside(
|
|
120
|
+
final_polygon,
|
|
121
|
+
c_edge_start, c_edge_end,
|
|
122
|
+
s_edge_start, s_edge_end
|
|
123
|
+
),
|
|
124
|
+
lambda: s_only_edge_end_inside(
|
|
125
|
+
final_polygon,
|
|
126
|
+
c_edge_start, c_edge_end,
|
|
127
|
+
s_edge_start, s_edge_end
|
|
128
|
+
),
|
|
129
|
+
),
|
|
130
|
+
lambda: jax.lax.cond(
|
|
131
|
+
jnp.any(inside(c_edge_start, c_edge_end, s_edge_start)),
|
|
132
|
+
lambda: s_only_edge_start_inside(
|
|
133
|
+
final_polygon,
|
|
134
|
+
c_edge_start, c_edge_end,
|
|
135
|
+
s_edge_start, s_edge_end
|
|
136
|
+
),
|
|
137
|
+
lambda: final_polygon
|
|
138
|
+
)
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
return jax.lax.fori_loop(0, next_polygon.shape[0], inner_iteration, final_polygon)
|
|
142
|
+
return repeat_last(jax.lax.fori_loop(0, clipping_polygon.shape[0], outer_iteration, final_polygon))
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import jax
|
|
2
|
+
import jax.numpy as jnp
|
|
3
|
+
from jax.typing import ArrayLike
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def sort_xy(points: ArrayLike) -> ArrayLike:
|
|
7
|
+
"""Sort points clockwise
|
|
8
|
+
|
|
9
|
+
Args:
|
|
10
|
+
points (ArrayLike): x, y coordinates (n, 2)
|
|
11
|
+
|
|
12
|
+
Returns:
|
|
13
|
+
ArrayLike: points with x, y coordinates sorted clockwise
|
|
14
|
+
"""
|
|
15
|
+
x, y = points[:, 0], points[:, 1]
|
|
16
|
+
x0 = jnp.mean(x)
|
|
17
|
+
y0 = jnp.mean(y)
|
|
18
|
+
|
|
19
|
+
r = jnp.sqrt((x-x0)**2 + (y-y0)**2)
|
|
20
|
+
|
|
21
|
+
angles = jnp.where((y-y0) > 0, jnp.arccos((x-x0)/r), 2*jnp.pi-jnp.arccos((x-x0)/r))
|
|
22
|
+
|
|
23
|
+
mask = jnp.argsort(angles)
|
|
24
|
+
|
|
25
|
+
return points[mask, :]
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def inside(p1: ArrayLike, p2: ArrayLike, q: ArrayLike) -> bool:
|
|
30
|
+
"""Check if the point is inside the polygon edge
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
p1 (ArrayLike): Polygon edge point 1
|
|
34
|
+
p2 (ArrayLike): Polygon edge point 2
|
|
35
|
+
q (ArrayLike): Point to be checked
|
|
36
|
+
|
|
37
|
+
Returns:
|
|
38
|
+
bool: Point is inside polygon
|
|
39
|
+
"""
|
|
40
|
+
return (p2[0]-p1[0])*(q[1]-p1[1])-(p2[1]-p1[1])*(q[0]-p1[0])>=0
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def last_non_nan_arg(arr: ArrayLike) -> ArrayLike:
|
|
44
|
+
"""Return the last non-nan index of the array
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
arr (ArrayLike): Array
|
|
48
|
+
|
|
49
|
+
Returns:
|
|
50
|
+
ArrayLike: Last non-nan index of the array
|
|
51
|
+
"""
|
|
52
|
+
return jnp.max(jnp.argwhere(jnp.all(~jnp.isnan(arr), axis=1), size=arr.shape[0], fill_value=0)).astype(int)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def last_non_nan(arr: ArrayLike, axis=1) -> ArrayLike:
|
|
56
|
+
"""Return the last non-nan value of the array
|
|
57
|
+
|
|
58
|
+
Args:
|
|
59
|
+
arr (ArrayLike): Array
|
|
60
|
+
|
|
61
|
+
Returns:
|
|
62
|
+
ArrayLike: Last non-nan row of the array
|
|
63
|
+
"""
|
|
64
|
+
return arr[jnp.max(jnp.argwhere(jnp.all(~jnp.isnan(arr), axis=axis), size=arr.shape[0], fill_value=0))]
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def append_to_last_nan(arr: ArrayLike, to_add: ArrayLike) -> ArrayLike:
|
|
68
|
+
"""Append the row to the first nan row
|
|
69
|
+
|
|
70
|
+
Args:
|
|
71
|
+
arr (ArrayLike): Array to add the row to
|
|
72
|
+
to_add (ArrayLike): The row to be added
|
|
73
|
+
|
|
74
|
+
Returns:
|
|
75
|
+
ArrayLike: Array with an appended row
|
|
76
|
+
"""
|
|
77
|
+
next_ind = jnp.min(jnp.argwhere(jnp.all(jnp.isnan(arr), axis=1), size=arr.shape[0], fill_value=arr.shape[0]-1))
|
|
78
|
+
return arr.at[next_ind].set(to_add.flatten())
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def append_value_to_last_nan(arr: ArrayLike, to_add: ArrayLike) -> ArrayLike:
|
|
82
|
+
"""Append a single value to the first nan in array
|
|
83
|
+
|
|
84
|
+
Args:
|
|
85
|
+
arr (ArrayLike): Array to add the value to
|
|
86
|
+
to_add (ArrayLike): The value to be appended
|
|
87
|
+
|
|
88
|
+
Returns:
|
|
89
|
+
ArrayLike: Array with an appended value
|
|
90
|
+
"""
|
|
91
|
+
next_ind = jnp.min(jnp.argwhere(jnp.isnan(arr), size=arr.shape[0], fill_value=arr.shape[0]-1))
|
|
92
|
+
return arr.at[next_ind].set(to_add)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def repeat_last(arr: ArrayLike) -> ArrayLike:
|
|
96
|
+
"""Repeat the last non-nan row to fill the whole array
|
|
97
|
+
|
|
98
|
+
Args:
|
|
99
|
+
arr (ArrayLike):
|
|
100
|
+
|
|
101
|
+
Returns:
|
|
102
|
+
ArrayLike: Array with nan values filled with last non-nan values
|
|
103
|
+
"""
|
|
104
|
+
return jax.lax.cond(jnp.any(jnp.all(jnp.isnan(arr), axis=1)),
|
|
105
|
+
lambda: jax.lax.fori_loop(0, jnp.sum(jnp.all(jnp.isnan(arr), axis=1)),
|
|
106
|
+
lambda i, _arr: append_to_last_nan(_arr, last_non_nan(_arr)),
|
|
107
|
+
arr),
|
|
108
|
+
lambda: arr)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def wrap(arr: ArrayLike) -> ArrayLike:
|
|
112
|
+
"""Copy the first row and concatenate to the first array
|
|
113
|
+
|
|
114
|
+
Args:
|
|
115
|
+
arr (ArrayLike):
|
|
116
|
+
|
|
117
|
+
Returns:
|
|
118
|
+
ArrayLike: Array with the copy of the first row appended
|
|
119
|
+
"""
|
|
120
|
+
return jnp.concatenate([arr, arr[0].reshape((-1, arr.shape[1]))])
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def polygon_area(x: ArrayLike, y: ArrayLike) -> ArrayLike:
|
|
124
|
+
"""Calculate a polygon's surface area from its x and y coordinates
|
|
125
|
+
|
|
126
|
+
Args:
|
|
127
|
+
x (ArrayLike): x coordinates of the polygon
|
|
128
|
+
y (ArrayLike): y coordinates of the polygon
|
|
129
|
+
|
|
130
|
+
Returns:
|
|
131
|
+
ArrayLike: polygon's surface area
|
|
132
|
+
"""
|
|
133
|
+
return 0.5*jnp.abs(jnp.dot(x,jnp.roll(y,1))-jnp.dot(y,jnp.roll(x,1)))
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
polygon_areas = jax.jit(jax.vmap(polygon_area, in_axes=(0, 0)))
|
|
137
|
+
|
|
138
|
+
def cast_indexes(cast_vertices: ArrayLike) -> ArrayLike:
|
|
139
|
+
"""When casting 3D vectors to 2D, get the indices of non-reduced dimensions
|
|
140
|
+
|
|
141
|
+
Args:
|
|
142
|
+
cast_vertices (ArrayLike): vertices after 3D to 2D casting
|
|
143
|
+
|
|
144
|
+
Returns:
|
|
145
|
+
ArrayLike: non-reduced (non-zero) dimensions
|
|
146
|
+
"""
|
|
147
|
+
zeroth_ind = jnp.argmax(jnp.all(jnp.isclose(cast_vertices, 0), axis=1))
|
|
148
|
+
|
|
149
|
+
return jax.lax.cond(zeroth_ind == 0,
|
|
150
|
+
lambda: jnp.array([1, 2]),
|
|
151
|
+
lambda: jax.lax.cond(zeroth_ind == 1,
|
|
152
|
+
lambda: jnp.array([0, 2]),
|
|
153
|
+
lambda: jnp.array([0, 1])))
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def get_cast_areas(face_vertices: ArrayLike) -> ArrayLike:
|
|
157
|
+
"""Get visible, 2D-casted polygon areas
|
|
158
|
+
|
|
159
|
+
Args:
|
|
160
|
+
face_vertices (ArrayLike): polygons' vertices after 3D to 2D casting
|
|
161
|
+
|
|
162
|
+
Returns:
|
|
163
|
+
ArrayLike: areas of 2D-casted polygons
|
|
164
|
+
"""
|
|
165
|
+
cast_ind = cast_indexes(face_vertices)
|
|
166
|
+
return polygon_areas(face_vertices[:, :, cast_ind[0]],
|
|
167
|
+
face_vertices[:, :, cast_ind[1]])
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import jax.numpy as jnp
|
|
2
|
+
from functools import partial
|
|
3
|
+
import jax
|
|
4
|
+
from jax.typing import ArrayLike
|
|
5
|
+
from typing import NamedTuple, Tuple
|
|
6
|
+
from .mesh_model import MeshModel
|
|
7
|
+
from .mesh_transform import transform, evaluate_body_orbit
|
|
8
|
+
import astropy.units as u
|
|
9
|
+
from .orbit_utils import get_orbit_jax
|
|
10
|
+
from .mesh_view import resolve_occlusion, Grid
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
YEAR_TO_SECONDS = (u.year).to(u.s)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class Binary(NamedTuple):
|
|
17
|
+
body1: MeshModel
|
|
18
|
+
body2: MeshModel
|
|
19
|
+
|
|
20
|
+
# Orbital elements
|
|
21
|
+
P: float
|
|
22
|
+
ecc: float
|
|
23
|
+
T: float
|
|
24
|
+
i: float
|
|
25
|
+
omega: float
|
|
26
|
+
Omega: float
|
|
27
|
+
|
|
28
|
+
evaluated_times: ArrayLike
|
|
29
|
+
|
|
30
|
+
body1_centers: ArrayLike
|
|
31
|
+
body2_centers: ArrayLike
|
|
32
|
+
body1_velocities: ArrayLike
|
|
33
|
+
body2_velocities: ArrayLike
|
|
34
|
+
|
|
35
|
+
@classmethod
|
|
36
|
+
def from_bodies(cls, body1: MeshModel, body2: MeshModel) -> "Binary":
|
|
37
|
+
"""Construct a Binary object from two mesh models.
|
|
38
|
+
|
|
39
|
+
Args:
|
|
40
|
+
body1 (MeshModel):
|
|
41
|
+
body2 (MeshModel):
|
|
42
|
+
|
|
43
|
+
Returns:
|
|
44
|
+
Binary: a binary consisting of body1 and body2
|
|
45
|
+
"""
|
|
46
|
+
return cls(body1, body2, 1., 0., 0., 0., 0., 0., 0., jnp.zeros_like(body1.centers), jnp.zeros_like(body2.centers), jnp.zeros_like(body1.velocities), jnp.zeros_like(body2.velocities))
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
@partial(jax.jit, static_argnums=(7,))
|
|
50
|
+
def add_orbit(binary: Binary, P: float, ecc: float,
|
|
51
|
+
T: float, i: float, omega: float, Omega: float, orbit_resolution_points: int) -> Binary:
|
|
52
|
+
"""Add orbit information to the binary object
|
|
53
|
+
|
|
54
|
+
Args:
|
|
55
|
+
binary (Binary):
|
|
56
|
+
P (float): orbit period [years]
|
|
57
|
+
ecc (float): orbit eccentrity
|
|
58
|
+
T (float): [defined time units]
|
|
59
|
+
i (float): inclination [rad]
|
|
60
|
+
omega (float): []
|
|
61
|
+
Omega (float): []
|
|
62
|
+
orbit_resolution_points (int): number of times to resolve the orbit at
|
|
63
|
+
|
|
64
|
+
Returns:
|
|
65
|
+
Binary: object with calculated orbit property values
|
|
66
|
+
"""
|
|
67
|
+
|
|
68
|
+
orbit_resolution_times = jnp.linspace(0, P, orbit_resolution_points)
|
|
69
|
+
orbit = get_orbit_jax(orbit_resolution_times, binary.body1.mass, binary.body2.mass, P,
|
|
70
|
+
ecc, T, i, omega, Omega)
|
|
71
|
+
return binary._replace(P=P, ecc=ecc, T=T, i=i, omega=omega, Omega=Omega,
|
|
72
|
+
evaluated_times=orbit_resolution_times,
|
|
73
|
+
body1_centers=orbit[2, :, :], body2_centers=orbit[4, :, :],
|
|
74
|
+
body1_velocities=orbit[3, :, :], body2_velocities=orbit[5, :, :])
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
@partial(jax.jit, static_argnums=(2,))
|
|
78
|
+
def _evaluate_orbit(binary: Binary, time: ArrayLike, grid: Grid) -> Tuple[MeshModel, MeshModel]:
|
|
79
|
+
interpolate_orbit = jax.jit(jax.vmap(lambda x: jnp.interp(time, binary.evaluated_times, x, period=binary.P), in_axes=(0,)))
|
|
80
|
+
body1_center = interpolate_orbit(binary.body1_centers)
|
|
81
|
+
body2_center = interpolate_orbit(binary.body2_centers)
|
|
82
|
+
body1_velocity = interpolate_orbit(binary.body1_velocities)
|
|
83
|
+
body2_velocity = interpolate_orbit(binary.body2_velocities)
|
|
84
|
+
|
|
85
|
+
body1 = evaluate_body_orbit(transform(binary.body1, binary.body1.center+body1_center), body1_velocity)
|
|
86
|
+
body2 = evaluate_body_orbit(transform(binary.body2, binary.body2.center+body2_center), body2_velocity)
|
|
87
|
+
|
|
88
|
+
return jax.lax.cond(jnp.mean(body1.los_z)>jnp.mean(body2.los_z),
|
|
89
|
+
lambda: (body1, resolve_occlusion(body2, body1, grid)),
|
|
90
|
+
lambda: (resolve_occlusion(body1, body2, grid), body2))
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
v_evaluate_orbit = jax.jit(jax.vmap(_evaluate_orbit, in_axes=(None, 0, None)), static_argnums=(2,))
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def evaluate_orbit(binary: Binary, time: ArrayLike, n_cells: int = 20) -> Tuple[MeshModel, MeshModel]:
|
|
97
|
+
"""_summary_
|
|
98
|
+
|
|
99
|
+
Args:
|
|
100
|
+
binary (Binary): a binary object
|
|
101
|
+
time (ArrayLike): time to resolve the orbit at
|
|
102
|
+
n_cells (int, optional): number of grid cells to divide the calculation of occlusion to. Defaults to 20.
|
|
103
|
+
|
|
104
|
+
Returns:
|
|
105
|
+
Tuple[MeshModel, MeshModel]: (body1, body2)
|
|
106
|
+
"""
|
|
107
|
+
grid = Grid.construct(binary.body1, binary.body2, n_cells)
|
|
108
|
+
return _evaluate_orbit(binary, time, grid)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def evaluate_orbit_at_times(binary: Binary, times: ArrayLike, n_cells: int = 20) -> Tuple[MeshModel, MeshModel]:
|
|
112
|
+
"""_summary_
|
|
113
|
+
|
|
114
|
+
Args:
|
|
115
|
+
binary (Binary): a binary object
|
|
116
|
+
times (ArrayLike): times to resolve the orbit at
|
|
117
|
+
n_cells (int, optional): number of grid cells to divide the calculation of occlusion to. Defaults to 20.
|
|
118
|
+
|
|
119
|
+
Returns:
|
|
120
|
+
Tuple[MeshModel, MeshModel]: (body1, body2)
|
|
121
|
+
"""
|
|
122
|
+
grid = Grid.construct(binary.body1, binary.body2, n_cells)
|
|
123
|
+
return v_evaluate_orbit(binary, times, grid)
|