LightWave2D 0.0.3__tar.gz → 0.0.4__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.
Files changed (53) hide show
  1. lightwave2d-0.0.4/LightWave2D/VERSION +1 -0
  2. lightwave2d-0.0.4/LightWave2D/__pycache__/__init__.cpython-311.pyc +0 -0
  3. lightwave2d-0.0.4/LightWave2D/__pycache__/detector.cpython-311.pyc +0 -0
  4. lightwave2d-0.0.4/LightWave2D/__pycache__/experiment.cpython-311.pyc +0 -0
  5. {lightwave2d-0.0.3 → lightwave2d-0.0.4}/LightWave2D/__pycache__/grid.cpython-311.pyc +0 -0
  6. {lightwave2d-0.0.3 → lightwave2d-0.0.4}/LightWave2D/__pycache__/physics.cpython-311.pyc +0 -0
  7. {lightwave2d-0.0.3 → lightwave2d-0.0.4}/LightWave2D/__pycache__/pml.cpython-311.pyc +0 -0
  8. lightwave2d-0.0.4/LightWave2D/__pycache__/source.cpython-311.pyc +0 -0
  9. lightwave2d-0.0.4/LightWave2D/__pycache__/utils.cpython-311.pyc +0 -0
  10. lightwave2d-0.0.4/LightWave2D/components/__init__.py +5 -0
  11. lightwave2d-0.0.4/LightWave2D/components/__pycache__/__init__.cpython-311.pyc +0 -0
  12. lightwave2d-0.0.4/LightWave2D/components/__pycache__/base_class.cpython-311.pyc +0 -0
  13. lightwave2d-0.0.4/LightWave2D/components/__pycache__/basic.cpython-311.pyc +0 -0
  14. lightwave2d-0.0.4/LightWave2D/components/__pycache__/complex.cpython-311.pyc +0 -0
  15. {lightwave2d-0.0.3 → lightwave2d-0.0.4}/LightWave2D/components/base_class.py +50 -11
  16. lightwave2d-0.0.4/LightWave2D/components/basic.py +133 -0
  17. lightwave2d-0.0.4/LightWave2D/components/complex.py +105 -0
  18. {lightwave2d-0.0.3 → lightwave2d-0.0.4}/LightWave2D/detector.py +2 -5
  19. {lightwave2d-0.0.3 → lightwave2d-0.0.4}/LightWave2D/experiment.py +41 -38
  20. {lightwave2d-0.0.3 → lightwave2d-0.0.4}/LightWave2D/pml.py +1 -1
  21. lightwave2d-0.0.4/LightWave2D/source.py +130 -0
  22. lightwave2d-0.0.4/LightWave2D/utils.py +45 -0
  23. {lightwave2d-0.0.3 → lightwave2d-0.0.4/LightWave2D.egg-info}/PKG-INFO +40 -3
  24. {lightwave2d-0.0.3 → lightwave2d-0.0.4}/LightWave2D.egg-info/SOURCES.txt +4 -2
  25. {lightwave2d-0.0.3/LightWave2D.egg-info → lightwave2d-0.0.4}/PKG-INFO +40 -3
  26. lightwave2d-0.0.4/README.rst +47 -0
  27. lightwave2d-0.0.4/developments/testings_0.py +127 -0
  28. lightwave2d-0.0.3/LightWave2D/VERSION +0 -1
  29. lightwave2d-0.0.3/LightWave2D/__pycache__/__init__.cpython-311.pyc +0 -0
  30. lightwave2d-0.0.3/LightWave2D/__pycache__/detector.cpython-311.pyc +0 -0
  31. lightwave2d-0.0.3/LightWave2D/__pycache__/experiment.cpython-311.pyc +0 -0
  32. lightwave2d-0.0.3/LightWave2D/__pycache__/source.cpython-311.pyc +0 -0
  33. lightwave2d-0.0.3/LightWave2D/__pycache__/utils.cpython-311.pyc +0 -0
  34. lightwave2d-0.0.3/LightWave2D/components/__init__.py +0 -4
  35. lightwave2d-0.0.3/LightWave2D/components/__pycache__/__init__.cpython-311.pyc +0 -0
  36. lightwave2d-0.0.3/LightWave2D/components/__pycache__/base_class.cpython-311.pyc +0 -0
  37. lightwave2d-0.0.3/LightWave2D/components/__pycache__/scatterers.cpython-311.pyc +0 -0
  38. lightwave2d-0.0.3/LightWave2D/components/scatterers.py +0 -185
  39. lightwave2d-0.0.3/LightWave2D/source.py +0 -119
  40. lightwave2d-0.0.3/LightWave2D/utils.py +0 -95
  41. lightwave2d-0.0.3/README.rst +0 -10
  42. lightwave2d-0.0.3/developments/testings_0.py +0 -87
  43. {lightwave2d-0.0.3 → lightwave2d-0.0.4}/LICENSE +0 -0
  44. {lightwave2d-0.0.3 → lightwave2d-0.0.4}/LightWave2D/__init__.py +0 -0
  45. {lightwave2d-0.0.3 → lightwave2d-0.0.4}/LightWave2D/directories.py +0 -0
  46. {lightwave2d-0.0.3 → lightwave2d-0.0.4}/LightWave2D/grid.py +0 -0
  47. {lightwave2d-0.0.3 → lightwave2d-0.0.4}/LightWave2D/physics.py +0 -0
  48. {lightwave2d-0.0.3 → lightwave2d-0.0.4}/LightWave2D.egg-info/dependency_links.txt +0 -0
  49. {lightwave2d-0.0.3 → lightwave2d-0.0.4}/LightWave2D.egg-info/requires.txt +0 -0
  50. {lightwave2d-0.0.3 → lightwave2d-0.0.4}/LightWave2D.egg-info/top_level.txt +0 -0
  51. {lightwave2d-0.0.3 → lightwave2d-0.0.4}/MANIFEST.in +0 -0
  52. {lightwave2d-0.0.3 → lightwave2d-0.0.4}/setup.cfg +0 -0
  53. {lightwave2d-0.0.3 → lightwave2d-0.0.4}/setup.py +0 -0
