emerge 0.6.6__py3-none-any.whl → 0.6.8__py3-none-any.whl

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.

Potentially problematic release.


This version of emerge might be problematic. Click here for more details.

@@ -30,6 +30,7 @@ class Alignment(Enum):
30
30
  CENTER = 1
31
31
  CORNER = 2
32
32
 
33
+
33
34
  class Box(GeoVolume):
34
35
  """Creates a box volume object.
35
36
  Specify the alignment of the box with the provided position. The options are CORNER (default)
@@ -100,7 +101,7 @@ class Box(GeoVolume):
100
101
 
101
102
  tags = list(reduce(lambda a,b: a+b, tagslist))
102
103
  return FaceSelection(tags)
103
-
104
+
104
105
 
105
106
  class Sphere(GeoVolume):
106
107
  """Generates a sphere objected centered ont he position with the given radius
@@ -122,6 +123,7 @@ class Sphere(GeoVolume):
122
123
  x,y,z = position
123
124
  self.tags: list[int] = [gmsh.model.occ.addSphere(x,y,z,radius),]
124
125
 
126
+
125
127
  class XYPlate(GeoSurface):
126
128
  """Generates and XY-plane oriented plate
127
129
 
@@ -209,27 +211,43 @@ class Plate(GeoSurface):
209
211
  tags: list[int] = [gmsh.model.occ.addPlaneSurface([tag_wire,]),]
210
212
  super().__init__(tags)
211
213
 
214
+
212
215
  class Cylinder(GeoVolume):
216
+ """Generates a Cylinder object in 3D space.
217
+ The cylinder will always be placed in the origin of the provided CoordinateSystem.
218
+ The bottom cylinder plane is always placed in the XY-plane. The length of the cylinder is
219
+ oriented along the Z-axis.
213
220
 
221
+ By default the cylinder uses the Open Cascade modeling for a cylinder. In this representation
222
+ the surface of the cylinder is approximated with a tolerance thay may be irregular.
223
+ As an alternative, the argument Nsections may be provided in which case the Cylinder is replaced
224
+ by an extrusion of a regular N-sided polygon.
225
+
226
+ Args:
227
+ radius (float): The radius of the Cylinder
228
+ height (float): The height of the Cylinder
229
+ cs (CoordinateSystem, optional): The coordinate system. Defaults to GCS.
230
+ Nsections (int, optional): The number of sections. Defaults to None.
231
+ """
214
232
  def __init__(self,
215
233
  radius: float,
216
234
  height: float,
217
- cs: CoordinateSystem = None,
218
- Nsections: int = None):
235
+ cs: CoordinateSystem = GCS,
236
+ Nsections: int | None = None):
219
237
  """Generates a Cylinder object in 3D space.
220
- The cyllinder will always be placed in the origin of the provided CoordinateSystem.
221
- The bottom cyllinder plane is always placed in the XY-plane. The lenth of the cyllinder is
238
+ The cylinder will always be placed in the origin of the provided CoordinateSystem.
239
+ The bottom cylinder plane is always placed in the XY-plane. The length of the cylinder is
222
240
  oriented along the Z-axis.
223
241
 
224
- By default the cyllinder uses the Open Cascade modeling for a cyllinder. In this representation
225
- the surface of the cyllinder is approximated with a tolerance thay may be irregular.
242
+ By default the cylinder uses the Open Cascade modeling for a cylinder. In this representation
243
+ the surface of the cylinder is approximated with a tolerance thay may be irregular.
226
244
  As an alternative, the argument Nsections may be provided in which case the Cylinder is replaced
227
245
  by an extrusion of a regular N-sided polygon.
228
246
 
229
247
  Args:
230
248
  radius (float): The radius of the Cylinder
231
249
  height (float): The height of the Cylinder
232
- cs (CoordinateSystem, optional): The coordinate system. Defaults to None.
250
+ cs (CoordinateSystem, optional): The coordinate system. Defaults to GCS.
233
251
  Nsections (int, optional): The number of sections. Defaults to None.
