LightWave2D 0.0.6__tar.gz → 0.0.8__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.
- lightwave2d-0.0.8/LightWave2D/VERSION +1 -0
- lightwave2d-0.0.8/LightWave2D/__pycache__/__init__.cpython-311.pyc +0 -0
- {lightwave2d-0.0.6 → lightwave2d-0.0.8}/LightWave2D/__pycache__/components.cpython-311.pyc +0 -0
- {lightwave2d-0.0.6 → lightwave2d-0.0.8}/LightWave2D/__pycache__/detector.cpython-311.pyc +0 -0
- lightwave2d-0.0.8/LightWave2D/__pycache__/experiment.cpython-311.pyc +0 -0
- {lightwave2d-0.0.6 → lightwave2d-0.0.8}/LightWave2D/__pycache__/grid.cpython-311.pyc +0 -0
- {lightwave2d-0.0.6 → lightwave2d-0.0.8}/LightWave2D/__pycache__/physics.cpython-311.pyc +0 -0
- lightwave2d-0.0.8/LightWave2D/__pycache__/pml.cpython-311.pyc +0 -0
- lightwave2d-0.0.8/LightWave2D/__pycache__/source.cpython-311.pyc +0 -0
- {lightwave2d-0.0.6 → lightwave2d-0.0.8}/LightWave2D/__pycache__/utils.cpython-311.pyc +0 -0
- {lightwave2d-0.0.6 → lightwave2d-0.0.8}/LightWave2D/components.py +24 -17
- {lightwave2d-0.0.6 → lightwave2d-0.0.8}/LightWave2D/experiment.py +65 -41
- {lightwave2d-0.0.6 → lightwave2d-0.0.8}/LightWave2D/grid.py +2 -0
- {lightwave2d-0.0.6 → lightwave2d-0.0.8}/LightWave2D/pml.py +0 -1
- {lightwave2d-0.0.6 → lightwave2d-0.0.8}/LightWave2D/source.py +115 -51
- {lightwave2d-0.0.6 → lightwave2d-0.0.8}/LightWave2D.egg-info/PKG-INFO +1 -1
- {lightwave2d-0.0.6 → lightwave2d-0.0.8}/PKG-INFO +1 -1
- lightwave2d-0.0.8/developments/testings_0.py +80 -0
- lightwave2d-0.0.6/LightWave2D/VERSION +0 -1
- lightwave2d-0.0.6/LightWave2D/__pycache__/__init__.cpython-311.pyc +0 -0
- lightwave2d-0.0.6/LightWave2D/__pycache__/experiment.cpython-311.pyc +0 -0
- lightwave2d-0.0.6/LightWave2D/__pycache__/pml.cpython-311.pyc +0 -0
- lightwave2d-0.0.6/LightWave2D/__pycache__/source.cpython-311.pyc +0 -0
- lightwave2d-0.0.6/developments/testings_0.py +0 -82
- {lightwave2d-0.0.6 → lightwave2d-0.0.8}/LICENSE +0 -0
- {lightwave2d-0.0.6 → lightwave2d-0.0.8}/LightWave2D/__init__.py +0 -0
- {lightwave2d-0.0.6 → lightwave2d-0.0.8}/LightWave2D/detector.py +0 -0
- {lightwave2d-0.0.6 → lightwave2d-0.0.8}/LightWave2D/directories.py +0 -0
- {lightwave2d-0.0.6 → lightwave2d-0.0.8}/LightWave2D/physics.py +0 -0
- {lightwave2d-0.0.6 → lightwave2d-0.0.8}/LightWave2D/utils.py +0 -0
- {lightwave2d-0.0.6 → lightwave2d-0.0.8}/LightWave2D.egg-info/SOURCES.txt +0 -0
- {lightwave2d-0.0.6 → lightwave2d-0.0.8}/LightWave2D.egg-info/dependency_links.txt +0 -0
- {lightwave2d-0.0.6 → lightwave2d-0.0.8}/LightWave2D.egg-info/requires.txt +0 -0
- {lightwave2d-0.0.6 → lightwave2d-0.0.8}/LightWave2D.egg-info/top_level.txt +0 -0
- {lightwave2d-0.0.6 → lightwave2d-0.0.8}/MANIFEST.in +0 -0
- {lightwave2d-0.0.6 → lightwave2d-0.0.8}/README.rst +0 -0
- {lightwave2d-0.0.6 → lightwave2d-0.0.8}/setup.cfg +0 -0
- {lightwave2d-0.0.6 → lightwave2d-0.0.8}/setup.py +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
0.0.8
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -4,12 +4,10 @@
|
|
|
4
4
|
from typing import NoReturn, Union, Tuple
|
|
5
5
|
from pydantic.dataclasses import dataclass
|
|
6
6
|
import numpy
|
|
7
|
-
from LightWave2D.physics import Physics
|
|
8
7
|
import matplotlib.pyplot as plt
|
|
9
8
|
import shapely.geometry as geo
|
|
10
9
|
from matplotlib.patches import PathPatch
|
|
11
10
|
from matplotlib import patches
|
|
12
|
-
import numpy as np
|
|
13
11
|
from matplotlib.path import Path
|
|
14
12
|
from matplotlib.collections import PatchCollection
|
|
15
13
|
from LightWave2D.grid import Grid
|
|
@@ -36,12 +34,14 @@ class BaseComponent():
|
|
|
36
34
|
edgecolor (str): The color of the component's edge.
|
|
37
35
|
alpha (float): Transparency level of the component.
|
|
38
36
|
rotation (float): Rotation angle of the component.
|
|
37
|
+
sigma (float): Conductivity of the component.
|
|
39
38
|
"""
|
|
40
39
|
grid: Grid
|
|
41
40
|
facecolor: str = 'lightblue'
|
|
42
41
|
edgecolor: str = 'blue'
|
|
43
42
|
alpha: float = 0.3
|
|
44
43
|
rotation: float = 0
|
|
44
|
+
sigma: float = 0
|
|
45
45
|
|
|
46
46
|
def __post_init__(self):
|
|
47
47
|
x0, y0 = self.position
|
|
@@ -63,14 +63,10 @@ class BaseComponent():
|
|
|
63
63
|
|
|
64
64
|
self.path = Path(self.polygon.exterior.coords)
|
|
65
65
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
coordinates = numpy.c_[x_mesh.T.flatten(), y_mesh.T.flatten()]
|
|
66
|
+
coordinates = numpy.c_[self.grid.x_mesh.T.flatten(), self.grid.y_mesh.T.flatten()]
|
|
69
67
|
|
|
70
68
|
self.idx = self.path.contains_points(coordinates).astype(bool).reshape(self.epsilon_r_mesh.shape)
|
|
71
69
|
|
|
72
|
-
self.epsilon_r_mesh[self.idx] = self.epsilon_r
|
|
73
|
-
|
|
74
70
|
def add_to_ax(self, ax: plt.axis) -> PatchCollection:
|
|
75
71
|
"""
|
|
76
72
|
Add the component to the provided axis.
|
|
@@ -82,8 +78,8 @@ class BaseComponent():
|
|
|
82
78
|
PatchCollection: The collection of patches added to the axis.
|
|
83
79
|
"""
|
|
84
80
|
path = Path.make_compound_path(
|
|
85
|
-
Path(
|
|
86
|
-
*[Path(
|
|
81
|
+
Path(numpy.asarray(self.polygon.exterior.coords)[:, :2]),
|
|
82
|
+
*[Path(numpy.asarray(ring.coords)[:, :2]) for ring in self.polygon.interiors])
|
|
87
83
|
|
|
88
84
|
patch = PathPatch(path, facecolor=self.facecolor, edgecolor=self.edgecolor, alpha=0.4)
|
|
89
85
|
collection = PatchCollection([patch], facecolor=self.facecolor, edgecolor=self.edgecolor, alpha=self.alpha)
|
|
@@ -107,25 +103,36 @@ class BaseComponent():
|
|
|
107
103
|
|
|
108
104
|
plt.show()
|
|
109
105
|
|
|
110
|
-
def
|
|
106
|
+
def add_to_epsilon_r_mesh(self, epsilon_r_mesh: numpy.ndarray) -> NoReturn:
|
|
111
107
|
"""
|
|
112
108
|
Add the component's permittivity to the provided mesh.
|
|
113
109
|
|
|
114
110
|
Args:
|
|
115
|
-
epsilon_r_mesh (
|
|
111
|
+
epsilon_r_mesh (numpy.ndarray): The permittivity mesh to be updated.
|
|
112
|
+
"""
|
|
113
|
+
epsilon_r_mesh[self.idx] = self.epsilon_r
|
|
114
|
+
|
|
115
|
+
def add_to_sigma_mesh(self, sigma_mesh: numpy.ndarray) -> NoReturn:
|
|
116
|
+
"""
|
|
117
|
+
Add the component's sigma to the provided mesh.
|
|
118
|
+
|
|
119
|
+
Args:
|
|
120
|
+
sigma_mesh (numpy.ndarray): The permittivity mesh to be updated.
|
|
116
121
|
"""
|
|
117
|
-
|
|
122
|
+
sigma_mesh[self.idx] = self.sigma
|
|
118
123
|
|
|
119
124
|
def add_non_linear_effect_to_field(self, field: numpy.ndarray) -> NoReturn:
|
|
120
125
|
"""
|
|
121
126
|
Add non-linear effects to the field.
|
|
122
127
|
|
|
123
128
|
Args:
|
|
124
|
-
field (
|
|
129
|
+
field (numpy.ndarray): The field to which non-linear effects will be added.
|
|
125
130
|
"""
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
131
|
+
return field
|
|
132
|
+
chi_3 = 1e-8 # Third-order non-linear susceptibility (example value)
|
|
133
|
+
intensity = numpy.abs(field) ** 2
|
|
134
|
+
nonlinear_term = chi_3 * intensity * field
|
|
135
|
+
return field + self.grid.dt * nonlinear_term
|
|
129
136
|
|
|
130
137
|
|
|
131
138
|
@dataclass(config=config_dict)
|
|
@@ -231,7 +238,7 @@ class Triangle(BaseComponent):
|
|
|
231
238
|
"""
|
|
232
239
|
Compute the polygon of the triangular scatterer.
|
|
233
240
|
"""
|
|
234
|
-
height = (
|
|
241
|
+
height = (numpy.sqrt(3) / 2) * self.side_length
|
|
235
242
|
|
|
236
243
|
# Vertices of an equilateral triangle centered at (coordinate.x, coordinate.y)
|
|
237
244
|
p0 = (self.coordinate.x - self.side_length / 2, self.coordinate.y - height / 3)
|
|
@@ -6,8 +6,8 @@ from typing import Tuple, NoReturn, Optional, Union, List
|
|
|
6
6
|
import numpy
|
|
7
7
|
from LightWave2D.physics import Physics
|
|
8
8
|
from LightWave2D.grid import Grid
|
|
9
|
-
from LightWave2D
|
|
10
|
-
from LightWave2D
|
|
9
|
+
from LightWave2D import components
|
|
10
|
+
from LightWave2D import source
|
|
11
11
|
from LightWave2D.detector import PointDetector
|
|
12
12
|
from LightWave2D.pml import PML
|
|
13
13
|
from MPSPlots import colormaps
|
|
@@ -116,82 +116,89 @@ class Experiment:
|
|
|
116
116
|
return self.pml
|
|
117
117
|
|
|
118
118
|
@add_to_component
|
|
119
|
-
def add_circle(self, **kwargs) -> Circle:
|
|
119
|
+
def add_circle(self, **kwargs) -> components.Circle:
|
|
120
120
|
"""
|
|
121
|
-
Method to add a Circle to the simulation.
|
|
121
|
+
Method to add a components.Circle to the simulation.
|
|
122
122
|
"""
|
|
123
|
-
return Circle(grid=self.grid, **kwargs)
|
|
123
|
+
return components.Circle(grid=self.grid, **kwargs)
|
|
124
124
|
|
|
125
125
|
@add_to_component
|
|
126
|
-
def add_ellipse(self, **kwargs) -> Ellipse:
|
|
126
|
+
def add_ellipse(self, **kwargs) -> components.Ellipse:
|
|
127
127
|
"""
|
|
128
|
-
Method to add a Ellipse to the simulation.
|
|
128
|
+
Method to add a components.Ellipse to the simulation.
|
|
129
129
|
"""
|
|
130
|
-
return Ellipse(grid=self.grid, **kwargs)
|
|
130
|
+
return components.Ellipse(grid=self.grid, **kwargs)
|
|
131
131
|
|
|
132
132
|
@add_to_component
|
|
133
|
-
def add_square(self, **kwargs) -> Square:
|
|
133
|
+
def add_square(self, **kwargs) -> components.Square:
|
|
134
134
|
"""
|
|
135
135
|
open()
|
|
136
|
-
Method to add a Square to the simulation.
|
|
136
|
+
Method to add a components.Square to the simulation.
|
|
137
137
|
"""
|
|
138
|
-
return Square(grid=self.grid, **kwargs)
|
|
138
|
+
return components.Square(grid=self.grid, **kwargs)
|
|
139
139
|
|
|
140
140
|
@add_to_component
|
|
141
|
-
def add_triangle(self, **kwargs) ->
|
|
141
|
+
def add_triangle(self, **kwargs) -> components.Triangle:
|
|
142
142
|
"""
|
|
143
|
-
Method to add a Triangle to the simulation.
|
|
143
|
+
Method to add a components.Triangle to the simulation.
|
|
144
144
|
"""
|
|
145
|
-
return Triangle(grid=self.grid, **kwargs)
|
|
145
|
+
return components.Triangle(grid=self.grid, **kwargs)
|
|
146
146
|
|
|
147
147
|
@add_to_component
|
|
148
|
-
def add_lense(self, **kwargs) ->
|
|
148
|
+
def add_lense(self, **kwargs) -> components.Lense:
|
|
149
149
|
"""
|
|
150
|
-
Method to add a Lense to the simulation.
|
|
150
|
+
Method to add a components.Lense to the simulation.
|
|
151
151
|
"""
|
|
152
|
-
return Lense(grid=self.grid, **kwargs)
|
|
152
|
+
return components.Lense(grid=self.grid, **kwargs)
|
|
153
153
|
|
|
154
154
|
@add_to_component
|
|
155
|
-
def add_grating(self, **kwargs) ->
|
|
155
|
+
def add_grating(self, **kwargs) -> components.Grating:
|
|
156
156
|
"""
|
|
157
|
-
Method to add a Grating to the simulation.
|
|
157
|
+
Method to add a components.Grating to the simulation.
|
|
158
158
|
"""
|
|
159
|
-
return Grating(grid=self.grid, **kwargs)
|
|
159
|
+
return components.Grating(grid=self.grid, **kwargs)
|
|
160
160
|
|
|
161
161
|
@add_to_component
|
|
162
|
-
def add_ring_resonator(self, **kwargs) ->
|
|
162
|
+
def add_ring_resonator(self, **kwargs) -> components.RingResonator:
|
|
163
163
|
"""
|
|
164
|
-
Method to add a RingResonator to the simulation.
|
|
164
|
+
Method to add a components.RingResonator to the simulation.
|
|
165
165
|
"""
|
|
166
|
-
return RingResonator(grid=self.grid, **kwargs)
|
|
166
|
+
return components.RingResonator(grid=self.grid, **kwargs)
|
|
167
167
|
|
|
168
168
|
@add_to_component
|
|
169
|
-
def add_waveguide(self, **kwargs) -> Waveguide:
|
|
169
|
+
def add_waveguide(self, **kwargs) -> components.Waveguide:
|
|
170
170
|
"""
|
|
171
|
-
Method to add a Waveguide to the simulation.
|
|
171
|
+
Method to add a components.Waveguide to the simulation.
|
|
172
172
|
"""
|
|
173
|
-
return Waveguide(grid=self.grid, **kwargs)
|
|
173
|
+
return components.Waveguide(grid=self.grid, **kwargs)
|
|
174
174
|
|
|
175
175
|
@add_to_source
|
|
176
|
-
def add_point_source(self, **kwargs) -> PointSource:
|
|
176
|
+
def add_point_source(self, **kwargs) -> source.PointSource:
|
|
177
177
|
"""
|
|
178
|
-
Method to add a PointSource to the simulation.
|
|
178
|
+
Method to add a source.PointSource to the simulation.
|
|
179
179
|
"""
|
|
180
|
-
return PointSource(grid=self.grid, **kwargs)
|
|
180
|
+
return source.PointSource(grid=self.grid, **kwargs)
|
|
181
181
|
|
|
182
182
|
@add_to_source
|
|
183
|
-
def add_point_impulsion(self, **kwargs) -> PointImpulsion:
|
|
183
|
+
def add_point_impulsion(self, **kwargs) -> source.PointImpulsion:
|
|
184
184
|
"""
|
|
185
|
-
Method to add a Impulsion to the simulation.
|
|
185
|
+
Method to add a source.Impulsion to the simulation.
|
|
186
186
|
"""
|
|
187
|
-
return PointImpulsion(grid=self.grid, **kwargs)
|
|
187
|
+
return source.PointImpulsion(grid=self.grid, **kwargs)
|
|
188
188
|
|
|
189
189
|
@add_to_source
|
|
190
|
-
def add_line_source(self, **kwargs) -> LineSource:
|
|
190
|
+
def add_line_source(self, **kwargs) -> source.LineSource:
|
|
191
191
|
"""
|
|
192
|
-
Method to add a LineSource to the simulation.
|
|
192
|
+
Method to add a source.LineSource to the simulation.
|
|
193
193
|
"""
|
|
194
|
-
return LineSource(grid=self.grid, **kwargs)
|
|
194
|
+
return source.LineSource(grid=self.grid, **kwargs)
|
|
195
|
+
|
|
196
|
+
@add_to_source
|
|
197
|
+
def add_line_impulsion(self, **kwargs) -> source.LineImpulsion:
|
|
198
|
+
"""
|
|
199
|
+
Method to add a source.LineSource to the simulation.
|
|
200
|
+
"""
|
|
201
|
+
return source.LineImpulsion(grid=self.grid, **kwargs)
|
|
195
202
|
|
|
196
203
|
@add_to_detector
|
|
197
204
|
def add_point_detector(self, **kwargs) -> PointDetector:
|
|
@@ -211,6 +218,11 @@ class Experiment:
|
|
|
211
218
|
sigma_x, sigma_y = self.pml.sigma_x, self.pml.sigma_y
|
|
212
219
|
else:
|
|
213
220
|
sigma_x = sigma_y = numpy.zeros(self.grid.shape)
|
|
221
|
+
|
|
222
|
+
for component in self.components:
|
|
223
|
+
component.add_to_sigma_mesh(sigma_x)
|
|
224
|
+
component.add_to_sigma_mesh(sigma_y)
|
|
225
|
+
|
|
214
226
|
return sigma_x, sigma_y
|
|
215
227
|
|
|
216
228
|
def get_epsilon(self) -> numpy.ndarray:
|
|
@@ -222,7 +234,7 @@ class Experiment:
|
|
|
222
234
|
"""
|
|
223
235
|
epsilon_r_mesh = numpy.ones(self.grid.shape)
|
|
224
236
|
for component in self.components:
|
|
225
|
-
component.
|
|
237
|
+
component.add_to_epsilon_r_mesh(epsilon_r_mesh)
|
|
226
238
|
|
|
227
239
|
return epsilon_r_mesh * Physics.epsilon_0
|
|
228
240
|
|
|
@@ -264,13 +276,17 @@ class Experiment:
|
|
|
264
276
|
|
|
265
277
|
Ez[1:-1, 1:-1] += eps_factor[1:-1, 1:-1] * (dHy_dx - dHx_dy)
|
|
266
278
|
|
|
267
|
-
|
|
268
|
-
|
|
279
|
+
# Apply absorption (losses) to the electric field Ez
|
|
280
|
+
absorption_factor = 1 - (sigma_x + sigma_y) * eps_factor / 2
|
|
281
|
+
absorption_factor = numpy.clip(absorption_factor, 0, 1) # Absorption cliped to [0, 1] to ensure stability
|
|
282
|
+
|
|
283
|
+
Ez *= absorption_factor
|
|
269
284
|
|
|
270
|
-
|
|
285
|
+
for component in self.components:
|
|
286
|
+
Ez = component.add_non_linear_effect_to_field(Ez)
|
|
271
287
|
|
|
272
|
-
for
|
|
273
|
-
|
|
288
|
+
for element in self.sources:
|
|
289
|
+
element.add_source_to_field(Ez, time=t)
|
|
274
290
|
|
|
275
291
|
self.Ez_t[iteration] = Ez
|
|
276
292
|
|
|
@@ -342,6 +358,7 @@ class Experiment:
|
|
|
342
358
|
frame_number (int): The index of the frame to be saved.
|
|
343
359
|
filename (str): The file path where the image will be saved.
|
|
344
360
|
dpi (int): The resolution of the saved image in dots per inch.
|
|
361
|
+
show_intensity (bool): If True, the intensity is shown instead of amplitude
|
|
345
362
|
colormap (Optional[Union[str, object]]): The colormap used for visualizing the data. Defaults to a predefined blue-black-red colormap.
|
|
346
363
|
|
|
347
364
|
Returns:
|
|
@@ -394,6 +411,7 @@ class Experiment:
|
|
|
394
411
|
skip_frame: int = 10,
|
|
395
412
|
scale_max: float = 5,
|
|
396
413
|
unit_size: int = 6,
|
|
414
|
+
auto_adjust_clim: bool = False,
|
|
397
415
|
colormap: Optional[Union[str, object]] = colormaps.blue_black_red) -> animation.FuncAnimation:
|
|
398
416
|
"""
|
|
399
417
|
Renders the propagation of a field as an animation using matplotlib.
|
|
@@ -403,6 +421,7 @@ class Experiment:
|
|
|
403
421
|
|
|
404
422
|
Args:
|
|
405
423
|
skip_frame (int): The number of time steps to skip between frames in the animation.
|
|
424
|
+
auto_adjust_clim (bool): TODO
|
|
406
425
|
colormap (Optional[Union[str, object]]): The colormap used for visualizing the data. Defaults to a predefined blue-black-red colormap.
|
|
407
426
|
unit_size (float): The base unit size for scaling the plot elements.
|
|
408
427
|
|
|
@@ -437,6 +456,11 @@ class Experiment:
|
|
|
437
456
|
"""
|
|
438
457
|
field_t = self.Ez_t[frame].T
|
|
439
458
|
field_artist.set_array(field_t)
|
|
459
|
+
|
|
460
|
+
if auto_adjust_clim:
|
|
461
|
+
max_amplitude = abs(field_t).max() / scale_max
|
|
462
|
+
field_artist.set_clim(vmin=-max_amplitude, vmax=max_amplitude)
|
|
463
|
+
|
|
440
464
|
return [artist for artist in artist_list]
|
|
441
465
|
|
|
442
466
|
def init_func():
|
|
@@ -52,6 +52,8 @@ class Grid:
|
|
|
52
52
|
self.x_stamp = numpy.arange(self.n_x) * self.dx
|
|
53
53
|
self.y_stamp = numpy.arange(self.n_y) * self.dy
|
|
54
54
|
|
|
55
|
+
self.x_mesh, self.y_mesh = numpy.meshgrid(self.x_stamp, self.y_stamp)
|
|
56
|
+
|
|
55
57
|
self.polygon = geo.Polygon([
|
|
56
58
|
(self.x_stamp[0], self.y_stamp[0]),
|
|
57
59
|
(self.x_stamp[0], self.y_stamp[-1]),
|
|
@@ -47,7 +47,6 @@ class PML:
|
|
|
47
47
|
opposite_width = f"{100 - opposite_width}%"
|
|
48
48
|
self.width_start = self.grid.get_coordinate(x=self.width, y=self.width)
|
|
49
49
|
self.width_stop = self.grid.get_coordinate(x=opposite_width, y=opposite_width)
|
|
50
|
-
self.width_stop_ = self.grid.get_coordinate(x="100%", y="100%")
|
|
51
50
|
|
|
52
51
|
bottom_boundary = y_mesh < self.width_start.y
|
|
53
52
|
left_boundary = x_mesh < self.width_start.x
|
|
@@ -25,15 +25,16 @@ config_dict = {
|
|
|
25
25
|
|
|
26
26
|
|
|
27
27
|
@dataclass(kw_only=True, config=config_dict)
|
|
28
|
-
class BaseSource
|
|
28
|
+
class BaseSource:
|
|
29
29
|
"""
|
|
30
|
-
|
|
30
|
+
Base class for sources in a 2D light wave simulation.
|
|
31
31
|
|
|
32
32
|
Attributes:
|
|
33
33
|
grid (Grid): The grid of the simulation mesh.
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
34
|
+
facecolor (str): Color of the source face.
|
|
35
|
+
edgecolor (str): Color of the source edge.
|
|
36
|
+
alpha (float): Transparency level of the source.
|
|
37
|
+
rotation (float): Rotation angle of the source.
|
|
37
38
|
"""
|
|
38
39
|
grid: Grid
|
|
39
40
|
facecolor: str = 'red'
|
|
@@ -42,73 +43,63 @@ class BaseSource():
|
|
|
42
43
|
rotation: float = 0
|
|
43
44
|
|
|
44
45
|
def __post_init__(self):
|
|
45
|
-
|
|
46
|
-
self.coordinate = self.grid.get_coordinate(x=x0, y=y0)
|
|
47
|
-
|
|
46
|
+
self.coordinate = self.grid.get_coordinate(x=self.position[0], y=self.position[1])
|
|
48
47
|
self.build_object()
|
|
49
48
|
|
|
50
49
|
def build_object(self) -> NoReturn:
|
|
51
50
|
"""
|
|
52
|
-
Build the
|
|
51
|
+
Build the source object and initialize its properties.
|
|
53
52
|
"""
|
|
54
53
|
self.compute_polygon()
|
|
55
|
-
|
|
56
54
|
self.path = Path(self.polygon.exterior.coords)
|
|
57
|
-
|
|
58
55
|
self.path = self.path.transformed(matplotlib.transforms.Affine2D().rotate_around(self.coordinate.x, self.coordinate.y, self.rotation))
|
|
59
|
-
|
|
60
56
|
x_mesh, y_mesh = numpy.meshgrid(self.grid.x_stamp, self.grid.y_stamp)
|
|
61
|
-
|
|
62
57
|
coordinates = numpy.c_[x_mesh.T.flatten(), y_mesh.T.flatten()]
|
|
58
|
+
self.idx = self.path.contains_points(coordinates).astype(bool).reshape(self.grid.shape)
|
|
63
59
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
def add_to_ax(self, ax: plt.axis) -> PatchCollection:
|
|
60
|
+
def add_to_ax(self, ax: plt.Axes) -> PatchCollection:
|
|
67
61
|
"""
|
|
68
|
-
Add the
|
|
62
|
+
Add the source to the provided axis as a scatter point or patch.
|
|
69
63
|
|
|
70
64
|
Args:
|
|
71
|
-
ax (
|
|
65
|
+
ax (plt.Axes): The axis to which the source will be added.
|
|
72
66
|
"""
|
|
73
67
|
path = Path.make_compound_path(
|
|
74
68
|
Path(numpy.asarray(self.polygon.exterior.coords)[:, :2]),
|
|
75
|
-
*[Path(numpy.asarray(ring.coords)[:, :2]) for ring in self.polygon.interiors]
|
|
76
|
-
|
|
77
|
-
patch = PathPatch(path,
|
|
78
|
-
collection = PatchCollection([patch],
|
|
79
|
-
|
|
69
|
+
*[Path(numpy.asarray(ring.coords)[:, :2]) for ring in self.polygon.interiors]
|
|
70
|
+
)
|
|
71
|
+
patch = PathPatch(path, facecolor=self.facecolor, edgecolor=self.edgecolor, alpha=self.alpha)
|
|
72
|
+
collection = PatchCollection([patch], facecolor=self.facecolor, edgecolor=self.edgecolor, alpha=self.alpha)
|
|
80
73
|
ax.add_collection(collection, autolim=True)
|
|
81
74
|
ax.autoscale_view()
|
|
82
75
|
return collection
|
|
83
76
|
|
|
84
77
|
def plot(self) -> NoReturn:
|
|
78
|
+
"""
|
|
79
|
+
Plot the source on a 2D grid.
|
|
80
|
+
"""
|
|
85
81
|
figure, ax = plt.subplots(1, 1, figsize=(6, 6))
|
|
86
82
|
ax.set_title('FDTD Simulation')
|
|
87
83
|
ax.set_xlabel(r'x position [$\mu$m]')
|
|
88
|
-
ax.
|
|
84
|
+
ax.set_ylabel(r'y position [$\mu$m]')
|
|
89
85
|
ax.set_aspect('equal')
|
|
90
|
-
|
|
91
86
|
self.add_to_ax(ax)
|
|
92
|
-
ax.autoscale_view()
|
|
93
|
-
|
|
94
87
|
plt.show()
|
|
95
88
|
|
|
96
89
|
|
|
97
|
-
@dataclass(
|
|
90
|
+
@dataclass(config=config_dict)
|
|
98
91
|
class PointSource(BaseSource):
|
|
99
92
|
"""
|
|
100
93
|
Represents a point source in a 2D light wave simulation.
|
|
101
94
|
|
|
102
95
|
Attributes:
|
|
103
|
-
wavelength (float): Wavelength of the source.
|
|
104
|
-
position (
|
|
105
|
-
amplitude (float): Amplitude of the electric field
|
|
96
|
+
wavelength (Union[float, List[float], numpy.ndarray]): Wavelength of the source.
|
|
97
|
+
position (Tuple[Union[float, str], Union[float, str]]): Position (x, y) of the source.
|
|
98
|
+
amplitude (float): Amplitude of the electric field.
|
|
106
99
|
"""
|
|
107
100
|
wavelength: Union[float | List[float] | numpy.ndarray]
|
|
108
101
|
position: Tuple[float | str, float | str]
|
|
109
|
-
amplitude: float
|
|
110
|
-
facecolor: str = 'red'
|
|
111
|
-
edgecolor: str = 'red'
|
|
102
|
+
amplitude: float
|
|
112
103
|
|
|
113
104
|
def __post_init__(self):
|
|
114
105
|
self.wavelength = numpy.atleast_1d(self.wavelength)
|
|
@@ -144,22 +135,21 @@ class PointSource(BaseSource):
|
|
|
144
135
|
field[self.p0.x_index, self.p0.y_index] = self.amplitude / len(self.omega) * numpy.sin(self.omega * time).sum()
|
|
145
136
|
|
|
146
137
|
|
|
147
|
-
@dataclass(
|
|
138
|
+
@dataclass(config=config_dict)
|
|
148
139
|
class PointImpulsion(BaseSource):
|
|
149
140
|
"""
|
|
150
|
-
Represents a point source in a 2D light wave simulation.
|
|
141
|
+
Represents a point impulsion source in a 2D light wave simulation.
|
|
151
142
|
|
|
152
143
|
Attributes:
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
144
|
+
amplitude (float): Amplitude of the electric field.
|
|
145
|
+
duration (float): Duration of the impulsion.
|
|
146
|
+
position (Tuple[Union[float, str], Union[float, str]]): Position (x, y) of the source.
|
|
147
|
+
delay (float): Delay before the impulsion starts.
|
|
156
148
|
"""
|
|
149
|
+
amplitude: float
|
|
157
150
|
duration: float
|
|
158
151
|
position: Tuple[float | str, float | str]
|
|
159
152
|
delay: float = 0
|
|
160
|
-
amplitude: float = 1.0
|
|
161
|
-
facecolor: str = 'red'
|
|
162
|
-
edgecolor: str = 'red'
|
|
163
153
|
|
|
164
154
|
def __post_init__(self):
|
|
165
155
|
x, y = self.position
|
|
@@ -183,10 +173,10 @@ class PointImpulsion(BaseSource):
|
|
|
183
173
|
|
|
184
174
|
def add_source_to_field(self, field: numpy.ndarray, time: float) -> NoReturn:
|
|
185
175
|
"""
|
|
186
|
-
Add the source's effect to the simulation field.
|
|
176
|
+
Add the impulsion source's effect to the simulation field.
|
|
187
177
|
|
|
188
178
|
Args:
|
|
189
|
-
field (
|
|
179
|
+
field (np.ndarray): The simulation field to which the source's effect will be added.
|
|
190
180
|
time (float): The current simulation time.
|
|
191
181
|
"""
|
|
192
182
|
source_field = numpy.exp(-((time - self.delay) / self.duration) ** 2)
|
|
@@ -194,21 +184,21 @@ class PointImpulsion(BaseSource):
|
|
|
194
184
|
field[self.p0.x_index, self.p0.y_index] = self.amplitude * source_field
|
|
195
185
|
|
|
196
186
|
|
|
197
|
-
@dataclass(
|
|
187
|
+
@dataclass(config=config_dict)
|
|
198
188
|
class LineSource(BaseSource):
|
|
199
189
|
"""
|
|
200
190
|
Represents a line source in a 2D light wave simulation.
|
|
201
191
|
|
|
202
192
|
Attributes:
|
|
193
|
+
amplitude (float): Amplitude of the electric field.
|
|
203
194
|
wavelength (float): Wavelength of the source.
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
amplitude (float): Amplitude of the electric field, default is 1.0.
|
|
195
|
+
position_0 (Tuple[Union[float, str], Union[float, str]]): Starting position (x, y) of the source.
|
|
196
|
+
position_1 (Tuple[Union[float, str], Union[float, str]]): Ending position (x, y) of the source.
|
|
207
197
|
"""
|
|
198
|
+
amplitude: float
|
|
208
199
|
wavelength: float
|
|
209
200
|
position_0: Tuple[float | str, float | str]
|
|
210
201
|
position_1: Tuple[float | str, float | str]
|
|
211
|
-
amplitude: float = 1.0
|
|
212
202
|
|
|
213
203
|
def __post_init__(self):
|
|
214
204
|
self.frequency = Physics.c / self.wavelength
|
|
@@ -252,11 +242,85 @@ class LineSource(BaseSource):
|
|
|
252
242
|
|
|
253
243
|
def add_source_to_field(self, field: numpy.ndarray, time: float) -> NoReturn:
|
|
254
244
|
"""
|
|
255
|
-
Add the source's effect to the simulation field.
|
|
245
|
+
Add the line source's effect to the simulation field.
|
|
256
246
|
|
|
257
247
|
Args:
|
|
258
|
-
field (
|
|
248
|
+
field (np.ndarray): The simulation field to which the source's effect will be added.
|
|
259
249
|
time (float): The current simulation time.
|
|
260
250
|
"""
|
|
261
251
|
rows, cols = self.slice_indexes
|
|
262
252
|
field[rows, cols] = self.amplitude * numpy.sin(self.omega * time)
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
@dataclass(config=config_dict)
|
|
256
|
+
class LineImpulsion(BaseSource):
|
|
257
|
+
"""
|
|
258
|
+
Represents a line impulsion source in a 2D light wave simulation.
|
|
259
|
+
|
|
260
|
+
Attributes:
|
|
261
|
+
duration (float): Duration of the impulsion.
|
|
262
|
+
amplitude (float): Amplitude of the electric field.
|
|
263
|
+
position_0 (Tuple[Union[float, str], Union[float, str]]): Starting position (x, y) of the source.
|
|
264
|
+
position_1 (Tuple[Union[float, str], Union[float, str]]): Ending position (x, y) of the source.
|
|
265
|
+
delay (float): Delay before the impulsion starts.
|
|
266
|
+
"""
|
|
267
|
+
duration: float
|
|
268
|
+
amplitude: float
|
|
269
|
+
position_0: Tuple[float | str, float | str]
|
|
270
|
+
position_1: Tuple[float | str, float | str]
|
|
271
|
+
delay: float = 0
|
|
272
|
+
|
|
273
|
+
def __post_init__(self):
|
|
274
|
+
self.build_object()
|
|
275
|
+
|
|
276
|
+
def build_object(self) -> NoReturn:
|
|
277
|
+
"""
|
|
278
|
+
Build the line source object and calculate the line's coordinates.
|
|
279
|
+
"""
|
|
280
|
+
self.p0 = self.grid.get_coordinate(x=self.position_0[0], y=self.position_0[1])
|
|
281
|
+
self.p1 = self.grid.get_coordinate(x=self.position_1[0], y=self.position_1[1])
|
|
282
|
+
|
|
283
|
+
position = bresenham_line(
|
|
284
|
+
x0=self.p0.x_index,
|
|
285
|
+
y0=self.p0.y_index,
|
|
286
|
+
x1=self.p1.x_index,
|
|
287
|
+
y1=self.p1.y_index
|
|
288
|
+
)
|
|
289
|
+
|
|
290
|
+
rows, cols = zip(*position.T)
|
|
291
|
+
self.slice_indexes = rows, cols
|
|
292
|
+
|
|
293
|
+
p0 = geo.Point(self.p0.x, self.p0.y)
|
|
294
|
+
p1 = geo.Point(self.p1.x, self.p1.y)
|
|
295
|
+
self.polygon = geo.LineString((p0, p1))
|
|
296
|
+
|
|
297
|
+
def add_to_ax(self, ax: plt.axis) -> NoReturn:
|
|
298
|
+
"""
|
|
299
|
+
Add the scatterer to the provided axis as a circle.
|
|
300
|
+
|
|
301
|
+
Args:
|
|
302
|
+
ax (Axis): The axis to which the scatterer will be added.
|
|
303
|
+
"""
|
|
304
|
+
ax.plot(
|
|
305
|
+
self.polygon.xy[0],
|
|
306
|
+
self.polygon.xy[1],
|
|
307
|
+
color=self.facecolor,
|
|
308
|
+
label='source'
|
|
309
|
+
)
|
|
310
|
+
|
|
311
|
+
def add_source_to_field(self, field: numpy.ndarray, time: float) -> NoReturn:
|
|
312
|
+
"""
|
|
313
|
+
Add the line impulsion source's effect to the simulation field.
|
|
314
|
+
|
|
315
|
+
Args:
|
|
316
|
+
field (np.ndarray): The simulation field to which the source's effect will be added.
|
|
317
|
+
time (float): The current simulation time.
|
|
318
|
+
"""
|
|
319
|
+
rows, cols = self.slice_indexes
|
|
320
|
+
|
|
321
|
+
source_field = numpy.exp(-((time - self.delay) / self.duration) ** 2)
|
|
322
|
+
|
|
323
|
+
field[rows, cols] = self.amplitude * source_field
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
# -
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Experiment: Circular Scatterer
|
|
3
|
+
==============================
|
|
4
|
+
|
|
5
|
+
This example demonstrates the setup and execution of a circular scatterer experiment using LightWave2D.
|
|
6
|
+
We will define the simulation grid, add a circular scatterer and a line source, apply a perfectly matched layer (PML), run the simulation, and visualize the results.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
# %%
|
|
10
|
+
# Importing the necessary packages
|
|
11
|
+
from LightWave2D.grid import Grid
|
|
12
|
+
from LightWave2D.experiment import Experiment
|
|
13
|
+
from MPSPlots import colormaps
|
|
14
|
+
|
|
15
|
+
# %%
|
|
16
|
+
# Define the simulation grid
|
|
17
|
+
grid = Grid(
|
|
18
|
+
resolution=0.1e-6, # Grid resolution in meters
|
|
19
|
+
size_x=32e-6, # Grid size in the x direction in meters
|
|
20
|
+
size_y=20e-6, # Grid size in the y direction in meters
|
|
21
|
+
n_steps=300 # Number of time steps for the simulation
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
# Initialize the experiment with the defined grid
|
|
25
|
+
experiment = Experiment(grid=grid)
|
|
26
|
+
|
|
27
|
+
# %%
|
|
28
|
+
# Add a circular scatterer to the experiment
|
|
29
|
+
scatterer = experiment.add_circle(
|
|
30
|
+
position=('30%', '50%'), # Center position of the scatterer
|
|
31
|
+
epsilon_r=1.5, # Relative permittivity of the scatterer
|
|
32
|
+
radius=3e-6, # Radius of the circular scatterer in meters
|
|
33
|
+
# sigma=1e6
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
# %%
|
|
37
|
+
# Add a line source to the experiment
|
|
38
|
+
source = experiment.add_line_source(
|
|
39
|
+
wavelength=1550e-9, # Wavelength of the source in meters
|
|
40
|
+
position_0=('10%', '80%'), # Starting position of the source
|
|
41
|
+
position_1=('10%', '20%'), # Ending position of the source
|
|
42
|
+
amplitude=10 # Amplitude of the source
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
# %%
|
|
46
|
+
# Add a perfectly matched layer (PML) to absorb boundary reflections
|
|
47
|
+
experiment.add_pml(
|
|
48
|
+
order=1, # Order of the PML polynomial profile
|
|
49
|
+
width='10%', # Width of the PML region as a percentage of grid size
|
|
50
|
+
sigma_max=5000 # Maximum conductivity for the PML
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
# %%
|
|
54
|
+
# Plot the entire experiment setup
|
|
55
|
+
experiment.plot()
|
|
56
|
+
|
|
57
|
+
# Run the FDTD simulation
|
|
58
|
+
experiment.run_fdtd()
|
|
59
|
+
|
|
60
|
+
# %%
|
|
61
|
+
# Plot the last time frame of the computed fields
|
|
62
|
+
experiment.plot_frame(
|
|
63
|
+
frame_number=-1, # Plot the last frame
|
|
64
|
+
scale_max=1, # Maximum scale for the field visualization
|
|
65
|
+
colormap=colormaps.polytechnique.red_black_blue, # Colormap for the plot
|
|
66
|
+
# show_intensity=True
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
# %%
|
|
70
|
+
# Render an animation of the field propagation over time
|
|
71
|
+
animation = experiment.render_propagation(
|
|
72
|
+
skip_frame=5, # Number of frames to skip in the animation
|
|
73
|
+
unit_size=5, # Size of each unit in the animation
|
|
74
|
+
colormap=colormaps.polytechnique.red_black_blue, # Colormap for the animation
|
|
75
|
+
scale_max=1,
|
|
76
|
+
auto_adjust_clim=True
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
# Save the animation as a GIF file
|
|
80
|
+
animation.save('./circular_scatterer_propagation.gif', writer='Pillow', fps=10)
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
0.0.6
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -1,82 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Experiment: elliptic scatterer
|
|
3
|
-
==============================
|
|
4
|
-
|
|
5
|
-
"""
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
# %%
|
|
9
|
-
# Importing the package
|
|
10
|
-
from LightWave2D.grid import Grid
|
|
11
|
-
from LightWave2D.experiment import Experiment
|
|
12
|
-
from MPSPlots import colormaps
|
|
13
|
-
|
|
14
|
-
# %%
|
|
15
|
-
# We define here the grid on which to build the experiemnt
|
|
16
|
-
grid = Grid(
|
|
17
|
-
resolution=0.05e-6,
|
|
18
|
-
size_x=50e-6,
|
|
19
|
-
size_y=15e-6,
|
|
20
|
-
n_steps=1200
|
|
21
|
-
)
|
|
22
|
-
|
|
23
|
-
experiment = Experiment(grid=grid)
|
|
24
|
-
|
|
25
|
-
# scatterer = experiment.add_ellipse(
|
|
26
|
-
# position=('30%', '40%'),
|
|
27
|
-
# width=4e-6,
|
|
28
|
-
# height=4e-6,
|
|
29
|
-
# epsilon_r=1,
|
|
30
|
-
# )
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
scatterer = experiment.add_waveguide(
|
|
34
|
-
position_0=('0%', '50%'),
|
|
35
|
-
position_1=('100%', '50%'),
|
|
36
|
-
width=2e-6,
|
|
37
|
-
epsilon_r=2,
|
|
38
|
-
)
|
|
39
|
-
|
|
40
|
-
source = experiment.add_line_source(
|
|
41
|
-
wavelength=1550e-9,
|
|
42
|
-
position_0=('20%', '45%'),
|
|
43
|
-
position_1=('20%', '55%'),
|
|
44
|
-
amplitude=10,
|
|
45
|
-
)
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
# source = experiment.add_impulsion(
|
|
49
|
-
# duration=1e-15,
|
|
50
|
-
# delay=0,
|
|
51
|
-
# position=('50%', '50%'),
|
|
52
|
-
# amplitude=1000,
|
|
53
|
-
# )
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
# %%
|
|
57
|
-
# We add a perfectly matched layer to avoid reflection at the boundary of the mesh
|
|
58
|
-
experiment.add_pml(order=1, width='10%', sigma_max=5000)
|
|
59
|
-
|
|
60
|
-
experiment.run_fdtd()
|
|
61
|
-
|
|
62
|
-
experiment.plot()
|
|
63
|
-
|
|
64
|
-
# %%
|
|
65
|
-
# Plotting the last time frame of the computed fields
|
|
66
|
-
# experiment.plot_frame(
|
|
67
|
-
# frame_number=-1,
|
|
68
|
-
# colormap=colormaps.polytechnique.red_black_blue,
|
|
69
|
-
# scale_max=1
|
|
70
|
-
# )
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
animation = experiment.render_propagation(
|
|
74
|
-
skip_frame=10,
|
|
75
|
-
colormap=colormaps.polytechnique.red_black_blue,
|
|
76
|
-
scale_max=2
|
|
77
|
-
)
|
|
78
|
-
|
|
79
|
-
_ = animation.save('./resonator.gif', writer='Pillow', fps=20)
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
# -
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|