@@ -0,0 +1 @@
1
+ 0.0.4
@@ -0,0 +1,5 @@
1
+ from .complex import Lense, Grating, RingResonator # noqa: F401
2
+
3
+ from .basic import Circle, Square, Ellipse, Triangle # noqa: F401
4
+
5
+ # -
@@ -2,15 +2,48 @@
2
2
  # -*- coding: utf-8 -*-
3
3
 
4
4
  from typing import NoReturn
5
-
5
+ from pydantic.dataclasses import dataclass
6
6
  import numpy
7
7
  from LightWave2D.physics import Physics
8
8
  import matplotlib.pyplot as plt
9
9
  from matplotlib.patches import PathPatch
10
+ import matplotlib as mpl
11
+ import numpy as np
12
+ from matplotlib.path import Path
13
+ from matplotlib.collections import PatchCollection
14
+ from LightWave2D.grid import Grid
15
+
16
+
17
+ # Configuration dictionary for dataclasses
18
+ config_dict = {
19
+ 'kw_only': True,
20
+ 'extra': 'forbid',
21
+ 'slots': True,
22
+ 'arbitrary_types_allowed': True
23
+ }
10
24
 
11
25
 
26
+ @dataclass(kw_only=True, config=config_dict)
12
27
  class BaseComponent():
28
+ """
29
+ Represents an elliptical scatterer in a simulation grid.
30
+
31
+ Attributes:
32
+ grid (Grid): The grid of the simulation mesh.
33
+ angle (float): Rotation angle of the ellipse.
34
+ facecolor (str): Color of the scatterer face.
35
+ edgecolor (str): Color of the scatterer edge.
36
+ """
37
+ grid: Grid
38
+ facecolor: str = 'lightblue'
39
+ edgecolor: str = 'blue'
40
+ alpha: float = 0.3
41
+ rotation: float = 0
42
+
13
43
  def __post_init__(self):
