rwlenspy 1.1.1__tar.gz
Sign up to get free protection for your applications and to get access to all the features.
- rwlenspy-1.1.1/LICENSE +21 -0
- rwlenspy-1.1.1/PKG-INFO +46 -0
- rwlenspy-1.1.1/README.md +25 -0
- rwlenspy-1.1.1/build.py +60 -0
- rwlenspy-1.1.1/examples/example_animate_multilens.py +368 -0
- rwlenspy-1.1.1/examples/example_animate_singlelens.py +339 -0
- rwlenspy-1.1.1/examples/example_transfer_multilens.py +197 -0
- rwlenspy-1.1.1/examples/example_transfer_singlelens.py +175 -0
- rwlenspy-1.1.1/pyproject.toml +45 -0
- rwlenspy-1.1.1/rwlenspy/__init__.py +0 -0
- rwlenspy-1.1.1/rwlenspy/lensing.pxd +224 -0
- rwlenspy-1.1.1/rwlenspy/lensing.pyx +839 -0
- rwlenspy-1.1.1/rwlenspy/rwlens.cpp +844 -0
- rwlenspy-1.1.1/rwlenspy/rwlens.h +264 -0
- rwlenspy-1.1.1/rwlenspy/utils.py +557 -0
- rwlenspy-1.1.1/setup.py +36 -0
- rwlenspy-1.1.1/tests/__init__.py +0 -0
- rwlenspy-1.1.1/tests/test_rwlens.py +395 -0
rwlenspy-1.1.1/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2023 Zarif Kader
|
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.
|
rwlenspy-1.1.1/PKG-INFO
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
Metadata-Version: 2.1
|
2
|
+
Name: rwlenspy
|
3
|
+
Version: 1.1.1
|
4
|
+
Summary: Lensing simulation from Fermat Potenials
|
5
|
+
Author: Zarif Kader
|
6
|
+
Author-email: kader.zarif@gmail.com
|
7
|
+
Requires-Python: >=3.9
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
9
|
+
Classifier: Programming Language :: Python :: 3.9
|
10
|
+
Classifier: Programming Language :: Python :: 3.10
|
11
|
+
Classifier: Programming Language :: Python :: 3.11
|
12
|
+
Classifier: Programming Language :: Python :: 3.12
|
13
|
+
Requires-Dist: Cython (>=0.29.33)
|
14
|
+
Requires-Dist: astropy (>=6.0.0)
|
15
|
+
Requires-Dist: matplotlib (>=3.8.2)
|
16
|
+
Requires-Dist: numpy (>=1.21.0)
|
17
|
+
Requires-Dist: scipy (>=1.7.0)
|
18
|
+
Requires-Dist: setuptools (>=66.1.1)
|
19
|
+
Description-Content-Type: text/markdown
|
20
|
+
|
21
|
+
# RWLensPy
|
22
|
+
|
23
|
+
This is a python package that generates observed morphologies and propagation transfer functions for radio wave propgation recorded by a radio telescope.
|
24
|
+
|
25
|
+
The code can be installed with:
|
26
|
+
|
27
|
+
`pip install rwlenspy`
|
28
|
+
|
29
|
+
## Examples
|
30
|
+
|
31
|
+
For examples see `examples/`. The image ray trace is shown in the `example_animate_*.py` files and how to get the coherent transfer function for a baseband simulation is shown in `example_transfer*.py`.
|
32
|
+
|
33
|
+
<img src="./examples/plots/singelens_spatial_freqslice.gif" width=42%> <img src="./examples/plots/singlelens_baseband_spatial_arrival.gif" width=42%>
|
34
|
+
|
35
|
+
## Custom/Dev Install
|
36
|
+
|
37
|
+
The package is built with Poetry and Cython using C++11 and OpenMP. This requires having a compiler like `gcc` if one is editing the code. If one requires a dev install, this can be done with:
|
38
|
+
|
39
|
+
`poetry install --with test,dev`
|
40
|
+
|
41
|
+
`poetry run python`
|
42
|
+
|
43
|
+
Once installed, tests can be run with:
|
44
|
+
|
45
|
+
`poetry run pytest`
|
46
|
+
|
rwlenspy-1.1.1/README.md
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
# RWLensPy
|
2
|
+
|
3
|
+
This is a python package that generates observed morphologies and propagation transfer functions for radio wave propgation recorded by a radio telescope.
|
4
|
+
|
5
|
+
The code can be installed with:
|
6
|
+
|
7
|
+
`pip install rwlenspy`
|
8
|
+
|
9
|
+
## Examples
|
10
|
+
|
11
|
+
For examples see `examples/`. The image ray trace is shown in the `example_animate_*.py` files and how to get the coherent transfer function for a baseband simulation is shown in `example_transfer*.py`.
|
12
|
+
|
13
|
+
<img src="./examples/plots/singelens_spatial_freqslice.gif" width=42%> <img src="./examples/plots/singlelens_baseband_spatial_arrival.gif" width=42%>
|
14
|
+
|
15
|
+
## Custom/Dev Install
|
16
|
+
|
17
|
+
The package is built with Poetry and Cython using C++11 and OpenMP. This requires having a compiler like `gcc` if one is editing the code. If one requires a dev install, this can be done with:
|
18
|
+
|
19
|
+
`poetry install --with test,dev`
|
20
|
+
|
21
|
+
`poetry run python`
|
22
|
+
|
23
|
+
Once installed, tests can be run with:
|
24
|
+
|
25
|
+
`poetry run pytest`
|
rwlenspy-1.1.1/build.py
ADDED
@@ -0,0 +1,60 @@
|
|
1
|
+
#!/usr/bin/env python
|
2
|
+
# -*- coding: utf-8 -*-
|
3
|
+
import numpy
|
4
|
+
from setuptools.command.build_ext import build_ext
|
5
|
+
from setuptools import Extension
|
6
|
+
|
7
|
+
from pathlib import Path
|
8
|
+
|
9
|
+
root = Path(__file__).parent
|
10
|
+
|
11
|
+
print("Build File Imported")
|
12
|
+
|
13
|
+
# See if Cython is installed
|
14
|
+
try:
|
15
|
+
from Cython.Build import cythonize
|
16
|
+
# If Cython is not available
|
17
|
+
except ImportError:
|
18
|
+
def build(setup_kwargs):
|
19
|
+
print("Build without Cython")
|
20
|
+
ext_modules = [
|
21
|
+
Extension(
|
22
|
+
"rwlenspy.lensing",
|
23
|
+
["rwlenspy/lensing.cpp"],
|
24
|
+
)
|
25
|
+
]
|
26
|
+
setup_kwargs.update(
|
27
|
+
{
|
28
|
+
"ext_modules": ext_modules,
|
29
|
+
"include_dirs": [numpy.get_include()],
|
30
|
+
}
|
31
|
+
)
|
32
|
+
# Cython is installed, Compile.
|
33
|
+
else:
|
34
|
+
# This function will be executed in setup.py:
|
35
|
+
def build(setup_kwargs):
|
36
|
+
print("Build with Cython")
|
37
|
+
# The files you want to compile
|
38
|
+
ext_modules = [
|
39
|
+
Extension(
|
40
|
+
"rwlenspy.lensing",
|
41
|
+
sources=["rwlenspy/lensing.pyx", "rwlenspy/rwlens.cpp"],
|
42
|
+
include_dirs=[numpy.get_include()],
|
43
|
+
extra_compile_args=["-fopenmp", "-std=c++11"],
|
44
|
+
extra_link_args=["-fopenmp"],
|
45
|
+
define_macros=[("NPY_NO_DEPRECATED_API", "NPY_1_7_API_VERSION")],
|
46
|
+
language="c++"
|
47
|
+
)
|
48
|
+
]
|
49
|
+
|
50
|
+
# Build
|
51
|
+
setup_kwargs.update(
|
52
|
+
{
|
53
|
+
"ext_modules": cythonize(
|
54
|
+
ext_modules,
|
55
|
+
language_level=3,
|
56
|
+
compiler_directives={"linetrace": True},
|
57
|
+
),
|
58
|
+
"cmdclass": {"build_ext": build_ext},
|
59
|
+
}
|
60
|
+
)
|
@@ -0,0 +1,368 @@
|
|
1
|
+
from pathlib import Path
|
2
|
+
from time import time
|
3
|
+
|
4
|
+
import matplotlib as mpl
|
5
|
+
import matplotlib.animation
|
6
|
+
import matplotlib.cm as cmaps
|
7
|
+
import matplotlib.pyplot as plt
|
8
|
+
import numpy as np
|
9
|
+
from astropy import constants as c
|
10
|
+
from astropy import cosmology
|
11
|
+
from astropy import units as u
|
12
|
+
from matplotlib.colors import LogNorm
|
13
|
+
from scipy.fft import rfftfreq
|
14
|
+
|
15
|
+
import rwlenspy.lensing as rwl
|
16
|
+
from rwlenspy.utils import LogLens, RandomGaussianLens
|
17
|
+
|
18
|
+
# Matplotlib setup
|
19
|
+
GREYMAP = mpl.cm.__dict__["Greys"]
|
20
|
+
mpl.rcParams["figure.figsize"] = [8.0, 6.0]
|
21
|
+
mpl.rcParams["figure.dpi"] = 80
|
22
|
+
mpl.rcParams["savefig.dpi"] = 100
|
23
|
+
mpl.rcParams["font.size"] = 12
|
24
|
+
mpl.rcParams["legend.fontsize"] = "large"
|
25
|
+
mpl.rcParams["figure.titlesize"] = "large"
|
26
|
+
mpl.rcParams["agg.path.chunksize"] = 10000
|
27
|
+
plt.rcParams["savefig.dpi"] = 70
|
28
|
+
|
29
|
+
"""
|
30
|
+
############################
|
31
|
+
#### Lensing Ray Trace #####
|
32
|
+
############################
|
33
|
+
"""
|
34
|
+
|
35
|
+
"""
|
36
|
+
Diagram of Lensing System setup.
|
37
|
+
############################################################
|
38
|
+
# | | | |
|
39
|
+
# | | | |
|
40
|
+
# | | | |
|
41
|
+
# | | | |
|
42
|
+
# obs r1 r2 src
|
43
|
+
############################################################
|
44
|
+
"""
|
45
|
+
# Memory in bytes
|
46
|
+
max_memory = 4e9
|
47
|
+
|
48
|
+
cosmo = cosmology.Planck18
|
49
|
+
|
50
|
+
# Comoving
|
51
|
+
D_obs_src = 1 * u.Gpc
|
52
|
+
D_obs_r1 = D_obs_src / 2
|
53
|
+
D_obs_r2 = 3 * D_obs_src / 4
|
54
|
+
|
55
|
+
# redshift
|
56
|
+
z_obs_r1 = cosmology.z_at_value(cosmo.comoving_distance, D_obs_r1)
|
57
|
+
z_obs_r2 = cosmology.z_at_value(cosmo.comoving_distance, D_obs_r2)
|
58
|
+
z_obs_src = cosmology.z_at_value(cosmo.comoving_distance, D_obs_src)
|
59
|
+
|
60
|
+
# Ang. Diam. Distance
|
61
|
+
D_obs_r1 = cosmo.angular_diameter_distance(z_obs_r1)
|
62
|
+
D_obs_r2 = cosmo.angular_diameter_distance(z_obs_r2)
|
63
|
+
D_r1_r2 = cosmo.angular_diameter_distance_z1z2(z_obs_r1, z_obs_r2)
|
64
|
+
D_obs_src = cosmo.angular_diameter_distance(z_obs_src)
|
65
|
+
D_r2_src = cosmo.angular_diameter_distance_z1z2(z_obs_r2, z_obs_src)
|
66
|
+
|
67
|
+
# Physical Lens (r2) Params
|
68
|
+
r_e = c.alpha**2 * c.a0 # classical electron radius
|
69
|
+
kdm = (
|
70
|
+
(r_e * c.c / (2 * np.pi)).to(u.cm**2 / u.s)
|
71
|
+
* ((1.0 * u.pc / u.cm).to(u.m / u.m)).value
|
72
|
+
).value
|
73
|
+
const_Dr2 = D_r2_src / (D_obs_r2 * D_obs_src)
|
74
|
+
lens_r2_scale = (5000 * u.AU / D_obs_r2).to(u.m / u.m)
|
75
|
+
scale_r2 = lens_r2_scale.value
|
76
|
+
sig_DM = 0.0005
|
77
|
+
geom_const_r2 = ((1 / (const_Dr2 * c.c)).to(u.s)).value
|
78
|
+
geom_const_r2 = geom_const_r2 * scale_r2**2
|
79
|
+
lens_const_r2 = kdm * sig_DM
|
80
|
+
freq_power_r2 = -2.0
|
81
|
+
beta_r2_x = 0.0
|
82
|
+
beta_r2_y = 0.0
|
83
|
+
|
84
|
+
# Physical Lens (r1) Params
|
85
|
+
Eins_time_const = 4 * c.G * c.M_sun / c.c**3
|
86
|
+
const_Dr1 = D_r1_r2 / (D_obs_r1 * D_obs_r2)
|
87
|
+
mass = 1 # solar mass
|
88
|
+
lens_r1_scale = np.sqrt(mass * Eins_time_const * c.c * const_Dr1).to(u.m / u.m)
|
89
|
+
scale_r1 = lens_r1_scale.value
|
90
|
+
geom_const_r1 = ((1 / (const_Dr1 * c.c)).to(u.s)).value
|
91
|
+
geom_const_r1 = geom_const_r1 * scale_r1**2
|
92
|
+
lens_const_r1 = mass * Eins_time_const.to(u.s).value
|
93
|
+
freq_power_r1 = 0
|
94
|
+
beta_r1_x = 1.5
|
95
|
+
beta_r1_y = 0.0
|
96
|
+
|
97
|
+
# Sim Parameters
|
98
|
+
freq_ref = 800e6
|
99
|
+
freqs = 800e6 - rfftfreq(2048, d=1 / (800e6)) # MHz
|
100
|
+
nyqalias = True
|
101
|
+
|
102
|
+
# Grid Parameters
|
103
|
+
max_fres = 5
|
104
|
+
theta_min = -max_fres
|
105
|
+
theta_max = max_fres
|
106
|
+
theta_N = 251
|
107
|
+
|
108
|
+
# Spatial Grid
|
109
|
+
x1 = np.arange(theta_N) * (theta_max - theta_min) / (theta_N - 1) + theta_min
|
110
|
+
|
111
|
+
# Lens functions
|
112
|
+
seed = 4321
|
113
|
+
lens_arr_r2 = RandomGaussianLens(theta_N, theta_N, 1, seed=seed)
|
114
|
+
lens_arr_r1 = -1.0 * LogLens(x1[:, None], x1[None, :])
|
115
|
+
|
116
|
+
lens_arr_r2 = lens_arr_r2.astype(np.double).ravel(order="C")
|
117
|
+
lens_arr_r1 = lens_arr_r1.astype(np.double).ravel(order="C")
|
118
|
+
freqs = freqs.astype(np.double).ravel(order="C")
|
119
|
+
|
120
|
+
# Get Images
|
121
|
+
print("Getting the Images")
|
122
|
+
t1 = time()
|
123
|
+
txvals, tyvals, fvals, delayvals, magvals = rwl.GetMultiplaneFreqStationaryPoints(
|
124
|
+
theta_min,
|
125
|
+
theta_max,
|
126
|
+
theta_N,
|
127
|
+
freqs,
|
128
|
+
freq_ref,
|
129
|
+
lens_arr_r2,
|
130
|
+
scale_r2,
|
131
|
+
beta_r2_x,
|
132
|
+
beta_r2_y,
|
133
|
+
geom_const_r2,
|
134
|
+
lens_const_r2,
|
135
|
+
freq_power_r2,
|
136
|
+
lens_arr_r1,
|
137
|
+
scale_r1,
|
138
|
+
beta_r1_x,
|
139
|
+
beta_r1_y,
|
140
|
+
geom_const_r1,
|
141
|
+
lens_const_r1,
|
142
|
+
freq_power_r1,
|
143
|
+
max_memory
|
144
|
+
)
|
145
|
+
tv = time() - t1
|
146
|
+
print("Total Time :", tv, "s", " | ", tv / 60, "min", tv / 3600, "hr")
|
147
|
+
|
148
|
+
txvals = np.asarray(txvals)
|
149
|
+
tyvals = np.asarray(tyvals)
|
150
|
+
fvals = np.asarray(fvals)
|
151
|
+
delayvals = np.asarray(delayvals)
|
152
|
+
magvals = np.asarray(magvals)
|
153
|
+
|
154
|
+
"""
|
155
|
+
##########################
|
156
|
+
#### Animate Spatial #####
|
157
|
+
##########################
|
158
|
+
"""
|
159
|
+
# Spatial Animation setup
|
160
|
+
num_frames = freqs.size
|
161
|
+
|
162
|
+
# Setup plot
|
163
|
+
fig = plt.figure()
|
164
|
+
ax = fig.add_subplot(111)
|
165
|
+
axsc = ax.scatter([], [], s=4)
|
166
|
+
axisscale = 1e-6
|
167
|
+
axisstr = "milli"
|
168
|
+
framescale = 1e6
|
169
|
+
framestr = "M"
|
170
|
+
scaling = scale_r1 * 206264.806247 / axisscale
|
171
|
+
|
172
|
+
|
173
|
+
# Select largest spatial extent
|
174
|
+
cut1 = fvals == np.amin(fvals) # lowest freq for scattering
|
175
|
+
maxv_ = (
|
176
|
+
max(np.amax(np.abs(txvals[cut1])), np.amax(np.abs(tyvals[cut1]))) * scaling * 1.1
|
177
|
+
)
|
178
|
+
|
179
|
+
# Set axes and plot
|
180
|
+
ax.set_ylim(-maxv_, maxv_)
|
181
|
+
ax.set_xlim(-maxv_, maxv_)
|
182
|
+
ax.set_ylabel(f"$\\theta_Y$ [{axisstr}arcsec]", size=14)
|
183
|
+
ax.set_xlabel(f"$\\theta_X$ [{axisstr}arcsec]", size=14)
|
184
|
+
ax.set_facecolor("black")
|
185
|
+
cmap = cmaps.gray
|
186
|
+
norm = LogNorm(vmin=1e-3, vmax=1)
|
187
|
+
axsc.set_cmap(cmap)
|
188
|
+
axsc.set_norm(norm)
|
189
|
+
cb = fig.colorbar(cmaps.ScalarMappable(norm=norm, cmap=cmap), ax=ax)
|
190
|
+
cb.ax.set_title("Img. Mag.", y=1.02)
|
191
|
+
|
192
|
+
|
193
|
+
# frame animation
|
194
|
+
def update(i):
|
195
|
+
tcutt = fvals == freqs[i]
|
196
|
+
data = np.stack([txvals[tcutt] * scaling, tyvals[tcutt] * scaling]).T
|
197
|
+
axsc.set_offsets(data)
|
198
|
+
axsc.set_array(np.abs(magvals[tcutt]))
|
199
|
+
ax.set_title(f"Freq: {freqs[i]/framescale:.0f} [{framestr}Hz]", size=14)
|
200
|
+
return (axsc,)
|
201
|
+
|
202
|
+
|
203
|
+
# image framing
|
204
|
+
image_duration = 2 # seconds
|
205
|
+
frame_interval = 30e-3 # seconds between frames
|
206
|
+
total_aniframes = image_duration / frame_interval
|
207
|
+
stepsize = np.ceil(freqs.size / total_aniframes).astype(int)
|
208
|
+
|
209
|
+
if stepsize == 0:
|
210
|
+
stepsize = 1
|
211
|
+
|
212
|
+
frame_numbers = np.arange(0, freqs.size, step=stepsize)
|
213
|
+
|
214
|
+
ani = matplotlib.animation.FuncAnimation(
|
215
|
+
fig, update, frames=frame_numbers, interval=30, blit=True
|
216
|
+
)
|
217
|
+
|
218
|
+
# save
|
219
|
+
save_path = Path.cwd()
|
220
|
+
save_path = save_path / "multilens_spatial_freqslice.gif"
|
221
|
+
ani.save(filename=str(save_path), writer="pillow")
|
222
|
+
|
223
|
+
|
224
|
+
"""
|
225
|
+
###########################
|
226
|
+
#### Animate Temporal #####
|
227
|
+
###########################
|
228
|
+
"""
|
229
|
+
# setup time
|
230
|
+
total_frames = 100
|
231
|
+
left_edge = -total_frames // 4
|
232
|
+
right_edge = total_frames + left_edge
|
233
|
+
time_res = 2.56e-6 # s
|
234
|
+
trange = np.arange(-1, total_frames + 2) * time_res + left_edge * time_res
|
235
|
+
|
236
|
+
# setup figure
|
237
|
+
fig = plt.figure()
|
238
|
+
ax = fig.add_subplot(111)
|
239
|
+
axsc = ax.scatter([], [], s=4)
|
240
|
+
time_axis_scale = 1e-3
|
241
|
+
time_axis_str = "m"
|
242
|
+
freq_axis_scale = 1e6
|
243
|
+
freq_axis_str = "M"
|
244
|
+
ax.set_ylim(400, 800)
|
245
|
+
ax.set_xlim(
|
246
|
+
left_edge * time_res / time_axis_scale, right_edge * time_res / time_axis_scale
|
247
|
+
)
|
248
|
+
ax.set_ylabel(f"Freq. [{freq_axis_str}Hz]")
|
249
|
+
ax.set_xlabel(f"Time [{time_axis_str}s]")
|
250
|
+
cmap = cmaps.binary
|
251
|
+
norm = LogNorm(vmin=1e-3, vmax=1)
|
252
|
+
axsc.set_cmap(cmap)
|
253
|
+
axsc.set_norm(norm)
|
254
|
+
cb = fig.colorbar(cmaps.ScalarMappable(norm=norm, cmap=cmap), ax=ax)
|
255
|
+
cb.ax.set_title("Img. Mag.")
|
256
|
+
|
257
|
+
# image framing
|
258
|
+
image_duration = 2 # seconds
|
259
|
+
frame_interval = 30e-3 # seconds between frames
|
260
|
+
total_aniframes = image_duration / frame_interval
|
261
|
+
stepsize = np.ceil((trange.size - 1) / total_aniframes).astype(int)
|
262
|
+
|
263
|
+
if stepsize == 0:
|
264
|
+
stepsize = 1
|
265
|
+
|
266
|
+
trange_inds = np.arange(0, trange.size - 1, step=stepsize)
|
267
|
+
|
268
|
+
|
269
|
+
# animate frame
|
270
|
+
def update(i):
|
271
|
+
tcutt = (delayvals > trange[trange_inds[i]]) * (
|
272
|
+
delayvals <= trange[trange_inds[i + 1]]
|
273
|
+
)
|
274
|
+
data = np.stack(
|
275
|
+
[delayvals[tcutt] / time_axis_scale, fvals[tcutt] / freq_axis_scale]
|
276
|
+
).T
|
277
|
+
axsc.set_offsets(data)
|
278
|
+
axsc.set_array(np.abs(magvals[tcutt]))
|
279
|
+
return (axsc,)
|
280
|
+
|
281
|
+
|
282
|
+
# animate
|
283
|
+
ani = matplotlib.animation.FuncAnimation(
|
284
|
+
fig, update, frames=trange_inds.size - 1, interval=30, blit=True
|
285
|
+
)
|
286
|
+
|
287
|
+
# save
|
288
|
+
save_path = Path.cwd()
|
289
|
+
save_path = save_path / "multilens_baseband_arrival.gif"
|
290
|
+
ani.save(filename=str(save_path), writer="pillow")
|
291
|
+
|
292
|
+
|
293
|
+
"""
|
294
|
+
#######################################
|
295
|
+
#### Animate Temporal and Spatial #####
|
296
|
+
#######################################
|
297
|
+
"""
|
298
|
+
# setup time
|
299
|
+
total_frames = 100
|
300
|
+
left_edge = -total_frames // 4
|
301
|
+
right_edge = total_frames + left_edge
|
302
|
+
time_res = 2.56e-6 # s
|
303
|
+
trange = np.arange(-1, total_frames + 2) * time_res + left_edge * time_res
|
304
|
+
fmin = np.amin(fvals)
|
305
|
+
|
306
|
+
# Setup plot
|
307
|
+
fig = plt.figure()
|
308
|
+
ax = fig.add_subplot(111)
|
309
|
+
axsc = ax.scatter([], [], s=4)
|
310
|
+
axisscale = 1e-6
|
311
|
+
axisstr = "milli"
|
312
|
+
framescale = 1e6
|
313
|
+
framestr = "M"
|
314
|
+
scaling = scale_r1 * 206264.806247 / axisscale
|
315
|
+
|
316
|
+
# Select largest spatial extent
|
317
|
+
cut1 = fvals == np.amin(fmin) # lowest freq for scattering
|
318
|
+
maxv_ = (
|
319
|
+
max(np.amax(np.abs(txvals[cut1])), np.amax(np.abs(tyvals[cut1]))) * scaling * 1.1
|
320
|
+
)
|
321
|
+
|
322
|
+
# Set axes and plot
|
323
|
+
ax.set_ylim(-maxv_, maxv_)
|
324
|
+
ax.set_xlim(-maxv_, maxv_)
|
325
|
+
ax.set_ylabel(f"$\\theta_Y$ [{axisstr}arcsec]", size=14)
|
326
|
+
ax.set_xlabel(f"$\\theta_X$ [{axisstr}arcsec]", size=14)
|
327
|
+
ax.set_facecolor("black")
|
328
|
+
cmap = cmaps.gray
|
329
|
+
norm = LogNorm(vmin=1e-3, vmax=1)
|
330
|
+
axsc.set_cmap(cmap)
|
331
|
+
axsc.set_norm(norm)
|
332
|
+
cb = fig.colorbar(cmaps.ScalarMappable(norm=norm, cmap=cmap), ax=ax)
|
333
|
+
cb.ax.set_title("Img. Mag.", y=1.02)
|
334
|
+
|
335
|
+
# image framing
|
336
|
+
image_duration = 2 # seconds
|
337
|
+
frame_interval = 30e-3 # seconds between frames
|
338
|
+
total_aniframes = image_duration / frame_interval
|
339
|
+
stepsize = np.ceil((trange.size - 1) / total_aniframes).astype(int)
|
340
|
+
|
341
|
+
if stepsize == 0:
|
342
|
+
stepsize = 1
|
343
|
+
|
344
|
+
trange_inds = np.arange(0, trange.size - 1, step=stepsize)
|
345
|
+
|
346
|
+
|
347
|
+
# frame animation
|
348
|
+
def update(i):
|
349
|
+
tcutt = (delayvals[cut1] > trange[trange_inds[i]]) * (
|
350
|
+
delayvals[cut1] <= trange[trange_inds[i + 1]]
|
351
|
+
)
|
352
|
+
|
353
|
+
data = np.stack([txvals[cut1][tcutt] * scaling, tyvals[cut1][tcutt] * scaling]).T
|
354
|
+
axsc.set_offsets(data)
|
355
|
+
axsc.set_array(np.abs(magvals[cut1][tcutt]))
|
356
|
+
ax.set_title(f"Freq: {fmin/framescale:.0f} [{framestr}Hz] ", size=14)
|
357
|
+
return (axsc,)
|
358
|
+
|
359
|
+
|
360
|
+
# animate
|
361
|
+
ani = matplotlib.animation.FuncAnimation(
|
362
|
+
fig, update, frames=trange_inds.size - 1, interval=30, blit=True
|
363
|
+
)
|
364
|
+
|
365
|
+
# save
|
366
|
+
save_path = Path.cwd()
|
367
|
+
save_path = save_path / "multilens_baseband_spatial_arrival.gif"
|
368
|
+
ani.save(filename=str(save_path), writer="pillow")
|