234
252
  """
235
253
  ax = cs.zax.np
@@ -270,33 +288,46 @@ class Cylinder(GeoVolume):
270
288
  xo, yo, zo = self.cs.in_global_cs(x.flatten(), y.flatten(), z.flatten())
271
289
  return xo, yo, zo
272
290
 
291
+
273
292
  class CoaxCylinder(GeoVolume):
274
- """A coaxial cylinder with an inner and outer radius."""
275
-
293
+ """Generates a Coaxial cylinder object in 3D space.
294
+ The coaxial cylinder will always be placed in the origin of the provided CoordinateSystem.
295
+ The bottom coax plane is always placed in the XY-plane. The lenth of the coax is
296
+ oriented along the Z-axis.
297
+
298
+ By default the coax uses the Open Cascade modeling for a cylinder. In this representation
299
+ the surface of the cylinder is approximated with a tolerance thay may be irregular.
300
+ As an alternative, the argument Nsections may be provided in which case the Cylinder is replaced
301
+ by an extrusion of a regular N-sided polygon.
302
+
303
+ Args:
304
+ radius (float): The radius of the Cylinder
305
+ height (float): The height of the Cylinder
306
+ cs (CoordinateSystem, optional): The coordinate system. Defaults to GCS.
307
+ Nsections (int, optional): The number of sections. Defaults to None.
308
+ """
276
309
  def __init__(self,
277
310
  rout: float,
278
311
  rin: float,
279
312
  height: float,
280
- cs: CoordinateSystem = None,
281
- Nsections: int = None):
282
- """Generates a Coaxial cyllinder object in 3D space.
283
- The coaxial cyllinder will always be placed in the origin of the provided CoordinateSystem.
313
+ cs: CoordinateSystem = GCS,
314
+ Nsections: int | None = None):
315
+ """Generates a Coaxial cylinder object in 3D space.
316
+ The coaxial cylinder will always be placed in the origin of the provided CoordinateSystem.
284
317
  The bottom coax plane is always placed in the XY-plane. The lenth of the coax is
285
318
  oriented along the Z-axis.
286
319
 
287
- By default the coax uses the Open Cascade modeling for a cyllinder. In this representation
288
- the surface of the cyllinder is approximated with a tolerance thay may be irregular.
320
+ By default the coax uses the Open Cascade modeling for a cylinder. In this representation
321
+ the surface of the cylinder is approximated with a tolerance thay may be irregular.
289
322
  As an alternative, the argument Nsections may be provided in which case the Cylinder is replaced
290
323
  by an extrusion of a regular N-sided polygon.
291
324
 
292
325
  Args:
293
326
  radius (float): The radius of the Cylinder
294
327
  height (float): The height of the Cylinder
295
- cs (CoordinateSystem, optional): The coordinate system. Defaults to None.
328
+ cs (CoordinateSystem, optional): The coordinate system. Defaults to GCS.
296
329
  Nsections (int, optional): The number of sections. Defaults to None.
297
330
  """
298
- if cs is None:
299
- cs = GCS
300
331
  if rout <= rin:
301
332
  raise ValueError("Outer radius must be greater than inner radius.")
302
333
 
@@ -336,7 +367,7 @@ class CoaxCylinder(GeoVolume):
336
367
  return xo, yo, zo
337
368
 
338
369
  class HalfSphere(GeoVolume):