44
+ x0, y0 = self.position
45
+ self.coordinate = self.grid.get_coordinate(x=x0, y=y0)
46
+
14
47
  self.build_object()
15
48
 
16
49
  def build_object(self) -> NoReturn:
@@ -19,7 +52,11 @@ class BaseComponent():
19
52
  """
20
53
  self.epsilon_r_mesh = numpy.ones(self.grid.shape) # Background permittivity
21
54
 
22
- self.compute_path()
55
+ self.compute_polygon()
56
+
57
+ self.path = Path(self.polygon.exterior.coords)
58
+
59
+ self.path = self.path.transformed(mpl.transforms.Affine2D().rotate_around(self.coordinate.x, self.coordinate.y, self.rotation))
23
60
 
24
61
  x_mesh, y_mesh = numpy.meshgrid(self.grid.x_stamp, self.grid.y_stamp)
25
62
 
@@ -29,21 +66,23 @@ class BaseComponent():
29
66
 
30
67
  self.epsilon_r_mesh[self.idx] = self.epsilon_r
31
68
 
32
- def add_to_ax(self, ax: plt.axis) -> NoReturn:
69
+ def add_to_ax(self, ax: plt.axis) -> PatchCollection:
33
70
  """
34
71
  Add the scatterer to the provided axis as a circle.
35
72
 
36
73
  Args:
37
74
  ax (Axis): The axis to which the scatterer will be added.
