rwlenspy 1.1.1__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.
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.
@@ -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
+
@@ -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`
@@ -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")