LightWave2D 0.0.4__tar.gz → 0.0.6__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.6/LightWave2D/VERSION +1 -0
- lightwave2d-0.0.6/LightWave2D/__pycache__/__init__.cpython-311.pyc +0 -0
- lightwave2d-0.0.6/LightWave2D/__pycache__/components.cpython-311.pyc +0 -0
- lightwave2d-0.0.6/LightWave2D/__pycache__/detector.cpython-311.pyc +0 -0
- lightwave2d-0.0.6/LightWave2D/__pycache__/experiment.cpython-311.pyc +0 -0
- {lightwave2d-0.0.4 → lightwave2d-0.0.6}/LightWave2D/__pycache__/grid.cpython-311.pyc +0 -0
- {lightwave2d-0.0.4 → lightwave2d-0.0.6}/LightWave2D/__pycache__/physics.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.4 → lightwave2d-0.0.6}/LightWave2D/__pycache__/utils.cpython-311.pyc +0 -0
- lightwave2d-0.0.6/LightWave2D/components.py +365 -0
- lightwave2d-0.0.6/LightWave2D/detector.py +156 -0
- {lightwave2d-0.0.4 → lightwave2d-0.0.6}/LightWave2D/experiment.py +98 -24
- {lightwave2d-0.0.4 → lightwave2d-0.0.6}/LightWave2D/grid.py +8 -0
- lightwave2d-0.0.6/LightWave2D/pml.py +97 -0
- lightwave2d-0.0.6/LightWave2D/source.py +262 -0
- lightwave2d-0.0.6/LightWave2D.egg-info/PKG-INFO +288 -0
- {lightwave2d-0.0.4 → lightwave2d-0.0.6}/LightWave2D.egg-info/SOURCES.txt +2 -8
- {lightwave2d-0.0.4 → lightwave2d-0.0.6}/LightWave2D.egg-info/requires.txt +1 -0
- lightwave2d-0.0.6/PKG-INFO +288 -0
- lightwave2d-0.0.6/README.rst +243 -0
- lightwave2d-0.0.6/developments/testings_0.py +82 -0
- {lightwave2d-0.0.4 → lightwave2d-0.0.6}/setup.cfg +1 -0
- lightwave2d-0.0.4/LightWave2D/VERSION +0 -1
- lightwave2d-0.0.4/LightWave2D/__pycache__/__init__.cpython-311.pyc +0 -0
- lightwave2d-0.0.4/LightWave2D/__pycache__/detector.cpython-311.pyc +0 -0
- lightwave2d-0.0.4/LightWave2D/__pycache__/experiment.cpython-311.pyc +0 -0
- lightwave2d-0.0.4/LightWave2D/__pycache__/pml.cpython-311.pyc +0 -0
- lightwave2d-0.0.4/LightWave2D/__pycache__/source.cpython-311.pyc +0 -0
- lightwave2d-0.0.4/LightWave2D/components/__init__.py +0 -5
- lightwave2d-0.0.4/LightWave2D/components/__pycache__/__init__.cpython-311.pyc +0 -0
- lightwave2d-0.0.4/LightWave2D/components/__pycache__/base_class.cpython-311.pyc +0 -0
- lightwave2d-0.0.4/LightWave2D/components/__pycache__/basic.cpython-311.pyc +0 -0
- lightwave2d-0.0.4/LightWave2D/components/__pycache__/complex.cpython-311.pyc +0 -0
- lightwave2d-0.0.4/LightWave2D/components/base_class.py +0 -139
- lightwave2d-0.0.4/LightWave2D/components/basic.py +0 -133
- lightwave2d-0.0.4/LightWave2D/components/complex.py +0 -105
- lightwave2d-0.0.4/LightWave2D/detector.py +0 -85
- lightwave2d-0.0.4/LightWave2D/pml.py +0 -74
- lightwave2d-0.0.4/LightWave2D/source.py +0 -130
- lightwave2d-0.0.4/LightWave2D.egg-info/PKG-INFO +0 -91
- lightwave2d-0.0.4/PKG-INFO +0 -91
- lightwave2d-0.0.4/README.rst +0 -47
- lightwave2d-0.0.4/developments/testings_0.py +0 -127
- {lightwave2d-0.0.4 → lightwave2d-0.0.6}/LICENSE +0 -0
- {lightwave2d-0.0.4 → lightwave2d-0.0.6}/LightWave2D/__init__.py +0 -0
- {lightwave2d-0.0.4 → lightwave2d-0.0.6}/LightWave2D/directories.py +0 -0
- {lightwave2d-0.0.4 → lightwave2d-0.0.6}/LightWave2D/physics.py +0 -0
- {lightwave2d-0.0.4 → lightwave2d-0.0.6}/LightWave2D/utils.py +0 -0
- {lightwave2d-0.0.4 → lightwave2d-0.0.6}/LightWave2D.egg-info/dependency_links.txt +0 -0
- {lightwave2d-0.0.4 → lightwave2d-0.0.6}/LightWave2D.egg-info/top_level.txt +0 -0
- {lightwave2d-0.0.4 → lightwave2d-0.0.6}/MANIFEST.in +0 -0
- {lightwave2d-0.0.4 → lightwave2d-0.0.6}/setup.py +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
0.0.6
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,365 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
|
|
4
|
+
from typing import NoReturn, Union, Tuple
|
|
5
|
+
from pydantic.dataclasses import dataclass
|
|
6
|
+
import numpy
|
|
7
|
+
from LightWave2D.physics import Physics
|
|
8
|
+
import matplotlib.pyplot as plt
|
|
9
|
+
import shapely.geometry as geo
|
|
10
|
+
from matplotlib.patches import PathPatch
|
|
11
|
+
from matplotlib import patches
|
|
12
|
+
import numpy as np
|
|
13
|
+
from matplotlib.path import Path
|
|
14
|
+
from matplotlib.collections import PatchCollection
|
|
15
|
+
from LightWave2D.grid import Grid
|
|
16
|
+
from shapely.affinity import scale, rotate
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
# Configuration dictionary for dataclasses
|
|
20
|
+
config_dict = {
|
|
21
|
+
'kw_only': True,
|
|
22
|
+
'extra': 'forbid',
|
|
23
|
+
'slots': True,
|
|
24
|
+
'arbitrary_types_allowed': True
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@dataclass(kw_only=True, config=config_dict)
|
|
29
|
+
class BaseComponent():
|
|
30
|
+
"""
|
|
31
|
+
Represents a generic component in a simulation grid.
|
|
32
|
+
|
|
33
|
+
Attributes:
|
|
34
|
+
grid (Grid): The simulation grid.
|
|
35
|
+
facecolor (str): The color of the component's face.
|
|
36
|
+
edgecolor (str): The color of the component's edge.
|
|
37
|
+
alpha (float): Transparency level of the component.
|
|
38
|
+
rotation (float): Rotation angle of the component.
|
|
39
|
+
"""
|
|
40
|
+
grid: Grid
|
|
41
|
+
facecolor: str = 'lightblue'
|
|
42
|
+
edgecolor: str = 'blue'
|
|
43
|
+
alpha: float = 0.3
|
|
44
|
+
rotation: float = 0
|
|
45
|
+
|
|
46
|
+
def __post_init__(self):
|
|
47
|
+
x0, y0 = self.position
|
|
48
|
+
self.coordinate = self.grid.get_coordinate(x=x0, y=y0)
|
|
49
|
+
|
|
50
|
+
self.build_object()
|
|
51
|
+
|
|
52
|
+
def build_object(self) -> NoReturn:
|
|
53
|
+
"""
|
|
54
|
+
Build the permittivity mesh for the component.
|
|
55
|
+
"""
|
|
56
|
+
self.epsilon_r_mesh = numpy.ones(self.grid.shape) # Background permittivity
|
|
57
|
+
|
|
58
|
+
self.compute_polygon()
|
|
59
|
+
|
|
60
|
+
self.polygon = rotate(self.polygon, angle=self.rotation)
|
|
61
|
+
|
|
62
|
+
self.polygon = self.polygon.intersection(self.grid.polygon)
|
|
63
|
+
|
|
64
|
+
self.path = Path(self.polygon.exterior.coords)
|
|
65
|
+
|
|
66
|
+
x_mesh, y_mesh = numpy.meshgrid(self.grid.x_stamp, self.grid.y_stamp)
|
|
67
|
+
|
|
68
|
+
coordinates = numpy.c_[x_mesh.T.flatten(), y_mesh.T.flatten()]
|
|
69
|
+
|
|
70
|
+
self.idx = self.path.contains_points(coordinates).astype(bool).reshape(self.epsilon_r_mesh.shape)
|
|
71
|
+
|
|
72
|
+
self.epsilon_r_mesh[self.idx] = self.epsilon_r
|
|
73
|
+
|
|
74
|
+
def add_to_ax(self, ax: plt.axis) -> PatchCollection:
|
|
75
|
+
"""
|
|
76
|
+
Add the component to the provided axis.
|
|
77
|
+
|
|
78
|
+
Args:
|
|
79
|
+
ax (plt.Axes): The axis to which the component will be added.
|
|
80
|
+
|
|
81
|
+
Returns:
|
|
82
|
+
PatchCollection: The collection of patches added to the axis.
|
|
83
|
+
"""
|
|
84
|
+
path = Path.make_compound_path(
|
|
85
|
+
Path(np.asarray(self.polygon.exterior.coords)[:, :2]),
|
|
86
|
+
*[Path(np.asarray(ring.coords)[:, :2]) for ring in self.polygon.interiors])
|
|
87
|
+
|
|
88
|
+
patch = PathPatch(path, facecolor=self.facecolor, edgecolor=self.edgecolor, alpha=0.4)
|
|
89
|
+
collection = PatchCollection([patch], facecolor=self.facecolor, edgecolor=self.edgecolor, alpha=self.alpha)
|
|
90
|
+
|
|
91
|
+
ax.add_collection(collection, autolim=True)
|
|
92
|
+
ax.autoscale_view()
|
|
93
|
+
return collection
|
|
94
|
+
|
|
95
|
+
def plot(self) -> NoReturn:
|
|
96
|
+
"""
|
|
97
|
+
Plot the component.
|
|
98
|
+
"""
|
|
99
|
+
figure, ax = plt.subplots(1, 1, figsize=(6, 6))
|
|
100
|
+
ax.set_title('FDTD Simulation')
|
|
101
|
+
ax.set_xlabel(r'x position [$\mu$m]')
|
|
102
|
+
ax.set_xlabel(r'y position [$\mu$m]')
|
|
103
|
+
ax.set_aspect('equal')
|
|
104
|
+
|
|
105
|
+
self.add_to_ax(ax)
|
|
106
|
+
ax.autoscale_view()
|
|
107
|
+
|
|
108
|
+
plt.show()
|
|
109
|
+
|
|
110
|
+
def add_to_mesh(self, epsilon_r_mesh: numpy.ndarray) -> NoReturn:
|
|
111
|
+
"""
|
|
112
|
+
Add the component's permittivity to the provided mesh.
|
|
113
|
+
|
|
114
|
+
Args:
|
|
115
|
+
epsilon_r_mesh (np.ndarray): The permittivity mesh to be updated.
|
|
116
|
+
"""
|
|
117
|
+
epsilon_r_mesh += self.epsilon_r_mesh
|
|
118
|
+
|
|
119
|
+
def add_non_linear_effect_to_field(self, field: numpy.ndarray) -> NoReturn:
|
|
120
|
+
"""
|
|
121
|
+
Add non-linear effects to the field.
|
|
122
|
+
|
|
123
|
+
Args:
|
|
124
|
+
field (np.ndarray): The field to which non-linear effects will be added.
|
|
125
|
+
"""
|
|
126
|
+
chi_2 = 1e10
|
|
127
|
+
|
|
128
|
+
field += self.idx * self.grid.dt**2 / (self.epsilon_r_mesh * Physics.epsilon_0 * Physics.mu_0) * chi_2 * field ** 2
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
@dataclass(config=config_dict)
|
|
132
|
+
class Waveguide(BaseComponent):
|
|
133
|
+
"""
|
|
134
|
+
Represents a Waveguide scatterer in a simulation grid.
|
|
135
|
+
|
|
136
|
+
Attributes:
|
|
137
|
+
position (Tuple[Union[float, str], Union[float, str]]): The center position of the scatterer.
|
|
138
|
+
epsilon_r (float): The relative permittivity inside the scatterer.
|
|
139
|
+
side_length (float): The side length of the square scatterer.
|
|
140
|
+
"""
|
|
141
|
+
position_0: Tuple[Union[float, str], Union[float, str]]
|
|
142
|
+
position_1: Tuple[Union[float, str], Union[float, str]]
|
|
143
|
+
width: float
|
|
144
|
+
epsilon_r: float
|
|
145
|
+
|
|
146
|
+
def __post_init__(self):
|
|
147
|
+
x0, y0 = self.position_0
|
|
148
|
+
x1, y1 = self.position_1
|
|
149
|
+
self.coordinate_0 = self.grid.get_coordinate(x=x0, y=y0)
|
|
150
|
+
self.coordinate_1 = self.grid.get_coordinate(x=x1, y=y1)
|
|
151
|
+
|
|
152
|
+
self.build_object()
|
|
153
|
+
|
|
154
|
+
def compute_polygon(self) -> Path:
|
|
155
|
+
"""
|
|
156
|
+
Compute the polygon of the square scatterer.
|
|
157
|
+
"""
|
|
158
|
+
line = geo.LineString(
|
|
159
|
+
[(self.coordinate_0.x, self.coordinate_0.y), (self.coordinate_1.x, self.coordinate_1.y)]
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
self.polygon = line.buffer(self.width)
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
@dataclass(config=config_dict)
|
|
166
|
+
class Square(BaseComponent):
|
|
167
|
+
"""
|
|
168
|
+
Represents a square scatterer in a simulation grid.
|
|
169
|
+
|
|
170
|
+
Attributes:
|
|
171
|
+
position (Tuple[Union[float, str], Union[float, str]]): The center position of the scatterer.
|
|
172
|
+
epsilon_r (float): The relative permittivity inside the scatterer.
|
|
173
|
+
side_length (float): The side length of the square scatterer.
|
|
174
|
+
"""
|
|
175
|
+
position: Tuple[Union[float, str], Union[float, str]]
|
|
176
|
+
epsilon_r: float
|
|
177
|
+
side_length: float
|
|
178
|
+
|
|
179
|
+
def compute_polygon(self) -> Path:
|
|
180
|
+
"""
|
|
181
|
+
Compute the polygon of the square scatterer.
|
|
182
|
+
"""
|
|
183
|
+
half_side = self.side_length / 2
|
|
184
|
+
x_start, x_end = self.coordinate.x - half_side, self.coordinate.x + half_side
|
|
185
|
+
y_start, y_end = self.coordinate.y - half_side, self.coordinate.y + half_side
|
|
186
|
+
|
|
187
|
+
self.polygon = geo.Polygon([
|
|
188
|
+
(x_start, y_start),
|
|
189
|
+
(x_start, y_end),
|
|
190
|
+
(x_end, y_end),
|
|
191
|
+
(x_end, y_start)
|
|
192
|
+
]).convex_hull
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
@dataclass(config=config_dict)
|
|
196
|
+
class Circle(BaseComponent):
|
|
197
|
+
"""
|
|
198
|
+
Represents a circular scatterer in a simulation grid.
|
|
199
|
+
|
|
200
|
+
Attributes:
|
|
201
|
+
position (Tuple[Union[float, str], Union[float, str]]): The center position of the scatterer.
|
|
202
|
+
epsilon_r (float): The relative permittivity inside the scatterer.
|
|
203
|
+
radius (float): The radius of the scatterer.
|
|
204
|
+
"""
|
|
205
|
+
position: Tuple[Union[float, str], Union[float, str]]
|
|
206
|
+
epsilon_r: float
|
|
207
|
+
radius: float
|
|
208
|
+
|
|
209
|
+
def compute_polygon(self) -> NoReturn:
|
|
210
|
+
"""
|
|
211
|
+
Compute the polygon of the circular scatterer.
|
|
212
|
+
"""
|
|
213
|
+
self.polygon = geo.Point(self.coordinate.x, self.coordinate.y).buffer(self.radius)
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
@dataclass(config=config_dict)
|
|
217
|
+
class Triangle(BaseComponent):
|
|
218
|
+
"""
|
|
219
|
+
Represents a triangular scatterer in a simulation grid.
|
|
220
|
+
|
|
221
|
+
Attributes:
|
|
222
|
+
position (Tuple[Union[float, str], Union[float, str]]): The center position of the scatterer.
|
|
223
|
+
epsilon_r (float): The relative permittivity inside the scatterer.
|
|
224
|
+
side_length (float): The side length of the triangular scatterer.
|
|
225
|
+
"""
|
|
226
|
+
position: Tuple[Union[float, str], Union[float, str]]
|
|
227
|
+
epsilon_r: float
|
|
228
|
+
side_length: float
|
|
229
|
+
|
|
230
|
+
def compute_polygon(self) -> NoReturn:
|
|
231
|
+
"""
|
|
232
|
+
Compute the polygon of the triangular scatterer.
|
|
233
|
+
"""
|
|
234
|
+
height = (np.sqrt(3) / 2) * self.side_length
|
|
235
|
+
|
|
236
|
+
# Vertices of an equilateral triangle centered at (coordinate.x, coordinate.y)
|
|
237
|
+
p0 = (self.coordinate.x - self.side_length / 2, self.coordinate.y - height / 3)
|
|
238
|
+
p1 = (self.coordinate.x + self.side_length / 2, self.coordinate.y - height / 3)
|
|
239
|
+
p2 = (self.coordinate.x, self.coordinate.y + 2 * height / 3)
|
|
240
|
+
|
|
241
|
+
self.polygon = geo.Polygon([p0, p1, p2])
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
@dataclass(config=config_dict)
|
|
245
|
+
class Ellipse(BaseComponent):
|
|
246
|
+
"""
|
|
247
|
+
Represents an elliptical scatterer in a simulation grid.
|
|
248
|
+
|
|
249
|
+
Attributes:
|
|
250
|
+
position (Tuple[Union[float, str], Union[float, str]]): The center position of the scatterer.
|
|
251
|
+
width (float): The width of the ellipse.
|
|
252
|
+
height (float): The height of the ellipse.
|
|
253
|
+
epsilon_r (float): The relative permittivity inside the scatterer.
|
|
254
|
+
resolution (float): The resolution of the ellipse.
|
|
255
|
+
"""
|
|
256
|
+
position: Tuple[Union[float, str], Union[float, str]]
|
|
257
|
+
width: float
|
|
258
|
+
height: float
|
|
259
|
+
epsilon_r: float
|
|
260
|
+
resolution: float = 100
|
|
261
|
+
|
|
262
|
+
def compute_polygon(self) -> NoReturn:
|
|
263
|
+
"""
|
|
264
|
+
Compute the polygon of the elliptical scatterer.
|
|
265
|
+
"""
|
|
266
|
+
ellipse_patch = patches.Ellipse(
|
|
267
|
+
(self.coordinate.x, self.coordinate.y),
|
|
268
|
+
width=self.resolution,
|
|
269
|
+
height=self.resolution * self.height / self.width,
|
|
270
|
+
)
|
|
271
|
+
|
|
272
|
+
vertices = ellipse_patch.get_verts()
|
|
273
|
+
polygon = geo.Polygon(vertices)
|
|
274
|
+
|
|
275
|
+
self.polygon = scale(polygon, xfact=self.width / self.resolution, yfact=self.width / self.resolution)
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
@dataclass(config=config_dict)
|
|
279
|
+
class Grating(BaseComponent):
|
|
280
|
+
"""
|
|
281
|
+
Represents a grating in a simulation grid.
|
|
282
|
+
|
|
283
|
+
Attributes:
|
|
284
|
+
position (Tuple[Union[float, str], Union[float, str]]): The starting position of the grating.
|
|
285
|
+
epsilon_r (float): The relative permittivity inside the grating.
|
|
286
|
+
period (float): The period of the grating.
|
|
287
|
+
duty_cycle (float): The duty cycle of the grating.
|
|
288
|
+
num_periods (int): The number of periods in the grating.
|
|
289
|
+
"""
|
|
290
|
+
position: Tuple[Union[float, str], Union[float, str]]
|
|
291
|
+
epsilon_r: float
|
|
292
|
+
period: float
|
|
293
|
+
duty_cycle: float
|
|
294
|
+
num_periods: int
|
|
295
|
+
|
|
296
|
+
def compute_polygon(self) -> NoReturn:
|
|
297
|
+
"""
|
|
298
|
+
Compute the polygon of the grating.
|
|
299
|
+
"""
|
|
300
|
+
bars = []
|
|
301
|
+
for i in range(self.num_periods):
|
|
302
|
+
x_start = self.coordinate.x + i * self.period
|
|
303
|
+
x_end = x_start + self.duty_cycle * self.period
|
|
304
|
+
bar = geo.box(x_start, self.coordinate.y - 0.5, x_end, self.coordinate.y + 0.5)
|
|
305
|
+
bars.append(bar)
|
|
306
|
+
|
|
307
|
+
self.polygon = geo.MultiPolygon(bars)
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
@dataclass(config=config_dict)
|
|
311
|
+
class RingResonator(BaseComponent):
|
|
312
|
+
"""
|
|
313
|
+
Represents a ring resonator in a simulation grid.
|
|
314
|
+
|
|
315
|
+
Attributes:
|
|
316
|
+
position (Tuple[Union[float, str], Union[float, str]]): The center position of the ring resonator.
|
|
317
|
+
epsilon_r (float): The relative permittivity inside the resonator.
|
|
318
|
+
inner_radius (float): The inner radius of the ring.
|
|
319
|
+
width (float): The width of the ring.
|
|
320
|
+
"""
|
|
321
|
+
grid: Grid
|
|
322
|
+
position: Tuple[Union[float, str], Union[float, str]]
|
|
323
|
+
epsilon_r: float
|
|
324
|
+
inner_radius: float
|
|
325
|
+
width: float
|
|
326
|
+
|
|
327
|
+
def compute_polygon(self) -> NoReturn:
|
|
328
|
+
"""
|
|
329
|
+
Compute the polygon of the ring resonator.
|
|
330
|
+
"""
|
|
331
|
+
inner_circle = geo.Point(self.coordinate.x, self.coordinate.y).buffer(self.inner_radius)
|
|
332
|
+
outer_circle = geo.Point(self.coordinate.x, self.coordinate.y).buffer(self.inner_radius + self.width)
|
|
333
|
+
self.polygon = outer_circle.difference(inner_circle)
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
@dataclass(config=config_dict)
|
|
337
|
+
class Lense(BaseComponent):
|
|
338
|
+
"""
|
|
339
|
+
Represents a lens in a simulation grid.
|
|
340
|
+
|
|
341
|
+
Attributes:
|
|
342
|
+
position (Tuple[Union[float, str], Union[float, str]]): The center position of the lens.
|
|
343
|
+
epsilon_r (float): The relative permittivity inside the lens.
|
|
344
|
+
width (float): The width of the lens.
|
|
345
|
+
curvature (float): The curvature of the lens (positive for convex, negative for concave).
|
|
346
|
+
"""
|
|
347
|
+
position: Tuple[Union[float, str], Union[float, str]]
|
|
348
|
+
epsilon_r: float
|
|
349
|
+
width: float
|
|
350
|
+
curvature: float
|
|
351
|
+
|
|
352
|
+
def compute_polygon(self) -> NoReturn:
|
|
353
|
+
"""
|
|
354
|
+
Compute the polygon of the lens.
|
|
355
|
+
"""
|
|
356
|
+
# Create two arcs for the lens shape
|
|
357
|
+
x0 = self.coordinate.x + self.curvature - self.width / 2
|
|
358
|
+
x1 = self.coordinate.x - self.curvature + self.width / 2
|
|
359
|
+
|
|
360
|
+
arc3 = geo.Point(x0, self.coordinate.y).buffer(self.curvature, resolution=100)
|
|
361
|
+
arc4 = geo.Point(x1, self.coordinate.y).buffer(self.curvature, resolution=100)
|
|
362
|
+
|
|
363
|
+
self.polygon = arc3.intersection(arc4)
|
|
364
|
+
|
|
365
|
+
# -
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
|
|
4
|
+
from typing import Tuple, Union, NoReturn
|
|
5
|
+
from dataclasses import field
|
|
6
|
+
import numpy
|
|
7
|
+
from LightWave2D.grid import Grid
|
|
8
|
+
import shapely.geometry as geo
|
|
9
|
+
from matplotlib.path import Path
|
|
10
|
+
from pydantic.dataclasses import dataclass
|
|
11
|
+
from matplotlib.collections import PatchCollection
|
|
12
|
+
from matplotlib.patches import PathPatch
|
|
13
|
+
import matplotlib.pyplot as plt
|
|
14
|
+
import matplotlib
|
|
15
|
+
|
|
16
|
+
config_dict = dict(
|
|
17
|
+
kw_only=True,
|
|
18
|
+
slots=True,
|
|
19
|
+
extra='forbid',
|
|
20
|
+
arbitrary_types_allowed=True
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@dataclass(kw_only=True, config=config_dict)
|
|
25
|
+
class BaseDetector():
|
|
26
|
+
"""
|
|
27
|
+
Represents an elliptical scatterer in a simulation grid.
|
|
28
|
+
|
|
29
|
+
Attributes:
|
|
30
|
+
grid (Grid): The grid of the simulation mesh.
|
|
31
|
+
angle (float): Rotation angle of the ellipse.
|
|
32
|
+
facecolor (str): Color of the scatterer face.
|
|
33
|
+
edgecolor (str): Color of the scatterer edge.
|
|
34
|
+
"""
|
|
35
|
+
grid: Grid
|
|
36
|
+
facecolor: str = 'green'
|
|
37
|
+
edgecolor: str = 'green'
|
|
38
|
+
alpha: float = 0.8
|
|
39
|
+
rotation: float = 0
|
|
40
|
+
|
|
41
|
+
def __post_init__(self):
|
|
42
|
+
x0, y0 = self.position
|
|
43
|
+
self.coordinate = self.grid.get_coordinate(x=x0, y=y0)
|
|
44
|
+
|
|
45
|
+
self.build_object()
|
|
46
|
+
|
|
47
|
+
def build_object(self) -> NoReturn:
|
|
48
|
+
"""
|
|
49
|
+
Build the permittivity mesh for the square scatterer.
|
|
50
|
+
"""
|
|
51
|
+
self.compute_polygon()
|
|
52
|
+
|
|
53
|
+
self.path = Path(self.polygon.exterior.coords)
|
|
54
|
+
|
|
55
|
+
self.path = self.path.transformed(matplotlib.transforms.Affine2D().rotate_around(self.coordinate.x, self.coordinate.y, self.rotation))
|
|
56
|
+
|
|
57
|
+
def add_to_ax(self, ax: plt.axis) -> PatchCollection:
|
|
58
|
+
"""
|
|
59
|
+
Add the scatterer to the provided axis as a circle.
|
|
60
|
+
|
|
61
|
+
Args:
|
|
62
|
+
ax (Axis): The axis to which the scatterer will be added.
|
|
63
|
+
"""
|
|
64
|
+
path = Path.make_compound_path(
|
|
65
|
+
Path(numpy.asarray(self.polygon.exterior.coords)[:, :2]),
|
|
66
|
+
*[Path(numpy.asarray(ring.coords)[:, :2]) for ring in self.polygon.interiors])
|
|
67
|
+
|
|
68
|
+
patch = PathPatch(path, color=self.facecolor, edgecolor=self.edgecolor, alpha=0.4)
|
|
69
|
+
collection = PatchCollection([patch], color=self.facecolor, edgecolor=self.edgecolor, alpha=self.alpha)
|
|
70
|
+
|
|
71
|
+
ax.add_collection(collection, autolim=True)
|
|
72
|
+
ax.autoscale_view()
|
|
73
|
+
return collection
|
|
74
|
+
|
|
75
|
+
def plot(self) -> NoReturn:
|
|
76
|
+
figure, ax = plt.subplots(1, 1, figsize=(6, 6))
|
|
77
|
+
ax.set_title('FDTD Simulation')
|
|
78
|
+
ax.set_xlabel(r'x position [$\mu$m]')
|
|
79
|
+
ax.set_xlabel(r'y position [$\mu$m]')
|
|
80
|
+
ax.set_aspect('equal')
|
|
81
|
+
|
|
82
|
+
self.add_to_ax(ax)
|
|
83
|
+
ax.autoscale_view()
|
|
84
|
+
|
|
85
|
+
plt.show()
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
@dataclass(kw_only=True, config=config_dict)
|
|
89
|
+
class PointDetector(BaseDetector):
|
|
90
|
+
"""
|
|
91
|
+
Represents a point detector within a simulation grid.
|
|
92
|
+
|
|
93
|
+
Attributes:
|
|
94
|
+
grid (Grid): The simulation mesh grid.
|
|
95
|
+
position (Tuple[float | str, float | str]): The (x, y) coordinates of the point detector on the grid.
|
|
96
|
+
data (numpy.ndarray): The data collected by the detector over time.
|
|
97
|
+
"""
|
|
98
|
+
position: Union[Tuple[float | str, float | str]]
|
|
99
|
+
coherent: bool = True
|
|
100
|
+
data: numpy.ndarray = field(init=False)
|
|
101
|
+
|
|
102
|
+
def __post_init__(self):
|
|
103
|
+
"""
|
|
104
|
+
Initialize the PointDetector by converting the given position into
|
|
105
|
+
grid coordinates and initializing the data array.
|
|
106
|
+
"""
|
|
107
|
+
x, y = self.position
|
|
108
|
+
|
|
109
|
+
self.p0 = self.grid.get_coordinate(x=x, y=y)
|
|
110
|
+
|
|
111
|
+
self.polygon = geo.Point(self.p0.x, self.p0.y)
|
|
112
|
+
|
|
113
|
+
self.path = Path(self.polygon.coords)
|
|
114
|
+
|
|
115
|
+
self.data = numpy.zeros(self.grid.n_steps)
|
|
116
|
+
|
|
117
|
+
def update_data(self, field: numpy.ndarray) -> NoReturn:
|
|
118
|
+
"""
|
|
119
|
+
Update the detector data based on the provided field values.
|
|
120
|
+
|
|
121
|
+
Parameters:
|
|
122
|
+
field (numpy.ndarray): The field values to update the detector data.
|
|
123
|
+
"""
|
|
124
|
+
if self.coherent:
|
|
125
|
+
self.data = field[:, self.p0.x_index, self.p0.y_index]
|
|
126
|
+
else:
|
|
127
|
+
self.data = abs(field[:, self.p0.x_index, self.p0.y_index])
|
|
128
|
+
|
|
129
|
+
def plot_data(self) -> NoReturn:
|
|
130
|
+
"""
|
|
131
|
+
Plot the detector data over time.
|
|
132
|
+
|
|
133
|
+
Returns:
|
|
134
|
+
SceneList: A scene list containing the plot.
|
|
135
|
+
"""
|
|
136
|
+
figure, ax = plt.subplots(1, 1, figsize=(8, 4))
|
|
137
|
+
ax.plot(self.grid.time_stamp, self.data)
|
|
138
|
+
ax.set_ylabel('Amplitude')
|
|
139
|
+
ax.set_xlabel('Time [seconds]')
|
|
140
|
+
figure.show()
|
|
141
|
+
|
|
142
|
+
def add_to_ax(self, ax: plt.axis) -> NoReturn:
|
|
143
|
+
"""
|
|
144
|
+
Add the scatterer to the provided axis as a circle.
|
|
145
|
+
|
|
146
|
+
Args:
|
|
147
|
+
ax (Axis): The axis to which the scatterer will be added.
|
|
148
|
+
"""
|
|
149
|
+
ax.scatter(
|
|
150
|
+
self.p0.x,
|
|
151
|
+
self.p0.y,
|
|
152
|
+
color=self.facecolor,
|
|
153
|
+
label='detector'
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
# -
|