38
75
  """
39
- patch = PathPatch(
40
- self.path,
41
- alpha=0.7,
42
- color=self.facecolor,
43
- label=str(self.__class__.__name__),
44
- )
45
-
46
- return ax.add_patch(patch)
76
+ path = Path.make_compound_path(
77
+ Path(np.asarray(self.polygon.exterior.coords)[:, :2]),
78
+ *[Path(np.asarray(ring.coords)[:, :2]) for ring in self.polygon.interiors])
79
+
80
+ patch = PathPatch(path, color=self.facecolor, edgecolor=self.edgecolor, alpha=0.4)
81
+ collection = PatchCollection([patch], color=self.facecolor, edgecolor=self.edgecolor, alpha=self.alpha)
82
+
83
+ ax.add_collection(collection, autolim=True)
84
+ ax.autoscale_view()
85
+ return collection
47
86
 
48
87
  def plot(self) -> NoReturn:
49
88
  figure, ax = plt.subplots(1, 1, figsize=(6, 6))
@@ -0,0 +1,133 @@
1
+ #!/usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+
4
+ from typing import Tuple, Union, NoReturn
5
+ from pydantic.dataclasses import dataclass
6
+ import numpy as np
7
+ from LightWave2D.grid import Grid
8
+ from LightWave2D.components.base_class import BaseComponent
9
+ from matplotlib.path import Path
10
+ import shapely.geometry as geo
11
+ from matplotlib import patches
12
+ from shapely.affinity import scale
13
+
14
+ # Configuration dictionary for dataclasses
15
+ config_dict = {
16
+ 'kw_only': True,
17
+ 'extra': 'forbid',
18
+ 'slots': True,
19
+ 'arbitrary_types_allowed': True
20
+ }
21
+
22
+
23
+ @dataclass(config=config_dict)
24
+ class Square(BaseComponent):
25
+ """
26
+ Represents a square scatterer in a simulation grid.
27
+
28
+ Attributes:
29
+ position (Tuple[Union[float, str], Union[float, str]]): Starting position of the scatterer (center of the square).
30
+ epsilon_r (float): Relative permittivity inside the scatterer.
31
+ side_length (float): Side length of the square scatterer.
32
+ """
33
+ position: Tuple[Union[float, str], Union[float, str]]
34
+ epsilon_r: float
35
+ side_length: float
36
+
37
+ def compute_polygon(self) -> Path:
38
+ """
39
+ Compute the path of the square scatterer.
40
+ """
41
+ half_side = self.side_length / 2
42
+ x_start, x_end = self.coordinate.x - half_side, self.coordinate.x + half_side
43
+ y_start, y_end = self.coordinate.y - half_side, self.coordinate.y + half_side
44
+
45
+ self.polygon = geo.Polygon([
46
+ (x_start, y_start),
47
+ (x_start, y_end),
48
+ (x_end, y_end),
49
+ (x_end, y_start)
50
+ ])
51
+
52
+
53
+ @dataclass(config=config_dict)
54
+ class Circle(BaseComponent):
55
+ """
56
+ Represents a circular scatterer in a simulation grid.
57
+
58
+ Attributes:
59
+ position (Tuple[Union[float, str], Union[float, str]]): Starting position of the scatterer.
60
+ epsilon_r (float): Relative permittivity inside the scatterer.
61
+ radius (float): Radius of the scatterer.
62
+ """
63
+ position: Tuple[Union[float, str], Union[float, str]]
64
+ epsilon_r: float
65
+ radius: float
66
+
67
+ def compute_polygon(self) -> Path:
68
+ """
69
+ Compute the path of the circular scatterer.
70
+ """
71
+ self.polygon = geo.Point(self.coordinate.x, self.coordinate.y).buffer(self.radius)
72
+
73
+
74
+ @dataclass(config=config_dict)
75
+ class Triangle(BaseComponent):
76
+ """
77
+ Represents a triangular scatterer in a simulation grid.
78
+
79
+ Attributes:
80
+ position (Tuple[Union[float, str], Union[float, str]]): Starting position of the scatterer (center of the triangle).
81
+ epsilon_r (float): Relative permittivity inside the scatterer.
82
+ side_length (float): Side length of the triangle.
83
+ """
84
+ position: Tuple[Union[float, str], Union[float, str]]
85
+ epsilon_r: float
86
+ side_length: float
87
+
88
+ def compute_polygon(self) -> NoReturn:
89
+ """
90
+ Compute the path of the triangular scatterer.
91
+ """
92
+ height = (np.sqrt(3) / 2) * self.side_length
93
+
94
+ # Vertices of an equilateral triangle centered at (coordinate.x, coordinate.y)
95
+ p0 = (self.coordinate.x - self.side_length / 2, self.coordinate.y - height / 3)
96
+ p1 = (self.coordinate.x + self.side_length / 2, self.coordinate.y - height / 3)
97
+ p2 = (self.coordinate.x, self.coordinate.y + 2 * height / 3)
98
+
99
+ self.polygon = geo.Polygon([p0, p1, p2])
100
+
101
+
102
+ @dataclass(config=config_dict)
103
+ class Ellipse(BaseComponent):
104
+ """
105
+ Represents an elliptical scatterer in a simulation grid.
106
+
107
+ Attributes:
108
+ position (Tuple[Union[float, str], Union[float, str]]): Starting position of the scatterer.
109
+ width (float): Width of the ellipse.
110
+ height (float): Height of the ellipse.
111
+ epsilon_r (float): Relative permittivity inside the scatterer.
112
+ resolution (float): Resolution of the ellipse.
113
+ """
114
+ position: Tuple[Union[float, str], Union[float, str]]
115
+ width: float
116
+ height: float
117
+ epsilon_r: float
118
+ resolution: float = 100
119
+
120
+ def compute_polygon(self) -> Path:
121
+ """
122
+ Compute the path of the elliptical scatterer.
123
+ """
124
+ ellipse_patch = patches.Ellipse(
125
+ (self.coordinate.x, self.coordinate.y),
126
+ width=self.resolution,
127
+ height=self.resolution * self.height / self.width,
128
+ )
129
+
130
+ vertices = ellipse_patch.get_verts()
131
+ polygon = geo.Polygon(vertices)
132
+
133
+ self.polygon = scale(polygon, xfact=self.width / self.resolution, yfact=self.width / self.resolution)
@@ -0,0 +1,105 @@
1
+ #!/usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+
4
+ from typing import Tuple, Union, NoReturn
5
+ from pydantic.dataclasses import dataclass
6
+ from LightWave2D.grid import Grid
7
+ from LightWave2D.components.base_class import BaseComponent
8
+ from matplotlib.path import Path
9
+ import shapely.geometry as geo
10
+
11
+ # Configuration dictionary for dataclasses
12
+ config_dict = {
13
+ 'kw_only': True,
14
+ 'extra': 'forbid',
15
+ 'slots': True,
16
+ 'arbitrary_types_allowed': True
17
+ }
18
+
19
+
20
+ @dataclass(config=config_dict)
21
+ class Grating(BaseComponent):
22
+ """
23
+ Represents a grating in a simulation grid.
24
+
25
+ Attributes:
26
+ position (Tuple[Union[float, str], Union[float, str]]): Starting position of the grating.
27
+ epsilon_r (float): Relative permittivity inside the grating.
28
+ period (float): Period of the grating.
29
+ duty_cycle (float): Duty cycle of the grating (fraction of period occupied by the grating material).
30
+ num_periods (int): Number of grating periods.
31
+ """
32
+ position: Tuple[Union[float, str], Union[float, str]]
33
+ epsilon_r: float
34
+ period: float
35
+ duty_cycle: float
36
+ num_periods: int
37
+
38
+ def compute_polygon(self) -> NoReturn:
39
+ """
40
+ Compute the path of the grating.
41
+ """
42
+ bars = []
43
+ for i in range(self.num_periods):
44
+ x_start = self.coordinate.x + i * self.period
45
+ x_end = x_start + self.duty_cycle * self.period
46
+ bar = geo.box(x_start, self.coordinate.y - 0.5, x_end, self.coordinate.y + 0.5)
47
+ bars.append(bar)
48
+
49
+ self.polygon = geo.MultiPolygon(bars)
50
+
51
+
52
+ @dataclass(config=config_dict)
53
+ class RingResonator(BaseComponent):
54
+ """
55
+ Represents a ring resonator in a simulation grid.
56
+
57
+ Attributes:
58
+ position (Tuple[Union[float, str], Union[float, str]]): Starting position of the resonator (center of the ring).
59
+ epsilon_r (float): Relative permittivity inside the resonator.
60
+ inner_radius (float): Inner radius of the ring.
61
+ width (float): Outer radius of the ring.
62
+ """
63
+ grid: Grid
64
+ position: Tuple[Union[float, str], Union[float, str]]
65
+ epsilon_r: float
66
+ inner_radius: float
67
+ width: float
68
+
69
+ def compute_polygon(self) -> Path:
70
+ """
71
+ Compute the path of the ring resonator.
72
+ """
73
+ inner_circle = geo.Point(self.coordinate.x, self.coordinate.y).buffer(self.inner_radius)
74
+ outer_circle = geo.Point(self.coordinate.x, self.coordinate.y).buffer(self.inner_radius + self.width)
75
+ self.polygon = outer_circle.difference(inner_circle)
76
+
77
+
78
+ @dataclass(config=config_dict)
79
+ class Lense(BaseComponent):
80
+ """
81
+ Represents a lens in a simulation grid.
82
+
83
+ Attributes:
84
+ position (Tuple[Union[float, str], Union[float, str]]): Starting position of the lens (center of the lens).
85
+ epsilon_r (float): Relative permittivity inside the lens.
86
+ radius (float): Radius of the lens.
87
+ curvature (float): Curvature of the lens (positive for convex, negative for concave).
88
+ """
89
+ position: Tuple[Union[float, str], Union[float, str]]
90
+ epsilon_r: float
91
+ width: float
92
+ curvature: float
93
+
94
+ def compute_polygon(self) -> Path:
95
+ """
96
+ Compute the path of the lens.
97
+ """
98
+ # Create two arcs for the lens shape
99
+ x0 = self.coordinate.x + self.curvature - self.width / 2
100
+ x1 = self.coordinate.x - self.curvature + self.width / 2
101
+
102
+ arc3 = geo.Point(x0, self.coordinate.y).buffer(self.curvature, resolution=100)
103
+ arc4 = geo.Point(x1, self.coordinate.y).buffer(self.curvature, resolution=100)
104
+
105
+ self.polygon = arc3.intersection(arc4)
@@ -8,7 +8,6 @@ from LightWave2D.grid import Grid
8
8
  from MPSPlots.render2D import SceneList
9
9
  import shapely.geometry as geo
10
10
  from matplotlib.path import Path
11
- import cv2
12
11
  from pydantic.dataclasses import dataclass
13
12
  from LightWave2D.components.base_class import BaseComponent
14
13
  import matplotlib.pyplot as plt
@@ -21,7 +20,7 @@ config_dict = dict(
21
20
  )
22
21
 
23
22
 
24
- @dataclass(config=config_dict)
23
+ @dataclass(kw_only=True, config=config_dict)
25
24
  class PointDetector(BaseComponent):
26
25
  """