339
-
370
+ """A half sphere volume."""
340
371
  def __init__(self,
341
372
  radius: float,
342
373
  position: tuple = (0,0,0),
@@ -362,8 +393,6 @@ class HalfSphere(GeoVolume):
362
393
  self._add_face_pointer('back',np.array(position), np.array(direction))
363
394
  self._add_face_pointer('bottom',np.array(position), np.array(direction))
364
395
  self._add_face_pointer('face',np.array(position), np.array(direction))
365
-
366
-
367
396
 
368
397
 
369
398
  class OldBox(GeoVolume):
@@ -389,8 +418,6 @@ class OldBox(GeoVolume):
389
418
  alignment (Alignment, optional): Which point of the box is placed at the position.
390
419
  Defaults to Alignment.CORNER.
391
420
  """
392
-
393
-
394
421
  if alignment is Alignment.CORNER:
395
422
  position = (position[0]+width/2, position[1]+depth/2, position[2])
396
423
  elif alignment is Alignment.CENTER:
@@ -472,7 +499,6 @@ class OldBox(GeoVolume):
472
499
  self._add_face_pointer('right', pc + width/2*wax, wax)
473
500
  self._add_face_pointer('top', pc + height/2*hax, hax)
474
501
  self._add_face_pointer('bottom', pc - height/2*hax, -hax)
475
-
476
502
 
477
503
  def outside(self, *exclude: Literal['bottom','top','right','left','front','back']) -> FaceSelection:
478
504
  """Select all outside faces except for the once specified by outside
@@ -485,8 +511,17 @@ class OldBox(GeoVolume):
485
511
  tags = list(reduce(lambda a,b: a+b, tagslist))
486
512
  return FaceSelection(tags)
487
513
 
514
+
488
515
  class Cone(GeoVolume):
489
-
516
+ """Constructis a cone that starts at position p0 and is aimed in the given direction.
517
+ r1 is the start radius and r2 the end radius. The magnitude of direction determines its length.
518
+
519
+ Args:
520
+ p0 (tuple[float, float, float]): _description_
521
+ direction (tuple[float, float, float]): _description_
522
+ r1 (float): _description_
523
+ r2 (float): _description_
524
+ """
490
525
  def __init__(self, p0: tuple[float, float, float],
491
526
  direction: tuple[float, float, float],
492
527
  r1: float,
@@ -376,7 +376,7 @@ class GeoObject:
376
376
  return self
377
377
 
378
378
  def prio_up(self) -> GeoObject:
379
- """Increase priority by 1
379
+ """Increases the material selection priority by 1
380
380
 
381
381
  Returns:
382
382
  GeoObject: _description_
@@ -385,7 +385,7 @@ class GeoObject:
385
385
  return self
386
386
 
387
387
  def prio_down(self) -> GeoObject:
388
- """Decrase priority by 1
388
+ """Decreases the material selection priority by 1
389
389
 
390
390
  Returns:
391
391
  GeoObject: _description_
@@ -394,7 +394,7 @@ class GeoObject:
394
394
  return self
395
395
 
396
396
  def background(self) -> GeoObject:
397
- """Set the priority to be on the background.
397
+ """Set the material selection priority to be on the background.
398
398
 
399
399
  Returns:
400
400
  GeoObject: _description_
@@ -403,7 +403,7 @@ class GeoObject:
403
403
  return self
404
404
 
405
405
  def foreground(self) -> GeoObject:
406
- """Set the priority to be on top.
406
+ """Set the material selection priority to be on top.
407
407
 
408
408
  Returns:
409
409
  GeoObject: _description_
@@ -16,108 +16,353 @@
16
16
  # <https://www.gnu.org/licenses/>.
17
17
 
18
18
  import numpy as np
19
- from dataclasses import dataclass
20
19
  from typing import Callable
20
+ import inspect
21
+
22
+
23
+ def num_args(func):
24
+ sig = inspect.signature(func)
25
+ return sum(
26
+ 1
27
+ for p in sig.parameters.values()
28
+ if p.default is inspect._empty and p.kind in (
29
+ p.POSITIONAL_ONLY,
30
+ p.POSITIONAL_OR_KEYWORD,
31
+ p.KEYWORD_ONLY
32
+ )
33
+ )
34
+
35
+ def _to_mat(value: float | complex | int | np.ndarray) -> np.ndarray:
36
+ if np.isscalar(value):
37
+ return np.eye(3)*value
38
+ if value.shape in ((3,), (3,1), (1,3)):
39
+ return np.diag(np.ravel(value))
40
+ if value.shape == (3,3):
41
+ return value
42
+ else:
43
+ return ValueError(f'Trying to parse {value} as a material property tensor but it cant be identified as scalar, vector or matrix')
44
+
45
+ class MatProperty:
46
+ _freq_dependent: bool = False
47
+ _coord_dependent: bool = False
48
+ """The MatProperty class is an interface for EMerge to deal with frequency and coordinate dependent material properties
49
+ """
50
+
51
+ def __init__(self, value: float | complex | int | np.ndarray):
52
+ self.value: np.ndarray = _to_mat(value)
53
+
54
+ self._apply_to: np.ndarray = np.array([], dtype=np.int64)
55
+ self._x: np.ndarray = np.array([], dtype=np.float64)
56
+ self._y: np.ndarray = np.array([], dtype=np.float64)
57
+ self._z: np.ndarray = np.array([], dtype=np.float64)
58
+
59
+ self._fmax = lambda f: value
60
+
61
+ def initialize(self, x: np.ndarray, y: np.ndarray, z: np.ndarray, ids: np.ndarray) -> None:
62
+ self._apply_to = np.concatenate([self._apply_to, ids])
63
+ self._x = np.concatenate([self._x, x])
64
+ self._y = np.concatenate([self._y, y])
65
+ self._z = np.concatenate([self._z, z])
66
+
67
+ def __call__(self, f: float, data: np.ndarray) -> np.ndarray:
68
+ data[:,:,self._apply_to] = np.repeat(self.value[:,:,np.newaxis], self._apply_to.shape[0], axis=2)
69
+ return data
70
+
71
+ def scalar(self, f: float):
72
+ return self.value[0,0]
73
+
74
+ def reset(self) -> None:
75
+ self._apply_to: np.ndarray = np.array([], dtype=np.int64)
76
+ self._x: np.ndarray = np.array([], dtype=np.float64)
77
+ self._y: np.ndarray = np.array([], dtype=np.float64)
78
+ self._z: np.ndarray = np.array([], dtype=np.float64)
79
+
80
+ class FreqDependent(MatProperty):
81
+ _freq_dependent: bool = True
82
+ _coord_dependent: bool = False
83
+
84
+ def __init__(self,
85
+ scalar: Callable | None = None,
86
+ vector: Callable | None = None,
87
+ matrix: Callable | None = None):
88
+ """Creates a frequency dependent property object.
89
+
90
+ If the property is defined as a scalar value, use the "scalar" argument
91
+ If the property is a diagonal rank-2 tensor, use the "vector" argument
92
+ If the property is a full rank-2 tensor, use the "matrix" argument
93
+
94
+ The max_value property must be set to tell EMerge how height this value can get
95
+ as it will be used to define the discretization of the mesh.
96
+
97
+ Args:
98
+ scalar (Callable | None, optional): The scalar value function returning a float/complex. Defaults to None.
99
+ vector (Callable | None, optional): The diagonal rank-2 tensor function returning a (3,) array. Defaults to None.
100
+ matrix (Callable | None, optional): The rank-2 tensor function returning a (3,3) array. Defaults to None.
101
+
102
+ Returns:
103
+ _type_: _description_
104
+ """
105
+ if scalar is not None:
106
+ def _func(f: float) -> np.ndarray:
107
+ return np.eye(3)*scalar(f)
108
+ if vector is not None:
109
+ def _func(f: float) -> np.ndarray:
110
+ return np.diag(np.ravel(vector(f)))
111
+
112
+ if matrix is not None:
113
+ _func = matrix
114
+
115
+ self._func: Callable = _func
116
+
117
+ self._apply_to: np.ndarray = np.array([], dtype=np.int64)
118
+ self._x: np.ndarray = np.array([], dtype=np.float64)
119
+ self._y: np.ndarray = np.array([], dtype=np.float64)
120
+ self._z: np.ndarray = np.array([], dtype=np.float64)
121
+
122
+ self._fmax: Callable = lambda f: np.max(np.ravel(self._func(f)))
123
+
124
+ def initialize(self, x: np.ndarray, y: np.ndarray, z: np.ndarray, ids: np.ndarray) -> None:
125
+ self._apply_to = np.concatenate([self._apply_to, ids])
126
+
127
+ def __call__(self, f: float, data: np.ndarray) -> np.ndarray:
128
+ data[:,:,self._apply_to] = np.repeat(self._func(f)[:,:,np.newaxis], self._apply_to.shape[0], axis=2)
129
+ return data
130
+
131
+ def scalar(self, f: float):
132
+ return self._func(f)[0,0]
133
+
134
+ class CoordDependent(MatProperty):
135
+ _freq_dependent: bool = False
136
+ _coord_dependent: bool = True
137
+ def __init__(self,
138
+ max_value: float,
139
+ scalar: Callable | None = None,
140
+ vector: Callable | None = None,
141
+ matrix: Callable | None = None,
142
+ ):
143
+ """Creates a coordinate dependent property object.
144
+
145
+ If the property is defined as a scalar value, use the "scalar" argument.
146
+
147
+ If the property is a diagonal rank-2 tensor, use the "vector" argument.
148
+
149
+ If the property is a full rank-2 tensor, use the "matrix" argument.
150
+
151
+
152
+ The max_value property must be set to tell EMerge how height this value can get
153
+ as it will be used to define the discretization of the mesh.
154
+
155
+ Args:
156
+ max_value (float): The heighest value of the material property
157
+ scalar (Callable | None, optional): The scalar value function returning a float/complex. Defaults to None.
158
+ vector (Callable | None, optional): The diagonal rank-2 tensor function returning a (3,) array. Defaults to None.
159
+ matrix (Callable | None, optional): The rank-2 tensor function returning a (3,3) array. Defaults to None.
160
+
161
+ Returns:
162
+ _type_: _description_
163
+ """
164
+
165
+ if scalar is not None:
166
+ def _func(x, y, z) -> np.ndarray:
167
+ return np.eye(3)[:, :, None] * scalar(x,y,z)[None, None, :]
168
+ if vector is not None:
169
+ def _func(x, y, z) -> np.ndarray:
170
+ N = x.shape[0]
171
+ out = np.zeros((3, 3, N), dtype=vector(0,0,0).dtype)
172
+ idx = np.arange(3)
173
+ out[idx, idx, :] = vector(x,y,z)
174
+ return out
175
+ if matrix is not None:
176
+ _func = matrix
177
+
178
+ self._func: Callable = _func
179
+ self._apply_to: np.ndarray = np.array([], dtype=np.int64)
180
+ self._x: np.ndarray = np.array([], dtype=np.float64)
181
+ self._y: np.ndarray = np.array([], dtype=np.float64)
182
+ self._z: np.ndarray = np.array([], dtype=np.float64)
183
+
184
+ self._values: np.ndarray = None
185
+ self._fmax: Callable = lambda f: max_value
186
+
187
+ def initialize(self, x: np.ndarray, y: np.ndarray, z: np.ndarray, ids: np.ndarray) -> None:
188
+ self._apply_to = np.concatenate([self._apply_to, ids])
189
+ self._x = np.concatenate([self._x, x])
190
+ self._y = np.concatenate([self._y, y])
191
+ self._z = np.concatenate([self._z, z])
192
+
193
+ def __call__(self, f: float, data: np.ndarray) -> np.ndarray:
194
+ data[:,:,self._apply_to] = self._func(self._x, self._y, self._z)
195
+ return data
196
+
197
+ def scalar(self, f: float):
198
+ return self._func(0,0,0)[0,0]
199
+
200
+ class FreqCoordDependent(MatProperty):
201
+ _freq_dependent: bool = True
202
+ _coord_dependent: bool = True
203
+
204
+ def __init__(self,
205
+ max_value: float,
206
+ scalar: Callable | None = None,
207
+ vector: Callable | None = None,
208
+ matrix: Callable | None = None):
209
+ """Creates a frequency and coordinate dependent property object.
210
+
211
+ If the property is defined as a scalar value, use the "scalar" argument.
212
+
213
+ If the property is a diagonal rank-2 tensor, use the "vector" argument.
214
+
215
+ If the property is a full rank-2 tensor, use the "matrix" argument.
216
+
217
+ The max_value property must be set to tell EMerge how height this value can get
218
+ as it will be used to define the discretization of the mesh.
219
+
220
+ Args:
221
+ max_value (float): The heighest value of the material property
222
+ scalar (Callable | None, optional): The scalar value function returning a float/complex. Defaults to None.
223
+ vector (Callable | None, optional): The diagonal rank-2 tensor function returning a (3,) array. Defaults to None.
224
+ matrix (Callable | None, optional): The rank-2 tensor function returning a (3,3) array. Defaults to None.
225
+
226
+ Returns:
227
+ _type_: _description_
228
+ """
229
+ if scalar is not None:
230
+ def _func(f, x, y, z) -> np.ndarray:
231
+ return np.eye(3)[:, :, None] * scalar(f,x,y,z)[None, None, :]
232
+ if vector is not None:
233
+ def _func(f,x, y, z) -> np.ndarray:
234
+ N = x.shape[0]
235
+ out = np.zeros((3, 3, N), dtype=vector(1e9,0,0,0).dtype)
236
+ idx = np.arange(3)
237
+ out[idx, idx, :] = vector(f,x,y,z)
238
+ return out
239
+ if matrix is not None:
240
+ _func = matrix
241
+
242
+ self._func: Callable = _func
243
+
244
+ self._apply_to: np.ndarray = np.array([], dtype=np.int64)
245
+ self._x: np.ndarray = np.array([], dtype=np.float64)
246
+ self._y: np.ndarray = np.array([], dtype=np.float64)
247
+ self._z: np.ndarray = np.array([], dtype=np.float64)
248
+
249
+ self._fmax: Callable = lambda f: max_value
250
+
251
+ def initialize(self, x: np.ndarray, y: np.ndarray, z: np.ndarray, ids: np.ndarray) -> None:
252
+ self._apply_to = np.concatenate([self._apply_to, ids])
253
+ self._x = np.concatenate([self._x, x])
254
+ self._y = np.concatenate([self._y, y])
255
+ self._z = np.concatenate([self._z, z])
256
+
257
+ def __call__(self, f: float, data: np.ndarray) -> np.ndarray:
258
+ data[:,:,self._apply_to] = self._func(f,self._x, self._y,self._z)
259
+ return data
260
+
261
+ def scalar(self, f: float):
262
+ return self._func(f, 0,0,0)[0,0]
263
+
264
+ # To be finished once its clear how to deal with default values for functions
265
+
266
+ # def parse_material_property(value: complex | float | Callable):
267
+ # if not isinstance(value, Callable):
268
+ # return MatProperty(value)
269
+ # pass
21
270
 
22
- @dataclass
23
271
  class Material:
24
272
  """The Material class generalizes a material in the EMerge FEM environment.
25
273
 
26
274
  If a scalar value is provided for the relative permittivity or the relative permeability
27
275
  it will be used as multiplication entries for the material property diadic as identity matrix.
28
276
 
29
- Additionally, a function may be provided that computes a coordinate dependent material property
30
- for _fer. For example: Material(_fer = lambda x,y,z: ...).
31
- The x,y and z coordinates are provided as a (N,) np.ndarray. The return array must be of shape (3,3,N)!
277
+ Additionally, a frequency, coordinate or both frequency and coordinate dependent material property
278
+ may be supplied for the properties: er, ur, tand and cond.
279
+
280
+ To supply a frequency-dependent property use: emerge.FreqDependent()
281
+ To supply a coordinate-dependent property use: emerge.CoordDependent()
282
+ to supply a frequency and coordinate dependent property use: emerge.FreqCoordDependent()
32
283
 
33
284
  """
34
- er: float = 1
35
- ur: float = 1
36
- tand: float = 0
37
- cond: float = 0
38
- _neff: float | None = None
39
- _fer: Callable | None= None
40
- _fur: Callable | None = None
41
- color: str = "#BEBEBE"
42
- _color_rgb: tuple[float,float,float] = (0.5, 0.5, 0.5)
43
- opacity: float = 1.0
44
-
45
- def __post_init__(self):
46
- hex_str = self.color.lstrip('#')
47
- self._color_rgb = tuple(int(hex_str[i:i+2], 16)/255.0 for i in (0, 2, 4))
48
-
49
- @property
50
- def sigma(self) -> float:
51
- return self.cond
285
+ def __init__(self,
286
+ er: float | complex | np.ndarray | MatProperty = 1.0,
287
+ ur: float | complex | np.ndarray | MatProperty = 1.0,
288
+ tand: float | MatProperty = 0.0,
289
+ cond: float | MatProperty = 0.0,
290
+ _neff: float | None = None,
291
+ color: str ="#BEBEBE",
292
+ opacity: float = 1.0):
52
293
 
53
- @property
54
- def color_rgb(self) -> tuple[float,float,float]:
55
- return self._color_rgb
56
-
57
- @property
58
- def ermat(self) -> np.ndarray:
59
- if isinstance(self.er, (float, complex, int, np.float64, np.complex128)):
60
- return self.er*(1-1j*self.tand)*np.eye(3)
61
- else:
62
- return self.er*(1-1j*self.tand)
63
-
64
- @property
65
- def urmat(self) -> np.ndarray:
66
- if isinstance(self.ur, (float, complex, int, np.float64, np.complex128)):
67
- return self.ur*np.eye(3)
294
+ if not isinstance(er, MatProperty):
295
+ er = MatProperty(er)
296
+ if not isinstance(ur, MatProperty):
297
+ ur = MatProperty(ur)
298
+ if not isinstance(tand, MatProperty):
299
+ tand = MatProperty(tand)
300
+ if not isinstance(cond, MatProperty):
301
+ cond = MatProperty(cond)
302
+
303
+ self.er: MatProperty = er
304
+ self.ur: MatProperty = ur
305
+ self.tand: MatProperty = tand
306
+ self.cond: MatProperty = cond
307
+
308
+ self.color: str = color
309
+ self.opacity: float = opacity
310
+ if _neff is None:
311
+ self._neff: Callable = lambda f: np.sqrt(self.ur._fmax(f)*self.er._fmax(f))
68
312
  else:
69
- return self.ur
313
+ self._neff: Callable = lambda f: _neff
314
+ hex_str = self.color.lstrip('#')
315
+ self._color_rgb = tuple(int(hex_str[i:i+2], 16)/255.0 for i in (0, 2, 4))
70
316
 
71
- @property
72
- def neff(self) -> complex:
73
- if self._neff is not None:
74
- return self._neff
75
- er = self.ermat[0,0]
76
- ur = self.urmat[0,0]
317
+ def initialize(self, xs: np.ndarray, ys: np.ndarray, zs: np.ndarray, ids: np.ndarray):
318
+ """Initializes the Material properties to be evaluated at xyz-coordinates for
319
+ a given set of tetrahedral ids.
77
320
 
78
- return np.abs(np.sqrt(er*(1-1j*self.tand)*ur))
321
+ Args:
322
+ xs (np.ndarray): The tet-centroid x-coordinates
323
+ ys (np.ndarray): The tet-centroid y-coordinates
324
+ zs (np.ndarray): The tet-centroid z-coordinates
325
+ ids (np.ndarray): The tet-indices
326
+ """
327
+ self.er.initialize(xs, ys, zs, ids)
328
+ self.ur.initialize(xs, ys, zs, ids)
329
+ self.tand.initialize(xs, ys, zs, ids)
330
+ self.cond.initialize(xs, ys, zs, ids)
79
331
 
80
- @property
81
- def fer2d(self) -> Callable:
82
- if self._fer is None:
83
- return lambda x,y: self.er*(1-1j*self.tand)*np.ones_like(x)
84
- else:
85
- return self._fer
332
+ def reset(self) -> None:
333
+ """Resets assignment of material properties to coordiantes and tetrahedral indices.
334
+ """
335
+ self.er.reset()
336
+ self.ur.reset()
337
+ self.tand.reset()
338
+ self.cond.reset()
86
339
 
87
340
  @property
88
- def fur2d(self) -> Callable:
89
- if self._fur is None:
341
+ def frequency_dependent(self) -> bool:
342
+ """If The material property are at all frequency dependent.
90
343
 
91
- return lambda x,y: self.ur*np.ones_like(x)
92
- else:
93
- return self._fur
94
- @property
95
- def fer3d(self) -> Callable:
96
- if self._fer is None:
97
- return lambda x,y,z: self.er*(1-1j*self.tand)*np.ones_like(x)
98
- else:
99
- return self._fer
344
+ Returns:
345
+ bool: _description_
346
+ """
347
+ return self.er._freq_dependent or self.ur._freq_dependent or self.tand._freq_dependent or self.cond._freq_dependent
100
348
 
101
349
  @property
102
- def fur3d(self) -> Callable:
103
- if self._fur is None:
104
- return lambda x,y,z: self.ur*np.ones_like(x)
105
- else:
106
- return self._fur
107
- @property
108
- def fer3d_mat(self) -> Callable:
109
- if self._fer is None:
110
-
111
- return lambda x,y,z: np.repeat(self.ermat[:, :, np.newaxis], x.shape[0], axis=2)
112
- else:
113
- return self._fer
350
+ def coordinate_dependent(self) -> bool:
351
+ """If the material properties are at all coordinate dependent
352
+
353
+ Returns:
354
+ bool: _description_
355
+ """
356
+ return self.er._coord_dependent or self.ur._coord_dependent or self.tand._coord_dependent or self.cond._coord_dependent
357
+
358
+
359
+ def neff(self, f: float):
360
+ """ Computes the maximum occuring effective refractive index for this material."""
361
+ return self._neff(f)
114
362
 
115
363
  @property
116
- def fur3d_mat(self) -> Callable:
117
- if self._fur is None:
118
- return lambda x,y,z: np.repeat(self.urmat[:, :, np.newaxis], x.shape[0], axis=2)
119
- else:
120
- return self._fur
121
-
364
+ def color_rgb(self) -> tuple[float,float,float]:
365
+ return self._color_rgb
366
+
122
367
  AIR = Material(color="#4496f3", opacity=0.05)
123
368
  COPPER = Material(cond=5.8e7, color="#62290c")