27
26
  Represents a point detector within a simulation grid.
@@ -31,10 +30,8 @@ class PointDetector(BaseComponent):
31
30
  position (Tuple[float | str, float | str]): The (x, y) coordinates of the point detector on the grid.
32
31
  data (numpy.ndarray): The data collected by the detector over time.
33
32
  """
34
- grid: Grid
35
33
  position: Union[Tuple[float | str, float | str]]
36
34
  data: numpy.ndarray = field(init=False)
37
- facecolor: str = 'blue'
38
35
 
39
36
  def __post_init__(self):
40
37
  """
@@ -85,4 +82,4 @@ class PointDetector(BaseComponent):
85
82
  color=self.facecolor
86
83
  )
87
84
 
88
-
85
+ # -
@@ -6,7 +6,8 @@ from typing import Tuple, NoReturn, Optional, Union
6
6
  import numpy
7
7
  from LightWave2D.physics import Physics
8
8
  from LightWave2D.grid import Grid
9
- from LightWave2D.components import Waveguide, Circle, Square, Ellipse
9
+ from LightWave2D.components.basic import Circle, Square, Ellipse, Triangle
10
+ from LightWave2D.components.complex import Lense, Grating, RingResonator
10
11
  from LightWave2D.source import PointSource, LineSource
11
12
  from LightWave2D.detector import PointDetector
12
13
  from LightWave2D.pml import PML
@@ -58,7 +59,7 @@ class Experiment:
58
59
  raise ValueError("Axis must be 'x' or 'y'.")
59
60
  return gradient
60
61
 
61
- def plot(self, unit_size: int = 4, colormap: Optional[Union[str, object]] = 'Blues') -> NoReturn:
62
+ def plot(self, unit_size: int = 6, colormap: Optional[Union[str, object]] = 'Blues') -> NoReturn:
62
63
  """
63
64
  Generates a plot of the FDTD simulation setup using a specified colormap.
64
65
 
@@ -72,13 +73,7 @@ class Experiment:
72
73
  Returns:
73
74
  SceneList: A SceneList object that contains the constructed plot.
74
75
  """
75
- figsize = int(unit_size), int(unit_size * self.grid.size_y / self.grid.size_x)
76
- figure, ax = plt.subplots(1, 1, figsize=figsize)
77
-
78
- ax.set_title('FDTD Simulation')
79
- ax.set_xlabel(r'x position [$\mu$m]')
80
- ax.set_xlabel(r'y position [$\mu$m]')
81
- ax.set_aspect('equal')
76
+ figure, ax = self.get_figure_ax(unit_size=unit_size)
82
77
 
83
78
  # Add PML layers to the plot if present
84
79
  if self.pml:
@@ -134,9 +129,24 @@ class Experiment:
134
129
  return Square(grid=self.grid, **kwargs)
135
130
 
136
131
  @add_to_component
137
- def add_waveguide(self, **kwargs) -> Waveguide:
132
+ def add_triangle(self, **kwargs) -> Square:
138
133
  """General method to add a component to the simulation."""
139
- return Waveguide(grid=self.grid, **kwargs)
134
+ return Triangle(grid=self.grid, **kwargs)
135
+
136
+ @add_to_component
137
+ def add_lense(self, **kwargs) -> Square:
138
+ """General method to add a component to the simulation."""
139
+ return Lense(grid=self.grid, **kwargs)
140
+
141
+ @add_to_component
142
+ def add_grating(self, **kwargs) -> Square:
143
+ """General method to add a component to the simulation."""
144
+ return Grating(grid=self.grid, **kwargs)
145
+
146
+ @add_to_component
147
+ def add_ring_resonator(self, **kwargs) -> Square:
148
+ """General method to add a component to the simulation."""
149
+ return RingResonator(grid=self.grid, **kwargs)
140
150
 
141
151
  @add_to_source
142
152
  def add_point_source(self, **kwargs) -> PointSource:
@@ -227,7 +237,7 @@ class Experiment:
227
237
  for detector in self.detectors:
228
238
  detector.update_data(field=self.Ez_t)
229
239
 
230
- def plot_frame(self, frame_number: int, unit_size: int = 4, colormap: Optional[Union[str, object]] = colormaps.polytechnique.blue_black_red) -> SceneList:
240
+ def plot_frame(self, frame_number: int, scale_max: float = 5, unit_size: int = 6, colormap: Optional[Union[str, object]] = colormaps.polytechnique.blue_black_red) -> SceneList:
231
241
  """
232
242
  Creates a plot of a specific frame from the FDTD simulation.
233
243
 
@@ -242,13 +252,7 @@ class Experiment:
242
252
  Returns:
243
253
  SceneList: A SceneList object that contains the constructed plot.
244
254
  """
245
- figsize = int(unit_size), int(unit_size * self.grid.size_y / self.grid.size_x)
246
- figure, ax = plt.subplots(1, 1, figsize=figsize)
247
-
248
- ax.set_title('FDTD Simulation')
249
- ax.set_xlabel(r'x position [$\mu$m]')
250
- ax.set_xlabel(r'y position [$\mu$m]')
251
- ax.set_aspect('equal')
255
+ figure, ax = self.get_figure_ax(unit_size=unit_size)
252
256
 
253
257
  image = ax.pcolormesh(
254
258
  self.grid.x_stamp,
@@ -262,13 +266,13 @@ class Experiment:
262
266
 
263
267
  vmin, vmax = image.get_clim()
264
268
 
265
- max_diff = max(abs(vmin), abs(vmax))
269
+ max_diff = max(abs(vmin), abs(vmax)) / scale_max
266
270
 
267
271
  image.set_clim([-max_diff, max_diff])
268
272
 
269
273
  plt.show()
270
274
 
271
- def save_frame_as_image(self, frame_number: int, filename: str, unit_size: int = 4, dpi: int = 200, colormap: Optional[Union[str, object]] = colormaps.polytechnique.blue_black_red) -> None:
275
+ def save_frame_as_image(self, frame_number: int, filename: str, scale_max: float = 5, unit_size: int = 6, dpi: int = 200, colormap: Optional[Union[str, object]] = colormaps.polytechnique.blue_black_red) -> None:
272
276
  """
273
277
  Saves a specific frame from the FDTD simulation as an image file at the specified resolution.
274
278
 
@@ -285,13 +289,7 @@ class Experiment:
285
289
  Returns:
286
290
  None: This method does not return any value, but saves an image file.
287
291
  """
288
- figsize = int(unit_size), int(unit_size * self.grid.size_y / self.grid.size_x)
289
- figure, ax = plt.subplots(1, 1, figsize=figsize)
290
-
291
- ax.set_title('FDTD Simulation')
292
- ax.set_xlabel(r'x position [$\mu$m]')
293
- ax.set_xlabel(r'y position [$\mu$m]')
294
- ax.set_aspect('equal')
292
+ figure, ax = self.get_figure_ax(unit_size=unit_size)
295
293
 
296
294
  image = ax.pcolormesh(
297
295
  self.grid.x_stamp,
@@ -305,13 +303,24 @@ class Experiment:
305
303
 
306
304
  vmin, vmax = image.get_clim()
307
305
 
308
- max_diff = max(abs(vmin), abs(vmax))
306
+ max_diff = max(abs(vmin), abs(vmax)) / scale_max
309
307
 
310
308
  image.set_clim([-max_diff, max_diff])
311
309
 
312
310
  plt.savefig(filename, dpi=dpi)
313
311
 
314
- def render_propagation(self, skip_frame: int = 10, unit_size: int = 4, colormap: Optional[Union[str, object]] = colormaps.blue_black_red) -> animation.FuncAnimation:
312
+ def get_figure_ax(self, unit_size: int = 6) -> Tuple:
313
+ figsize = int(unit_size), int(unit_size * self.grid.size_y / self.grid.size_x)
314
+ figure, ax = plt.subplots(1, 1, figsize=figsize)
315
+
316
+ ax.set_title('FDTD Simulation')
317
+ ax.set_xlabel(r'x position [$\mu$m]')
318
+ ax.set_ylabel(r'y position [$\mu$m]')
319
+ ax.set_aspect('equal')
320
+
321
+ return figure, ax
322
+
323
+ def render_propagation(self, skip_frame: int = 10, scale_max: float = 5, unit_size: int = 6, colormap: Optional[Union[str, object]] = colormaps.blue_black_red) -> animation.FuncAnimation:
315
324
  """
316
325
  Renders the propagation of a field as an animation using matplotlib.
317
326
 
@@ -326,13 +335,7 @@ class Experiment:
326
335
  Returns:
327
336
  animation.FuncAnimation: The animation object that can be displayed or saved.
328
337
  """
329
- figsize = int(unit_size), int(unit_size * self.grid.size_y / self.grid.size_x)
330
- figure, ax = plt.subplots(1, 1, figsize=figsize)
331
-
332
- ax.set_title('FDTD Simulation')
333
- ax.set_xlabel(r'x position [$\mu$m]')
334
- ax.set_xlabel(r'y position [$\mu$m]')
335
- ax.set_aspect('equal')
338
+ figure, ax = self.get_figure_ax(unit_size=unit_size)
336
339
 
337
340
  # Initialize the field display
338
341
  initial_field = numpy.zeros(self.Ez_t[0].shape).T
@@ -351,7 +354,7 @@ class Experiment:
351
354
  artist_list.append(component.add_to_ax(ax))
352
355
 
353
356
  # Set color limits based on the maximum field amplitude
354
- max_amplitude = abs(self.Ez_t).max() / 2
357
+ max_amplitude = abs(self.Ez_t).max() / scale_max
355
358
  field_artist.set_clim(vmin=-max_amplitude, vmax=max_amplitude)
356
359
 
357
360
  def update(frame) -> list:
@@ -58,7 +58,7 @@ class PML():
58
58
  label='PML'
59
59
  )
60
60
 
61
- def plot(self, unit_size: int = 4) -> NoReturn:
61
+ def plot(self, unit_size: int = 6) -> NoReturn:
62
62
  figsize = int(unit_size), int(unit_size * self.grid.size_y / self.grid.size_x)
63
63
  figure, ax = plt.subplots(1, 1, figsize=figsize)
64
64