morpho.py 0.0.3__tar.gz → 0.1.0__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.
- morpho_py-0.1.0/PKG-INFO +24 -0
- morpho_py-0.1.0/morpho/__init__.py +28 -0
- morpho_py-0.1.0/morpho/brillouinzone.py +339 -0
- morpho_py-0.1.0/morpho/geometry.py +265 -0
- morpho_py-0.1.0/morpho/solver.py +230 -0
- morpho_py-0.1.0/morpho/utils.py +58 -0
- morpho_py-0.1.0/pyproject.toml +64 -0
- morpho.py-0.0.3/PKG-INFO +0 -13
- morpho.py-0.0.3/morpho/__init__.py +0 -5
- morpho.py-0.0.3/morpho/brillouinzone.py +0 -80
- morpho.py-0.0.3/morpho/geometry.py +0 -69
- morpho.py-0.0.3/morpho/solver.py +0 -86
- morpho.py-0.0.3/morpho/utils.py +0 -39
- morpho.py-0.0.3/pyproject.toml +0 -25
- morpho.py-0.0.3/setup.py +0 -34
- {morpho.py-0.0.3 → morpho_py-0.1.0}/LICENSE +0 -0
- {morpho.py-0.0.3 → morpho_py-0.1.0}/morpho/exceptions.py +0 -0
morpho_py-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: morpho.py
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: PWE framework for photonic crystal analysis
|
|
5
|
+
License-Expression: MIT
|
|
6
|
+
License-File: LICENSE
|
|
7
|
+
Author: Tiago Vilela
|
|
8
|
+
Author-email: tiagovla@gmail.com
|
|
9
|
+
Requires-Python: >=3.9,<3.16
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.15
|
|
18
|
+
Provides-Extra: docs
|
|
19
|
+
Requires-Dist: matplotlib (>=3.4.3,<4.0.0)
|
|
20
|
+
Requires-Dist: mkdocs (>=1.6.1,<2) ; extra == "docs"
|
|
21
|
+
Requires-Dist: mkdocs-material (>=9.7.7) ; extra == "docs"
|
|
22
|
+
Requires-Dist: mkdocstrings[python] (>=0.24.0) ; extra == "docs"
|
|
23
|
+
Requires-Dist: numpy (>=1.20.1,<3.0.0)
|
|
24
|
+
Requires-Dist: scipy (>=1.6.2,<2.0.0)
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
__version__ = "0.1.0"
|
|
2
|
+
|
|
3
|
+
from morpho.brillouinzone import (
|
|
4
|
+
BrillouinZonePath,
|
|
5
|
+
BrillouinZonePath1D,
|
|
6
|
+
BrillouinZonePath2D,
|
|
7
|
+
BrillouinZonePath3D,
|
|
8
|
+
SymmetryPoint,
|
|
9
|
+
)
|
|
10
|
+
from morpho.geometry import Geometry, Geometry1D, Geometry2D, Geometry3D
|
|
11
|
+
from morpho.solver import Solver, Solver1D, Solver2D, Solver3D
|
|
12
|
+
|
|
13
|
+
__all__ = [
|
|
14
|
+
"__version__",
|
|
15
|
+
"BrillouinZonePath",
|
|
16
|
+
"BrillouinZonePath1D",
|
|
17
|
+
"BrillouinZonePath2D",
|
|
18
|
+
"BrillouinZonePath3D",
|
|
19
|
+
"SymmetryPoint",
|
|
20
|
+
"Geometry",
|
|
21
|
+
"Geometry1D",
|
|
22
|
+
"Geometry2D",
|
|
23
|
+
"Geometry3D",
|
|
24
|
+
"Solver",
|
|
25
|
+
"Solver1D",
|
|
26
|
+
"Solver2D",
|
|
27
|
+
"Solver3D",
|
|
28
|
+
]
|
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
"""This module implements classes related to the brillouinzone."""
|
|
2
|
+
|
|
3
|
+
from abc import ABC, abstractproperty
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from numbers import Integral
|
|
6
|
+
from typing import List, Optional, Tuple, Union
|
|
7
|
+
|
|
8
|
+
import numpy as np
|
|
9
|
+
from numpy.linalg import norm
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@dataclass
|
|
13
|
+
class BlochVectors:
|
|
14
|
+
"""Represents a data structure of Bloch wave vectors."""
|
|
15
|
+
|
|
16
|
+
values: np.ndarray
|
|
17
|
+
cumsum: np.ndarray
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class SymmetryPoint:
|
|
21
|
+
"""Model of a symmetry point at the brillouinzone.
|
|
22
|
+
|
|
23
|
+
Parameters
|
|
24
|
+
----------
|
|
25
|
+
point : Tuple[float, ...]
|
|
26
|
+
A point in the reciprocal domain.
|
|
27
|
+
name : str
|
|
28
|
+
A label to describe the point.
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
def __init__(self, point: Tuple[float, ...], name: str):
|
|
32
|
+
"""Initialize a SymmetryPoint."""
|
|
33
|
+
self.point = np.array(point)
|
|
34
|
+
self.name = name
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class BrillouinZonePathBase(ABC):
|
|
38
|
+
"""BrillouinZoneBase class."""
|
|
39
|
+
|
|
40
|
+
def __init__(
|
|
41
|
+
self,
|
|
42
|
+
path: List[SymmetryPoint],
|
|
43
|
+
n_points: int = 50,
|
|
44
|
+
strategy: str = "linear",
|
|
45
|
+
points_per_segment: Optional[int] = None,
|
|
46
|
+
):
|
|
47
|
+
self.path_points = path
|
|
48
|
+
self.n_points = n_points
|
|
49
|
+
self.strat = strategy
|
|
50
|
+
if points_per_segment is not None:
|
|
51
|
+
if (
|
|
52
|
+
not isinstance(points_per_segment, Integral)
|
|
53
|
+
or isinstance(points_per_segment, bool)
|
|
54
|
+
or points_per_segment < 1
|
|
55
|
+
):
|
|
56
|
+
raise ValueError("points_per_segment must be a positive integer.")
|
|
57
|
+
if len(path) < 2:
|
|
58
|
+
raise ValueError(
|
|
59
|
+
"At least two symmetry points are needed for segment sampling."
|
|
60
|
+
)
|
|
61
|
+
points_per_segment = int(points_per_segment)
|
|
62
|
+
self.points_per_segment = points_per_segment
|
|
63
|
+
self._path: np.ndarray
|
|
64
|
+
|
|
65
|
+
@abstractproperty
|
|
66
|
+
def betas(self) -> BlochVectors:
|
|
67
|
+
"""Return bloch wave vectors."""
|
|
68
|
+
|
|
69
|
+
@staticmethod
|
|
70
|
+
def _interpolate_beta(beta_cs, path, n_points, points_per_segment=None):
|
|
71
|
+
"""Interpolate a path globally or by segment."""
|
|
72
|
+
if points_per_segment is not None:
|
|
73
|
+
beta_values = []
|
|
74
|
+
beta_distances = []
|
|
75
|
+
for index in range(path.shape[1] - 1):
|
|
76
|
+
fractions = np.linspace(0.0, 1.0, points_per_segment + 1)
|
|
77
|
+
start = path[:, index, None]
|
|
78
|
+
delta = (path[:, index + 1] - path[:, index])[:, None]
|
|
79
|
+
segment_values = start + delta * fractions[None, :]
|
|
80
|
+
|
|
81
|
+
start_distance = beta_cs[index]
|
|
82
|
+
segment_length = beta_cs[index + 1] - start_distance
|
|
83
|
+
segment_distances = start_distance + segment_length * fractions
|
|
84
|
+
|
|
85
|
+
if index:
|
|
86
|
+
segment_values = segment_values[:, 1:]
|
|
87
|
+
segment_distances = segment_distances[1:]
|
|
88
|
+
beta_values.append(segment_values)
|
|
89
|
+
beta_distances.append(segment_distances)
|
|
90
|
+
|
|
91
|
+
return BlochVectors(
|
|
92
|
+
np.concatenate(beta_values, axis=1),
|
|
93
|
+
np.concatenate(beta_distances),
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
beta_csi = np.linspace(0, beta_cs[-1], n_points)
|
|
97
|
+
beta_vi = np.vstack(
|
|
98
|
+
[
|
|
99
|
+
np.interp(beta_csi, beta_cs, path[i, :]).flatten()
|
|
100
|
+
for i in range(path.shape[0])
|
|
101
|
+
]
|
|
102
|
+
)
|
|
103
|
+
return BlochVectors(beta_vi, beta_csi)
|
|
104
|
+
|
|
105
|
+
@property
|
|
106
|
+
def point_locations(self):
|
|
107
|
+
"""Return cumulative sum of vectors' length."""
|
|
108
|
+
return np.concatenate(
|
|
109
|
+
(np.zeros(1), np.cumsum(norm(np.diff(self._path, axis=1), axis=0)))
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
@property
|
|
113
|
+
def point_names(self):
|
|
114
|
+
"""Return list of symmetry point names."""
|
|
115
|
+
return [p.name for p in self.path_points]
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
class BrillouinZonePath1D(BrillouinZonePathBase):
|
|
119
|
+
"""BrillouinZonePath1D.
|
|
120
|
+
|
|
121
|
+
Parameters
|
|
122
|
+
----------
|
|
123
|
+
a1 : Tuple[float]
|
|
124
|
+
Direct lattice vector a1.
|
|
125
|
+
path : List[SymmetryPoint]
|
|
126
|
+
List of symmetry points.
|
|
127
|
+
n_points : int
|
|
128
|
+
Total number of vectors sampled along the full path. Used when
|
|
129
|
+
``points_per_segment`` is not set.
|
|
130
|
+
strategy : str = 'linear'
|
|
131
|
+
Strategy to interpolate.
|
|
132
|
+
points_per_segment : int, optional
|
|
133
|
+
Number of intervals on each segment. Every symmetry point is sampled;
|
|
134
|
+
the total is one plus this value times the number of segments. This
|
|
135
|
+
takes precedence over ``n_points``.
|
|
136
|
+
"""
|
|
137
|
+
|
|
138
|
+
def __init__(
|
|
139
|
+
self,
|
|
140
|
+
a1: Tuple[float],
|
|
141
|
+
path: List[SymmetryPoint],
|
|
142
|
+
n_points: int = 50,
|
|
143
|
+
strategy: str = "linear",
|
|
144
|
+
points_per_segment: Optional[int] = None,
|
|
145
|
+
):
|
|
146
|
+
"""Initialize a BrillouinZonePath."""
|
|
147
|
+
super().__init__(path, n_points, strategy, points_per_segment)
|
|
148
|
+
self.a1 = np.array(a1)
|
|
149
|
+
self.dim = 1
|
|
150
|
+
|
|
151
|
+
@property
|
|
152
|
+
def _path(self) -> np.ndarray:
|
|
153
|
+
"""Return the path matrix."""
|
|
154
|
+
path = np.stack([p.point for p in self.path_points], axis=1)
|
|
155
|
+
return path[0, :] * self.b1[:, None]
|
|
156
|
+
|
|
157
|
+
@property
|
|
158
|
+
def betas(self) -> BlochVectors:
|
|
159
|
+
"""Return beta vector values and cumsum."""
|
|
160
|
+
beta_cs = np.cumsum(norm(np.diff(self._path, axis=1), axis=0))
|
|
161
|
+
beta_cs = np.pad(beta_cs, (1, 0), "constant")
|
|
162
|
+
if self.strat == "linear":
|
|
163
|
+
return self._interpolate_beta(
|
|
164
|
+
beta_cs, self._path, self.n_points, self.points_per_segment
|
|
165
|
+
)
|
|
166
|
+
raise NotImplementedError
|
|
167
|
+
|
|
168
|
+
@property
|
|
169
|
+
def b1(self) -> np.ndarray:
|
|
170
|
+
"""Return reciprocal lattice vector b1."""
|
|
171
|
+
return 2 * np.pi / self.a1
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
class BrillouinZonePath2D(BrillouinZonePathBase):
|
|
175
|
+
"""BrillouinZonePath2D.
|
|
176
|
+
|
|
177
|
+
Parameters
|
|
178
|
+
----------
|
|
179
|
+
a1 : Tuple[float, float]
|
|
180
|
+
Direct lattice vector a1.
|
|
181
|
+
a2 : Tuple[float, float]
|
|
182
|
+
Direct lattice vector a2.
|
|
183
|
+
path : List[SymmetryPoint]
|
|
184
|
+
List of symmetry points.
|
|
185
|
+
n_points : int
|
|
186
|
+
Total number of vectors sampled along the full path. Used when
|
|
187
|
+
``points_per_segment`` is not set.
|
|
188
|
+
strategy : str = 'linear'
|
|
189
|
+
Strategy to interpolate.
|
|
190
|
+
points_per_segment : int, optional
|
|
191
|
+
Number of intervals on each segment. Every symmetry point is sampled;
|
|
192
|
+
the total is one plus this value times the number of segments. This
|
|
193
|
+
takes precedence over ``n_points``.
|
|
194
|
+
"""
|
|
195
|
+
|
|
196
|
+
def __init__(
|
|
197
|
+
self,
|
|
198
|
+
a1: Tuple[float, float],
|
|
199
|
+
a2: Tuple[float, float],
|
|
200
|
+
path: List[SymmetryPoint],
|
|
201
|
+
n_points: int = 50,
|
|
202
|
+
strategy: str = "linear",
|
|
203
|
+
points_per_segment: Optional[int] = None,
|
|
204
|
+
):
|
|
205
|
+
"""Initialize a BrillouinZonePath."""
|
|
206
|
+
super().__init__(path, n_points, strategy, points_per_segment)
|
|
207
|
+
self.a1 = np.array(a1)
|
|
208
|
+
self.a2 = np.array(a2)
|
|
209
|
+
self.dim = 2
|
|
210
|
+
|
|
211
|
+
@property
|
|
212
|
+
def _path(self) -> np.ndarray:
|
|
213
|
+
"""Return the path matrix."""
|
|
214
|
+
path = np.stack([p.point for p in self.path_points], axis=1)
|
|
215
|
+
return path[0, :] * self.b1[:, None] + path[1, :] * self.b2[:, None]
|
|
216
|
+
|
|
217
|
+
@property
|
|
218
|
+
def betas(self) -> BlochVectors:
|
|
219
|
+
"""Return beta vector values and cumsum."""
|
|
220
|
+
beta_cs = np.cumsum(norm(np.diff(self._path, axis=1), axis=0))
|
|
221
|
+
beta_cs = np.pad(beta_cs, (1, 0), "constant")
|
|
222
|
+
if self.strat == "linear":
|
|
223
|
+
return self._interpolate_beta(
|
|
224
|
+
beta_cs, self._path, self.n_points, self.points_per_segment
|
|
225
|
+
)
|
|
226
|
+
raise NotImplementedError
|
|
227
|
+
|
|
228
|
+
@property
|
|
229
|
+
def b1(self) -> np.ndarray:
|
|
230
|
+
"""Return reciprocal lattice vector b1."""
|
|
231
|
+
Q = np.array([[0, -1], [1, 0]])
|
|
232
|
+
return 2 * np.pi * Q @ self.a2 / np.dot(self.a1, Q @ self.a2)
|
|
233
|
+
|
|
234
|
+
@property
|
|
235
|
+
def b2(self) -> np.ndarray:
|
|
236
|
+
"""Return reciprocal lattice vector b2."""
|
|
237
|
+
Q = np.array([[0, -1], [1, 0]])
|
|
238
|
+
return 2 * np.pi * Q @ self.a1 / np.dot(self.a2, Q @ self.a1)
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
class BrillouinZonePath3D(BrillouinZonePathBase):
|
|
242
|
+
"""BrillouinZonePath3D.
|
|
243
|
+
|
|
244
|
+
Parameters
|
|
245
|
+
----------
|
|
246
|
+
a1 : Tuple[float, float, float]
|
|
247
|
+
Direct lattice vector a1.
|
|
248
|
+
a2 : Tuple[float, float, float]
|
|
249
|
+
Direct lattice vector a2.
|
|
250
|
+
a3 : Tuple[float, float, float]
|
|
251
|
+
Direct lattice vector a3.
|
|
252
|
+
path : List[SymmetryPoint]
|
|
253
|
+
List of symmetry points.
|
|
254
|
+
n_points : int
|
|
255
|
+
Total number of vectors sampled along the full path. Used when
|
|
256
|
+
``points_per_segment`` is not set.
|
|
257
|
+
strategy : str = 'linear'
|
|
258
|
+
Strategy to interpolate.
|
|
259
|
+
points_per_segment : int, optional
|
|
260
|
+
Number of intervals on each segment. Every symmetry point is sampled;
|
|
261
|
+
the total is one plus this value times the number of segments. This
|
|
262
|
+
takes precedence over ``n_points``.
|
|
263
|
+
"""
|
|
264
|
+
|
|
265
|
+
def __init__(
|
|
266
|
+
self,
|
|
267
|
+
a1: Tuple[float, float, float],
|
|
268
|
+
a2: Tuple[float, float, float],
|
|
269
|
+
a3: Tuple[float, float, float],
|
|
270
|
+
path: List[SymmetryPoint],
|
|
271
|
+
n_points: int = 50,
|
|
272
|
+
strategy: str = "interpolate",
|
|
273
|
+
points_per_segment: Optional[int] = None,
|
|
274
|
+
):
|
|
275
|
+
"""Initialize a BrillouinZonePath."""
|
|
276
|
+
super().__init__(path, n_points, strategy, points_per_segment)
|
|
277
|
+
self.a1 = np.array(a1)
|
|
278
|
+
self.a2 = np.array(a2)
|
|
279
|
+
self.a3 = np.array(a3)
|
|
280
|
+
self.dim = 3
|
|
281
|
+
|
|
282
|
+
@property
|
|
283
|
+
def _path(self) -> np.ndarray:
|
|
284
|
+
"""Return the path matrix."""
|
|
285
|
+
path = np.stack([p.point for p in self.path_points], axis=1)
|
|
286
|
+
return (
|
|
287
|
+
path[0, :] * self.b1[:, None]
|
|
288
|
+
+ path[1, :] * self.b2[:, None]
|
|
289
|
+
+ path[2, :] * self.b3[:, None]
|
|
290
|
+
)
|
|
291
|
+
|
|
292
|
+
@property
|
|
293
|
+
def betas(self) -> BlochVectors:
|
|
294
|
+
"""Return beta vector values and cumsum."""
|
|
295
|
+
beta_cs = np.cumsum(norm(np.diff(self._path, axis=1), axis=0))
|
|
296
|
+
beta_cs = np.pad(beta_cs, (1, 0), "constant")
|
|
297
|
+
if self.strat == "interpolate":
|
|
298
|
+
return self._interpolate_beta(
|
|
299
|
+
beta_cs, self._path, self.n_points, self.points_per_segment
|
|
300
|
+
)
|
|
301
|
+
raise NotImplementedError
|
|
302
|
+
|
|
303
|
+
@property
|
|
304
|
+
def b1(self) -> np.ndarray:
|
|
305
|
+
"""Return reciprocal lattice vector b1."""
|
|
306
|
+
return (
|
|
307
|
+
2
|
|
308
|
+
* np.pi
|
|
309
|
+
* np.cross(self.a2, self.a3)
|
|
310
|
+
/ np.dot(self.a1, np.cross(self.a2, self.a3))
|
|
311
|
+
)
|
|
312
|
+
|
|
313
|
+
@property
|
|
314
|
+
def b2(self) -> np.ndarray:
|
|
315
|
+
"""Return reciprocal lattice vector b2."""
|
|
316
|
+
return (
|
|
317
|
+
2
|
|
318
|
+
* np.pi
|
|
319
|
+
* np.cross(self.a3, self.a1)
|
|
320
|
+
/ np.dot(self.a1, np.cross(self.a2, self.a3))
|
|
321
|
+
)
|
|
322
|
+
|
|
323
|
+
@property
|
|
324
|
+
def b3(self) -> np.ndarray:
|
|
325
|
+
"""Return reciprocal lattice vector b3."""
|
|
326
|
+
return (
|
|
327
|
+
2
|
|
328
|
+
* np.pi
|
|
329
|
+
* np.cross(self.a1, self.a2)
|
|
330
|
+
/ np.dot(self.a1, np.cross(self.a2, self.a3))
|
|
331
|
+
)
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
def BrillouinZonePath(
|
|
335
|
+
a1, *args, **kwargs
|
|
336
|
+
) -> Union[BrillouinZonePath1D, BrillouinZonePath2D, BrillouinZonePath3D]:
|
|
337
|
+
"""BrillouinZonePath factory."""
|
|
338
|
+
dim_obj = {1: BrillouinZonePath1D, 2: BrillouinZonePath2D, 3: BrillouinZonePath3D}
|
|
339
|
+
return dim_obj[len(a1)](a1, *args, **kwargs)
|
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
"""Implement photonic crystal geometries."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from abc import ABC
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
from typing import Callable, Optional, Tuple, Union
|
|
8
|
+
|
|
9
|
+
import numpy as np
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class GeometryBase(ABC):
|
|
13
|
+
"""Base Geometry class."""
|
|
14
|
+
|
|
15
|
+
def __init__(self, eps_rf: Optional[Callable], mu_rf: Optional[Callable]):
|
|
16
|
+
self.eps_rf: Optional[Callable] = eps_rf or None
|
|
17
|
+
self.mu_rf: Optional[Callable] = mu_rf or None
|
|
18
|
+
self._eps_r: np.ndarray
|
|
19
|
+
self._mu_r: np.ndarray
|
|
20
|
+
|
|
21
|
+
def overwrite(self, func: Callable):
|
|
22
|
+
"""Overwrite decorator.
|
|
23
|
+
|
|
24
|
+
Parameters
|
|
25
|
+
----------
|
|
26
|
+
func : Callable
|
|
27
|
+
func
|
|
28
|
+
"""
|
|
29
|
+
if func.__name__ in ["eps_rf", "mu_rf"]:
|
|
30
|
+
setattr(self, func.__name__, func)
|
|
31
|
+
else:
|
|
32
|
+
raise Exception("Can only overwrite eps_rf or mu_rf.")
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class Geometry1D(GeometryBase):
|
|
36
|
+
"""Geometry class.
|
|
37
|
+
|
|
38
|
+
Parameters
|
|
39
|
+
----------
|
|
40
|
+
a1 : Tuple[float, ...]
|
|
41
|
+
Direct a1 vector.
|
|
42
|
+
n1 : int
|
|
43
|
+
Number of divisions in the a1 vector direction.
|
|
44
|
+
eps_rf : Optional[Callable]
|
|
45
|
+
Permittivity matrix eps_r if directly supplied.
|
|
46
|
+
mu_rf : Optional[Callable]
|
|
47
|
+
Permeabillity matrix mu_r if directly supplied.
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
def __init__(
|
|
51
|
+
self,
|
|
52
|
+
a1: Tuple[float],
|
|
53
|
+
n1: int = 64,
|
|
54
|
+
eps_rf: Optional[Callable] = None,
|
|
55
|
+
mu_rf: Optional[Callable] = None,
|
|
56
|
+
):
|
|
57
|
+
super().__init__(eps_rf, mu_rf)
|
|
58
|
+
self.a1: np.ndarray = np.array(a1)
|
|
59
|
+
self.n1: int = n1
|
|
60
|
+
self._eps_r: np.ndarray = np.ones((n1,), dtype=complex)
|
|
61
|
+
self._mu_r: np.ndarray = np.ones((n1,), dtype=complex)
|
|
62
|
+
|
|
63
|
+
@property
|
|
64
|
+
def X(self) -> CartesianVectors1D:
|
|
65
|
+
"""Return cartesian positions."""
|
|
66
|
+
P0 = np.arange(self.n1) / self.n1 - 0.5
|
|
67
|
+
x = P0 * self.a1[0]
|
|
68
|
+
return CartesianVectors1D(x)
|
|
69
|
+
|
|
70
|
+
@property
|
|
71
|
+
def eps_r(self) -> np.ndarray:
|
|
72
|
+
"""Return the relative permittivity profile."""
|
|
73
|
+
if self.eps_rf:
|
|
74
|
+
self.eps_rf(eps_r=self._eps_r, x=self.X.x)
|
|
75
|
+
return self._eps_r
|
|
76
|
+
|
|
77
|
+
@property
|
|
78
|
+
def mu_r(self) -> np.ndarray:
|
|
79
|
+
"""Return the relative permeability profile."""
|
|
80
|
+
if self.mu_rf:
|
|
81
|
+
self.mu_rf(self._mu_r, x=self.X.x)
|
|
82
|
+
return self._mu_r
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
class Geometry2D(GeometryBase):
|
|
86
|
+
"""Geometry class.
|
|
87
|
+
|
|
88
|
+
Parameters
|
|
89
|
+
----------
|
|
90
|
+
a1 : Tuple[float, ...]
|
|
91
|
+
Direct a1 vector.
|
|
92
|
+
a2 : Optional[Tuple[float, ...]]
|
|
93
|
+
Direct a2 vector.
|
|
94
|
+
n1 : int
|
|
95
|
+
Number of divisions in the a1 vector direction.
|
|
96
|
+
n2 : int
|
|
97
|
+
Number of divisions in the a2 vector direction.
|
|
98
|
+
eps_rf : Optional[Callable]
|
|
99
|
+
Permittivity matrix eps_r if directly supplied.
|
|
100
|
+
mu_rf : Optional[Callable]
|
|
101
|
+
Permeabillity matrix mu_r if directly supplied.
|
|
102
|
+
"""
|
|
103
|
+
|
|
104
|
+
def __init__(
|
|
105
|
+
self,
|
|
106
|
+
a1: Tuple[float, float],
|
|
107
|
+
a2: Tuple[float, float],
|
|
108
|
+
n1: int = 64,
|
|
109
|
+
n2: int = 64,
|
|
110
|
+
eps_rf: Optional[Callable] = None,
|
|
111
|
+
mu_rf: Optional[Callable] = None,
|
|
112
|
+
):
|
|
113
|
+
super().__init__(eps_rf, mu_rf)
|
|
114
|
+
self.a1: np.ndarray = np.array(a1)
|
|
115
|
+
self.a2: np.ndarray = np.array(a2)
|
|
116
|
+
self.n1: int = n1
|
|
117
|
+
self.n2: int = n2
|
|
118
|
+
self._eps_r: np.ndarray = np.ones((n1, n2), dtype=complex)
|
|
119
|
+
self._mu_r: np.ndarray = np.ones((n1, n2), dtype=complex)
|
|
120
|
+
|
|
121
|
+
@property
|
|
122
|
+
def X(self) -> CartesianVectors2D:
|
|
123
|
+
"""Return cartesian positions."""
|
|
124
|
+
P0, Q0 = np.meshgrid(
|
|
125
|
+
np.arange(self.n1) / self.n1 - 0.5,
|
|
126
|
+
np.arange(self.n2) / self.n2 - 0.5,
|
|
127
|
+
indexing="ij",
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
x = P0 * self.a1[0] + Q0 * self.a2[0]
|
|
131
|
+
y = P0 * self.a1[1] + Q0 * self.a2[1]
|
|
132
|
+
|
|
133
|
+
return CartesianVectors2D(x, y)
|
|
134
|
+
|
|
135
|
+
@property
|
|
136
|
+
def eps_r(self) -> np.ndarray:
|
|
137
|
+
"""Return the relative permittivity matrice."""
|
|
138
|
+
if self.eps_rf:
|
|
139
|
+
self.eps_rf(eps_r=self._eps_r, x=self.X.x, y=self.X.y)
|
|
140
|
+
return self._eps_r
|
|
141
|
+
|
|
142
|
+
@property
|
|
143
|
+
def mu_r(self) -> np.ndarray:
|
|
144
|
+
"""Return the relative permeabillity matrice."""
|
|
145
|
+
if self.mu_rf:
|
|
146
|
+
self.mu_rf(self._mu_r, x=self.X.x, y=self.X.y)
|
|
147
|
+
return self._mu_r
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
class Geometry3D(GeometryBase):
|
|
151
|
+
"""Geometry class.
|
|
152
|
+
|
|
153
|
+
Parameters
|
|
154
|
+
----------
|
|
155
|
+
a1 : Tuple[float, float, float]
|
|
156
|
+
Direct a1 vector.
|
|
157
|
+
a2 : Optional[Tuple[float, float, float]]
|
|
158
|
+
Direct a2 vector.
|
|
159
|
+
a3 : Optional[Tuple[float, float, float]]
|
|
160
|
+
Direct a3 vector.
|
|
161
|
+
n1 : int
|
|
162
|
+
Number of divisions in the a1 vector direction.
|
|
163
|
+
n2 : int
|
|
164
|
+
Number of divisions in the a2 vector direction.
|
|
165
|
+
n3 : int
|
|
166
|
+
Number of divisions in the a3 vector direction.
|
|
167
|
+
eps_rf : Optional[Callable]
|
|
168
|
+
Permittivity matrix function eps_rf if directly supplied.
|
|
169
|
+
mu_rf : Optional[Callable]
|
|
170
|
+
Permeabillity matrix function mu_rf if directly supplied.
|
|
171
|
+
"""
|
|
172
|
+
|
|
173
|
+
def __init__(
|
|
174
|
+
self,
|
|
175
|
+
a1: Tuple[float, float, float],
|
|
176
|
+
a2: Tuple[float, float, float],
|
|
177
|
+
a3: Tuple[float, float, float],
|
|
178
|
+
n1: int = 64,
|
|
179
|
+
n2: int = 64,
|
|
180
|
+
n3: int = 64,
|
|
181
|
+
eps_rf: Optional[Callable] = None,
|
|
182
|
+
mu_rf: Optional[Callable] = None,
|
|
183
|
+
):
|
|
184
|
+
super().__init__(eps_rf, mu_rf)
|
|
185
|
+
self.a1: np.ndarray = np.array(a1)
|
|
186
|
+
self.a2: np.ndarray = np.array(a2)
|
|
187
|
+
self.a3: np.ndarray = np.array(a3)
|
|
188
|
+
self.n1: int = n1
|
|
189
|
+
self.n2: int = n2
|
|
190
|
+
self.n3: int = n3
|
|
191
|
+
self._eps_r: np.ndarray = np.ones((n1, n2, n3), dtype=complex)
|
|
192
|
+
self._mu_r: np.ndarray = np.ones((n1, n2, n3), dtype=complex)
|
|
193
|
+
|
|
194
|
+
@property
|
|
195
|
+
def X(self) -> CartesianVectors3D:
|
|
196
|
+
"""Return cartesian positions."""
|
|
197
|
+
P0, Q0, R0 = np.meshgrid(
|
|
198
|
+
np.arange(self.n1) / self.n1 - 0.5,
|
|
199
|
+
np.arange(self.n2) / self.n2 - 0.5,
|
|
200
|
+
np.arange(self.n3) / self.n3 - 0.5,
|
|
201
|
+
indexing="ij",
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
x = P0 * self.a1[0] + Q0 * self.a2[0] + R0 * self.a3[0]
|
|
205
|
+
y = P0 * self.a1[1] + Q0 * self.a2[1] + R0 * self.a3[1]
|
|
206
|
+
z = P0 * self.a1[2] + Q0 * self.a2[2] + R0 * self.a3[2]
|
|
207
|
+
|
|
208
|
+
return CartesianVectors3D(x, y, z)
|
|
209
|
+
|
|
210
|
+
@property
|
|
211
|
+
def eps_r(self) -> np.ndarray:
|
|
212
|
+
"""Return the relative permittivity matrice."""
|
|
213
|
+
if self.eps_rf:
|
|
214
|
+
self.eps_rf(eps_r=self._eps_r, x=self.X.x, y=self.X.y, z=self.X.z)
|
|
215
|
+
return self._eps_r
|
|
216
|
+
|
|
217
|
+
@property
|
|
218
|
+
def mu_r(self) -> np.ndarray:
|
|
219
|
+
"""Return the relative permeabillity matrice."""
|
|
220
|
+
if self.mu_rf:
|
|
221
|
+
self.mu_rf(self._mu_r, x=self.X.x, y=self.X.y, z=self.X.z)
|
|
222
|
+
return self._mu_r
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
def Geometry(*args, **kwargs) -> Union[Geometry1D, Geometry2D, Geometry3D]:
|
|
226
|
+
"""Geometry factory.
|
|
227
|
+
|
|
228
|
+
Parameters
|
|
229
|
+
----------
|
|
230
|
+
args :
|
|
231
|
+
args
|
|
232
|
+
kwargs :
|
|
233
|
+
kwargs
|
|
234
|
+
|
|
235
|
+
Returns
|
|
236
|
+
-------
|
|
237
|
+
Union[Geometry1D, Geometry2D, Geometry3D]
|
|
238
|
+
|
|
239
|
+
"""
|
|
240
|
+
dim_obj = {1: Geometry1D, 2: Geometry2D, 3: Geometry3D}
|
|
241
|
+
return dim_obj[len(args[0])](*args, **kwargs)
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
@dataclass
|
|
245
|
+
class CartesianVectors1D:
|
|
246
|
+
"""Represent a data structure of cartesian coordinates."""
|
|
247
|
+
|
|
248
|
+
x: np.ndarray
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
@dataclass
|
|
252
|
+
class CartesianVectors2D:
|
|
253
|
+
"""Represent a data structure of cartesian coordinates."""
|
|
254
|
+
|
|
255
|
+
x: np.ndarray
|
|
256
|
+
y: np.ndarray
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
@dataclass
|
|
260
|
+
class CartesianVectors3D:
|
|
261
|
+
"""Represent a data structure of cartesian coordinates."""
|
|
262
|
+
|
|
263
|
+
x: np.ndarray
|
|
264
|
+
y: np.ndarray
|
|
265
|
+
z: np.ndarray
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
"""Implement 3D/2D solvers."""
|
|
2
|
+
|
|
3
|
+
from typing import List, Union
|
|
4
|
+
|
|
5
|
+
import numpy as np
|
|
6
|
+
from scipy.linalg import eigh
|
|
7
|
+
|
|
8
|
+
from .brillouinzone import BrillouinZonePath1D as BZPath1D
|
|
9
|
+
from .brillouinzone import BrillouinZonePath2D as BZPath2D
|
|
10
|
+
from .brillouinzone import BrillouinZonePath3D as BZPath3D
|
|
11
|
+
from .geometry import Geometry1D, Geometry2D, Geometry3D
|
|
12
|
+
from .utils import convmat, harmonic_indices
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class Solver1D:
|
|
16
|
+
"""Implement a 1D PWE Solver.
|
|
17
|
+
|
|
18
|
+
Parameters
|
|
19
|
+
----------
|
|
20
|
+
geometry : Geometry
|
|
21
|
+
geometry
|
|
22
|
+
path : BrillouinZonePath
|
|
23
|
+
A BrillouinZonePath object.
|
|
24
|
+
P : int
|
|
25
|
+
Number of terms in the direction of the reciprocal vector b1.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
def __init__(
|
|
29
|
+
self, geometry: Geometry1D, path: BZPath1D, P: int = 1, pol: str = "TM"
|
|
30
|
+
):
|
|
31
|
+
"""Initialize the PWE Solver."""
|
|
32
|
+
self.geo = geometry
|
|
33
|
+
self.path = path
|
|
34
|
+
self.P = P
|
|
35
|
+
self.pol = pol
|
|
36
|
+
|
|
37
|
+
self.eps_rc: np.ndarray
|
|
38
|
+
self.mu_rc: np.ndarray
|
|
39
|
+
self.wn: List[np.ndarray] = []
|
|
40
|
+
self.modes: List[np.ndarray] = []
|
|
41
|
+
|
|
42
|
+
def run(self):
|
|
43
|
+
"""Calculate the eigen-wavenumbers and modes."""
|
|
44
|
+
self.eps_rc = convmat(self.geo.eps_r, self.P)
|
|
45
|
+
self.mu_rc = convmat(self.geo.mu_r, self.P)
|
|
46
|
+
mu_rci = convmat(1 / self.geo.mu_r, self.P)
|
|
47
|
+
eps_rci = convmat(1 / self.geo.eps_r, self.P)
|
|
48
|
+
|
|
49
|
+
p = harmonic_indices(self.P)
|
|
50
|
+
G_k = p * self.path.b1[0]
|
|
51
|
+
beta = self.path.betas.values
|
|
52
|
+
|
|
53
|
+
for col in range(beta.shape[1]):
|
|
54
|
+
k = beta[0, col] - G_k
|
|
55
|
+
KX = np.diag(k)
|
|
56
|
+
|
|
57
|
+
if self.pol == "TM":
|
|
58
|
+
A = KX @ mu_rci @ KX
|
|
59
|
+
B = self.eps_rc
|
|
60
|
+
else:
|
|
61
|
+
A = KX @ eps_rci @ KX
|
|
62
|
+
B = self.mu_rc
|
|
63
|
+
|
|
64
|
+
D, V = eigh(A, B)
|
|
65
|
+
self.wn.append(np.sqrt(np.maximum(D, 0)))
|
|
66
|
+
self.modes.append(V)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class Solver2D:
|
|
70
|
+
"""Implement a 2D PWE Solver.
|
|
71
|
+
|
|
72
|
+
Parameters
|
|
73
|
+
----------
|
|
74
|
+
geometry : Geometry
|
|
75
|
+
geometry
|
|
76
|
+
path : BrillouinZonePath
|
|
77
|
+
A BrillouinZonePath object.
|
|
78
|
+
P : int
|
|
79
|
+
Number of terms in the direction of the reciprocal vector b1.
|
|
80
|
+
Q : int
|
|
81
|
+
Number of terms in the direction of the reciprocal vector b2.
|
|
82
|
+
"""
|
|
83
|
+
|
|
84
|
+
def __init__(
|
|
85
|
+
self,
|
|
86
|
+
geometry: Geometry2D,
|
|
87
|
+
path: BZPath2D,
|
|
88
|
+
P: int = 1,
|
|
89
|
+
Q: int = 1,
|
|
90
|
+
pol: str = "TM",
|
|
91
|
+
):
|
|
92
|
+
"""Initialize the PWE Solver."""
|
|
93
|
+
self.geo = geometry
|
|
94
|
+
self.path = path
|
|
95
|
+
self.P, self.Q = P, Q
|
|
96
|
+
self.pol = pol
|
|
97
|
+
|
|
98
|
+
self.eps_rc: np.ndarray
|
|
99
|
+
self.mu_rc: np.ndarray
|
|
100
|
+
self.wn: List[np.ndarray] = []
|
|
101
|
+
self.modes: List[np.ndarray] = []
|
|
102
|
+
|
|
103
|
+
def run(self):
|
|
104
|
+
"""Calculate the eigen-wavenumbers and modes."""
|
|
105
|
+
self.eps_rc = convmat(self.geo.eps_r, self.P, self.Q)
|
|
106
|
+
self.mu_rc = convmat(self.geo.mu_r, self.P, self.Q)
|
|
107
|
+
|
|
108
|
+
mu_rci = convmat(1 / self.geo.mu_r, self.P, self.Q)
|
|
109
|
+
if self.pol != "TM":
|
|
110
|
+
# Factorize epsilon before truncating its reciprocal. This
|
|
111
|
+
# inverse rule converges much faster for TE modes at sharp
|
|
112
|
+
# dielectric interfaces than Fourier coefficients of 1/eps.
|
|
113
|
+
eps_rci = np.linalg.inv(self.eps_rc)
|
|
114
|
+
|
|
115
|
+
p = harmonic_indices(self.P)
|
|
116
|
+
q = harmonic_indices(self.Q)
|
|
117
|
+
|
|
118
|
+
P0, Q0 = np.meshgrid(p, q, indexing="ij")
|
|
119
|
+
b1, b2 = self.path.b1, self.path.b2
|
|
120
|
+
|
|
121
|
+
G_k = P0.ravel(order="F") * b1[:, None] + Q0.ravel(order="F") * b2[:, None]
|
|
122
|
+
beta = self.path.betas.values
|
|
123
|
+
|
|
124
|
+
for col in range(beta.shape[1]):
|
|
125
|
+
k = beta[:, col, None] - G_k
|
|
126
|
+
|
|
127
|
+
KX = np.diag(k[0, :])
|
|
128
|
+
KY = np.diag(k[1, :])
|
|
129
|
+
|
|
130
|
+
if self.pol == "TM":
|
|
131
|
+
A = KX @ mu_rci @ KX + KY @ mu_rci @ KY
|
|
132
|
+
B = self.eps_rc
|
|
133
|
+
else:
|
|
134
|
+
A = KX @ eps_rci @ KX + KY @ eps_rci @ KY
|
|
135
|
+
B = self.mu_rc
|
|
136
|
+
|
|
137
|
+
D, V = eigh(A, B)
|
|
138
|
+
|
|
139
|
+
self.wn.append(np.sqrt(np.maximum(D, 0)))
|
|
140
|
+
self.modes.append(V)
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
class Solver3D:
|
|
144
|
+
"""Implement a 3D PWE Solver.
|
|
145
|
+
|
|
146
|
+
Parameters
|
|
147
|
+
----------
|
|
148
|
+
geometry : Geometry
|
|
149
|
+
geometry
|
|
150
|
+
path : BrillouinZonePath
|
|
151
|
+
A BrillouinZonePath object.
|
|
152
|
+
P : int
|
|
153
|
+
Number of terms in the direction of the reciprocal vector b1.
|
|
154
|
+
Q : int
|
|
155
|
+
Number of terms in the direction of the reciprocal vector b2.
|
|
156
|
+
R : int
|
|
157
|
+
Number of terms in the direction of the reciprocal vector b3.
|
|
158
|
+
"""
|
|
159
|
+
|
|
160
|
+
def __init__(
|
|
161
|
+
self, geometry: Geometry3D, path: BZPath3D, P: int = 1, Q: int = 1, R: int = 1
|
|
162
|
+
):
|
|
163
|
+
"""Initialize the PWE Solver."""
|
|
164
|
+
self.geo = geometry
|
|
165
|
+
self.path = path
|
|
166
|
+
self.P, self.Q, self.R = P, Q, R
|
|
167
|
+
|
|
168
|
+
self.eps_rc: np.ndarray
|
|
169
|
+
self.mu_rc: np.ndarray
|
|
170
|
+
self.wn: List[np.ndarray] = []
|
|
171
|
+
self.modes: List[np.ndarray] = []
|
|
172
|
+
|
|
173
|
+
def run(self):
|
|
174
|
+
"""Calculate the eigen-wavenumbers and modes."""
|
|
175
|
+
self.eps_rc = convmat(self.geo.eps_r, self.P, self.Q, self.R)
|
|
176
|
+
self.mu_rc = convmat(self.geo.mu_r, self.P, self.Q, self.R)
|
|
177
|
+
|
|
178
|
+
eps_rk = np.kron(np.eye(3), self.eps_rc)
|
|
179
|
+
mu_ri = convmat(1 / self.geo.mu_r, self.P, self.Q, self.R)
|
|
180
|
+
mu_rki = np.kron(np.eye(3), mu_ri)
|
|
181
|
+
|
|
182
|
+
p = harmonic_indices(self.P)
|
|
183
|
+
q = harmonic_indices(self.Q)
|
|
184
|
+
r = harmonic_indices(self.R)
|
|
185
|
+
|
|
186
|
+
P0, Q0, R0 = np.meshgrid(p, q, r, indexing="ij")
|
|
187
|
+
b1, b2, b3 = self.path.b1, self.path.b2, self.path.b3
|
|
188
|
+
|
|
189
|
+
G_k = (
|
|
190
|
+
b1[:, None] * P0.ravel(order="F")
|
|
191
|
+
+ b2[:, None] * Q0.ravel(order="F")
|
|
192
|
+
+ b3[:, None] * R0.ravel(order="F")
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
beta = self.path.betas.values
|
|
196
|
+
for col in range(beta.shape[1]):
|
|
197
|
+
k = beta[:, col, None] - G_k
|
|
198
|
+
|
|
199
|
+
KX = np.diag(k[0, :])
|
|
200
|
+
KY = np.diag(k[1, :])
|
|
201
|
+
KZ = np.diag(k[2, :])
|
|
202
|
+
K_V = np.vstack(
|
|
203
|
+
(
|
|
204
|
+
np.hstack((0 * KX, -KZ, KY)),
|
|
205
|
+
np.hstack((KZ, 0 * KY, -KX)),
|
|
206
|
+
np.hstack((-KY, KX, 0 * KZ)),
|
|
207
|
+
)
|
|
208
|
+
)
|
|
209
|
+
A = K_V @ mu_rki @ K_V
|
|
210
|
+
B = eps_rk
|
|
211
|
+
|
|
212
|
+
D, V = eigh(A, B)
|
|
213
|
+
|
|
214
|
+
# The curl-curl operator has one longitudinal null mode per
|
|
215
|
+
# retained reciprocal vector. Drop those N zero eigenvalues;
|
|
216
|
+
# at Gamma the two physical zero-frequency modes remain.
|
|
217
|
+
n_harmonics = KX.shape[0]
|
|
218
|
+
D = D[:-n_harmonics]
|
|
219
|
+
V = V[:, :-n_harmonics]
|
|
220
|
+
|
|
221
|
+
# eigh returns the negative eigenvalues from most negative to
|
|
222
|
+
# least negative, so reverse to report frequencies low to high.
|
|
223
|
+
self.wn.append(np.sqrt(np.maximum(-D, 0))[::-1])
|
|
224
|
+
self.modes.append(V[:, ::-1])
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
def Solver(geometry, path, *args, **kwargs) -> Union[Solver1D, Solver2D, Solver3D]:
|
|
228
|
+
"""Solver factory."""
|
|
229
|
+
dim_obj = {1: Solver1D, 2: Solver2D, 3: Solver3D}
|
|
230
|
+
return dim_obj[path.dim](geometry, path, *args, **kwargs)
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"""This module implements utilities."""
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def harmonic_indices(count: int) -> np.ndarray:
|
|
7
|
+
"""Return ``count`` integer harmonics centered as closely as possible at 0.
|
|
8
|
+
|
|
9
|
+
For an even count, the integer range is necessarily asymmetric; this
|
|
10
|
+
convention keeps zero and the negative Nyquist-side harmonic.
|
|
11
|
+
"""
|
|
12
|
+
if count < 1:
|
|
13
|
+
raise ValueError("The number of harmonics must be positive.")
|
|
14
|
+
return np.arange(-(count // 2), (count + 1) // 2)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def convmat(A: np.ndarray, P: int = 1, Q: int = 1, R: int = 1):
|
|
18
|
+
"""Return a permittivity/permeability convolution matrix.
|
|
19
|
+
|
|
20
|
+
Parameters
|
|
21
|
+
----------
|
|
22
|
+
A : np.ndarray
|
|
23
|
+
A 3D/2D/1D permittivity or permeability matrix.
|
|
24
|
+
P : int
|
|
25
|
+
Number of terms in the direction of the reciprocal vector T1.
|
|
26
|
+
Q : int
|
|
27
|
+
Number of terms in the direction of the reciprocal vector T2.
|
|
28
|
+
R : int
|
|
29
|
+
Number of terms in the direction of the reciprocal vector T3.
|
|
30
|
+
"""
|
|
31
|
+
NH = P * Q * R
|
|
32
|
+
A = np.asarray(A)
|
|
33
|
+
if A.ndim == 1:
|
|
34
|
+
A = A[:, None, None]
|
|
35
|
+
elif A.ndim == 2:
|
|
36
|
+
A = A[:, :, None]
|
|
37
|
+
elif A.ndim != 3:
|
|
38
|
+
raise ValueError("A must have one, two, or three dimensions.")
|
|
39
|
+
N1, N2, N3 = A.shape
|
|
40
|
+
|
|
41
|
+
p = harmonic_indices(P)
|
|
42
|
+
q = harmonic_indices(Q)
|
|
43
|
+
r = harmonic_indices(R)
|
|
44
|
+
|
|
45
|
+
A = np.fft.fftshift(np.fft.fftn(A) / A.size)
|
|
46
|
+
|
|
47
|
+
p_0, q_0, r_0 = N1 // 2, N2 // 2, N3 // 2
|
|
48
|
+
|
|
49
|
+
p_, q_, r_ = np.meshgrid(p, q, r, indexing="ij")
|
|
50
|
+
rr_, cc_ = np.mgrid[0:NH, 0:NH]
|
|
51
|
+
rr_i = np.unravel_index(rr_, p_.shape, order="F")
|
|
52
|
+
cc_i = np.unravel_index(cc_, p_.shape, order="F")
|
|
53
|
+
|
|
54
|
+
p_fft_ = p_[rr_i] - p_[cc_i] + p_0
|
|
55
|
+
q_fft_ = q_[rr_i] - q_[cc_i] + q_0
|
|
56
|
+
r_fft_ = r_[rr_i] - r_[cc_i] + r_0
|
|
57
|
+
|
|
58
|
+
return A[p_fft_, q_fft_, r_fft_]
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "morpho.py"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "PWE framework for photonic crystal analysis"
|
|
5
|
+
license = "MIT"
|
|
6
|
+
authors = [{name = "Tiago Vilela", email = "tiagovla@gmail.com"}]
|
|
7
|
+
requires-python = ">=3.9,<3.16"
|
|
8
|
+
classifiers = [
|
|
9
|
+
"Programming Language :: Python :: 3",
|
|
10
|
+
"Programming Language :: Python :: 3.9",
|
|
11
|
+
"Programming Language :: Python :: 3.10",
|
|
12
|
+
"Programming Language :: Python :: 3.11",
|
|
13
|
+
"Programming Language :: Python :: 3.12",
|
|
14
|
+
"Programming Language :: Python :: 3.13",
|
|
15
|
+
"Programming Language :: Python :: 3.14",
|
|
16
|
+
"Programming Language :: Python :: 3.15",
|
|
17
|
+
]
|
|
18
|
+
dependencies = [
|
|
19
|
+
"numpy>=1.20.1,<3.0.0",
|
|
20
|
+
"scipy>=1.6.2,<2.0.0",
|
|
21
|
+
"matplotlib>=3.4.3,<4.0.0",
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
[project.optional-dependencies]
|
|
25
|
+
docs = [
|
|
26
|
+
"mkdocs>=1.6.1,<2",
|
|
27
|
+
"mkdocs-material>=9.7.7",
|
|
28
|
+
"mkdocstrings[python]>=0.24.0",
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
[tool.poetry]
|
|
32
|
+
packages = [
|
|
33
|
+
{include = "morpho", from = "."}
|
|
34
|
+
]
|
|
35
|
+
|
|
36
|
+
[dependency-groups]
|
|
37
|
+
dev = [
|
|
38
|
+
"black==24.8.0",
|
|
39
|
+
"pre-commit>=3.8.0,<4.0.0",
|
|
40
|
+
"pytest>=5.2",
|
|
41
|
+
"ruff==0.16.10",
|
|
42
|
+
"mkdocs>=1.6.1,<2",
|
|
43
|
+
"mkdocs-material>=9.7.7",
|
|
44
|
+
"mkdocstrings[python]>=0.24.0",
|
|
45
|
+
]
|
|
46
|
+
|
|
47
|
+
[tool.uv]
|
|
48
|
+
package = true
|
|
49
|
+
|
|
50
|
+
[build-system]
|
|
51
|
+
requires = ["poetry-core>=2.0.0"]
|
|
52
|
+
build-backend = "poetry.core.masonry.api"
|
|
53
|
+
|
|
54
|
+
[tool.black]
|
|
55
|
+
line-length = 88
|
|
56
|
+
target-version = ["py39"]
|
|
57
|
+
|
|
58
|
+
[tool.ruff]
|
|
59
|
+
line-length = 88
|
|
60
|
+
target-version = "py39"
|
|
61
|
+
|
|
62
|
+
[tool.ruff.lint]
|
|
63
|
+
select = ["E4", "E7", "E9", "F"]
|
|
64
|
+
|
morpho.py-0.0.3/PKG-INFO
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.1
|
|
2
|
-
Name: morpho.py
|
|
3
|
-
Version: 0.0.3
|
|
4
|
-
Summary:
|
|
5
|
-
Author: Tiago Vilela
|
|
6
|
-
Author-email: tiagovla@gmail.com
|
|
7
|
-
Requires-Python: >=3.8,<4.0
|
|
8
|
-
Classifier: Programming Language :: Python :: 3
|
|
9
|
-
Classifier: Programming Language :: Python :: 3.8
|
|
10
|
-
Classifier: Programming Language :: Python :: 3.9
|
|
11
|
-
Requires-Dist: matplotlib (>=3.3.4,<4.0.0)
|
|
12
|
-
Requires-Dist: numpy (>=1.20.1,<2.0.0)
|
|
13
|
-
Requires-Dist: scipy (>=1.6.1,<2.0.0)
|
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
"""This module implements classes related to the brillouinzone."""
|
|
2
|
-
|
|
3
|
-
from typing import List, Optional, Tuple
|
|
4
|
-
|
|
5
|
-
import numpy as np
|
|
6
|
-
from numpy.linalg import norm
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
class SymmetryPoint:
|
|
10
|
-
"""Model of a symmetry point at the brillouinzone."""
|
|
11
|
-
|
|
12
|
-
def __init__(self, point: Tuple[float, ...], name: str):
|
|
13
|
-
"""Initialize a SymmetryPoint."""
|
|
14
|
-
self.point = np.array(point)
|
|
15
|
-
self.name = name
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
class BrillouinZonePath:
|
|
19
|
-
"""Model of a path at the brillouinzone."""
|
|
20
|
-
|
|
21
|
-
def __init__(
|
|
22
|
-
self,
|
|
23
|
-
path: List[SymmetryPoint],
|
|
24
|
-
t1: Tuple[float, ...],
|
|
25
|
-
t2: Optional[Tuple[float, ...]] = None,
|
|
26
|
-
t3: Optional[Tuple[float, ...]] = None,
|
|
27
|
-
n_points: int = 5,
|
|
28
|
-
):
|
|
29
|
-
"""Initialize a BrillouinZonePath."""
|
|
30
|
-
self.path_points = path
|
|
31
|
-
self.n_points = n_points
|
|
32
|
-
|
|
33
|
-
self.dim = len(t1)
|
|
34
|
-
|
|
35
|
-
self._t1 = np.pad(np.array(t1), (0, 3 - len(t1)), "constant")
|
|
36
|
-
self._t2 = (np.pad(np.array(t2), (0, 3 - len(t2)), "constant")
|
|
37
|
-
if t2 else np.array([0, 1, 0]))
|
|
38
|
-
self._t3 = (np.pad(np.array(t3), (0, 3 - len(t1)), "constant")
|
|
39
|
-
if t3 else np.array([0, 0, 1]))
|
|
40
|
-
|
|
41
|
-
self._T1 = (2 * np.pi * np.cross(self._t2, self._t3) /
|
|
42
|
-
np.dot(self._t1, np.cross(self._t2, self._t3)))
|
|
43
|
-
self._T2 = (2 * np.pi * np.cross(self._t3, self._t1) /
|
|
44
|
-
np.dot(self._t1, np.cross(self._t2, self._t3)))
|
|
45
|
-
self._T3 = (2 * np.pi * np.cross(self._t1, self._t2) /
|
|
46
|
-
np.dot(self._t1, np.cross(self._t2, self._t3)))
|
|
47
|
-
|
|
48
|
-
self.beta_vec = None
|
|
49
|
-
self.beta_vec_len = None
|
|
50
|
-
self.symmetry_names = None
|
|
51
|
-
self.symmetry_locations = None
|
|
52
|
-
self._calculate_beta()
|
|
53
|
-
|
|
54
|
-
def _calculate_beta(self):
|
|
55
|
-
beta_path = np.stack(
|
|
56
|
-
[
|
|
57
|
-
np.pad(p.point, (0, 3 - len(p.point)), "constant")
|
|
58
|
-
for p in self.path_points
|
|
59
|
-
],
|
|
60
|
-
axis=1,
|
|
61
|
-
)
|
|
62
|
-
beta_path = (beta_path[0, :] * self._T1[:, None] +
|
|
63
|
-
beta_path[1, :] * self._T2[:, None] +
|
|
64
|
-
beta_path[2, :] * self._T3[:, None])
|
|
65
|
-
|
|
66
|
-
beta_len = np.cumsum(
|
|
67
|
-
norm(np.diff(beta_path, axis=1)[:self.dim], axis=0))
|
|
68
|
-
beta_len = np.pad(beta_len, (1, 0), "constant")
|
|
69
|
-
beta_len_interp = np.linspace(0, beta_len[-1], self.n_points)
|
|
70
|
-
beta_ix = np.interp(beta_len_interp, beta_len,
|
|
71
|
-
beta_path[0, :].flatten())
|
|
72
|
-
beta_iy = np.interp(beta_len_interp, beta_len,
|
|
73
|
-
beta_path[1, :].flatten())
|
|
74
|
-
beta_iz = np.interp(beta_len_interp, beta_len,
|
|
75
|
-
beta_path[2, :].flatten())
|
|
76
|
-
|
|
77
|
-
self.beta_vec = np.vstack((beta_ix, beta_iy, beta_iz))[:self.dim, :]
|
|
78
|
-
self.beta_vec_len = beta_len_interp
|
|
79
|
-
self.symmetry_names = [p.name for p in self.path_points]
|
|
80
|
-
self.symmetry_locations = beta_len
|
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
"""Implement photonic crystal geometries."""
|
|
2
|
-
|
|
3
|
-
from typing import Callable, Optional, Tuple
|
|
4
|
-
|
|
5
|
-
import matplotlib.pyplot as plt
|
|
6
|
-
import numpy as np
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
class Geometry:
|
|
10
|
-
"""Geometry class."""
|
|
11
|
-
|
|
12
|
-
def __init__(
|
|
13
|
-
self,
|
|
14
|
-
t1: Tuple[float, float, float],
|
|
15
|
-
t2: Tuple[float, float, float],
|
|
16
|
-
t3: Tuple[float, float, float],
|
|
17
|
-
Nx: int = 32,
|
|
18
|
-
Ny: int = 32,
|
|
19
|
-
Nz: int = 32,
|
|
20
|
-
eps_r: Optional[np.ndarray] = None,
|
|
21
|
-
mu_r: Optional[np.ndarray] = None,
|
|
22
|
-
):
|
|
23
|
-
"""Initialize the geometry obj."""
|
|
24
|
-
self.t1 = np.array(t1).reshape(-1, 1)
|
|
25
|
-
self.t2 = np.array(t2).reshape(-1, 1)
|
|
26
|
-
self.t3 = np.array(t3).reshape(-1, 1)
|
|
27
|
-
|
|
28
|
-
self.T1 = (2 * np.pi * np.cross(self.t2, self.t3, axis=0) /
|
|
29
|
-
np.dot(t1, np.cross(self.t2, self.t3, axis=0)))
|
|
30
|
-
self.T2 = (2 * np.pi * np.cross(self.t3, self.t1, axis=0) /
|
|
31
|
-
np.dot(t1, np.cross(self.t2, self.t3, axis=0)))
|
|
32
|
-
self.T3 = (2 * np.pi * np.cross(self.t1, self.t2, axis=0) /
|
|
33
|
-
np.dot(t1, np.cross(self.t2, self.t3, axis=0)))
|
|
34
|
-
|
|
35
|
-
self.eps_r = eps_r or np.ones((Nx, Ny, Nz), dtype=complex)
|
|
36
|
-
self.mu_r = mu_r or np.ones((Nx, Ny, Nz), dtype=complex)
|
|
37
|
-
|
|
38
|
-
P0, Q0, R0 = np.meshgrid(
|
|
39
|
-
np.linspace(-0.5, 0.5, Nx),
|
|
40
|
-
np.linspace(-0.5, 0.5, Ny),
|
|
41
|
-
np.linspace(-0.5, 0.5, Nz),
|
|
42
|
-
)
|
|
43
|
-
self.x = P0 * self.t1[0] + Q0 * self.t2[0] + R0 * self.t3[0]
|
|
44
|
-
self.y = P0 * self.t1[1] + Q0 * self.t2[1] + R0 * self.t3[1]
|
|
45
|
-
self.z = P0 * self.t1[2] + Q0 * self.t2[2] + R0 * self.t3[2]
|
|
46
|
-
|
|
47
|
-
self.epsr_f: Optional[Callable] = None
|
|
48
|
-
self.mur_f: Optional[Callable] = None
|
|
49
|
-
|
|
50
|
-
def setup(self):
|
|
51
|
-
"""Set the material properties."""
|
|
52
|
-
if self.epsr_f:
|
|
53
|
-
self.epsr_f()
|
|
54
|
-
if self.mur_f:
|
|
55
|
-
self.mur_f()
|
|
56
|
-
|
|
57
|
-
def plot(self):
|
|
58
|
-
"""Plot ep_s and mu_r."""
|
|
59
|
-
raise NotImplementedError
|
|
60
|
-
|
|
61
|
-
def set_epsr_f(self, func: Callable):
|
|
62
|
-
"""Set decorator to set a eps_r profile."""
|
|
63
|
-
self.epsr_f = func
|
|
64
|
-
return func
|
|
65
|
-
|
|
66
|
-
def set_mur_f(self, func: Callable):
|
|
67
|
-
"""Set decorator to set a mu_r profile."""
|
|
68
|
-
self.mur_f = func
|
|
69
|
-
return func
|
morpho.py-0.0.3/morpho/solver.py
DELETED
|
@@ -1,86 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
"""Implement the solver."""
|
|
3
|
-
|
|
4
|
-
from typing import List
|
|
5
|
-
|
|
6
|
-
import matplotlib.pyplot as plt
|
|
7
|
-
import numpy as np
|
|
8
|
-
from scipy.linalg import eigh
|
|
9
|
-
|
|
10
|
-
from .brillouinzone import BrillouinZonePath as BZPath
|
|
11
|
-
from .geometry import Geometry
|
|
12
|
-
from .utils import convmat
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
class Solver:
|
|
16
|
-
"""Implement the PWE solver."""
|
|
17
|
-
|
|
18
|
-
def __init__(self,
|
|
19
|
-
geometry: Geometry,
|
|
20
|
-
path: BZPath,
|
|
21
|
-
P: int = 1,
|
|
22
|
-
Q: int = 1,
|
|
23
|
-
R: int = 1):
|
|
24
|
-
"""Initialize the PWE Solver."""
|
|
25
|
-
self.geo = geometry
|
|
26
|
-
self.path = path
|
|
27
|
-
self.P, self.Q, self.R = P, Q, R
|
|
28
|
-
self.eps_rc: np.ndarray
|
|
29
|
-
self.mu_rc: np.ndarray
|
|
30
|
-
|
|
31
|
-
self.wn: List[np.ndarray] = []
|
|
32
|
-
self.modes: List[np.ndarray] = []
|
|
33
|
-
|
|
34
|
-
def run(self):
|
|
35
|
-
"""Run the simulation."""
|
|
36
|
-
self.geo.setup()
|
|
37
|
-
self.eps_rc = convmat(self.geo.eps_r, self.P, self.Q, self.R)
|
|
38
|
-
self.mu_rc = convmat(self.geo.mu_r, self.P, self.Q, self.R)
|
|
39
|
-
|
|
40
|
-
eps_rk = np.kron(np.eye(3), self.eps_rc)
|
|
41
|
-
# mu_rk = np.kron(np.eye(3), self.eps_rc)
|
|
42
|
-
# epsr_ki = np.kron(np.eye(3), np.linalg.inv(self.eps_rc))
|
|
43
|
-
mu_rki = np.kron(np.eye(3), np.linalg.inv(self.mu_rc))
|
|
44
|
-
|
|
45
|
-
p = np.arange(-(self.P // 2), self.P // 2 + 1)
|
|
46
|
-
q = np.arange(-(self.Q // 2), self.Q // 2 + 1)
|
|
47
|
-
r = np.arange(-(self.R // 2), self.R // 2 + 1)
|
|
48
|
-
|
|
49
|
-
P0, Q0, R0 = np.meshgrid(p, q, r)
|
|
50
|
-
T1, T2, T3 = self.geo.T1, self.geo.T2, self.geo.T3
|
|
51
|
-
|
|
52
|
-
G_k = T1 * P0.flatten() + T2 * Q0.flatten() + T3 * R0.flatten()
|
|
53
|
-
beta = self.path.beta_vec
|
|
54
|
-
for col in range(beta.shape[1]):
|
|
55
|
-
k = beta[:, col, None] - G_k
|
|
56
|
-
|
|
57
|
-
KX = np.diag(k[0, :])
|
|
58
|
-
KY = np.diag(k[1, :])
|
|
59
|
-
KZ = np.diag(k[2, :])
|
|
60
|
-
K_V = np.vstack((
|
|
61
|
-
np.hstack((0 * KX, -KZ, KY)),
|
|
62
|
-
np.hstack((KZ, 0 * KY, -KX)),
|
|
63
|
-
np.hstack((-KY, KX, 0 * KZ)),
|
|
64
|
-
))
|
|
65
|
-
A = K_V @ mu_rki @ K_V
|
|
66
|
-
B = eps_rk
|
|
67
|
-
|
|
68
|
-
D, V = eigh(A, B)
|
|
69
|
-
|
|
70
|
-
self.wn.append(np.sqrt(-np.minimum(D, 0)))
|
|
71
|
-
self.modes.append(V)
|
|
72
|
-
|
|
73
|
-
def plot_bands(self):
|
|
74
|
-
"""Plot a bandgap diagram."""
|
|
75
|
-
beta_len = self.path.beta_vec_len
|
|
76
|
-
wn = np.vstack([d for d in self.wn])
|
|
77
|
-
fig, ax = plt.subplots()
|
|
78
|
-
ax.plot(beta_len, wn / (2 * np.pi), "k-")
|
|
79
|
-
ax.set_xticklabels(self.path.symmetry_names)
|
|
80
|
-
ax.set_xticks(self.path.symmetry_locations)
|
|
81
|
-
ax.set_xlim(0, self.path.symmetry_locations[-1])
|
|
82
|
-
ax.set_ylim(0, 2)
|
|
83
|
-
ax.set_xlabel("Bloch Wave Vector $\\beta$")
|
|
84
|
-
ax.set_ylabel("Frequency [$\\frac{a \\omega}{2\\pi c}$]")
|
|
85
|
-
ax.grid(True)
|
|
86
|
-
plt.show()
|
morpho.py-0.0.3/morpho/utils.py
DELETED
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
"""This module implements utilities."""
|
|
2
|
-
import matplotlib.pyplot as plt
|
|
3
|
-
import numpy as np
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
def convmat(A: np.ndarray, P: int = 1, Q: int = 1, R: int = 1):
|
|
7
|
-
"""Create a convolution matrix."""
|
|
8
|
-
NH = P * Q * R
|
|
9
|
-
Nx, Ny, Nz = A.shape
|
|
10
|
-
|
|
11
|
-
p = np.arange(-(P // 2), P//2 + 1)
|
|
12
|
-
q = np.arange(-(Q // 2), Q//2 + 1)
|
|
13
|
-
r = np.arange(-(R // 2), R//2 + 1)
|
|
14
|
-
|
|
15
|
-
A = np.fft.fftshift(np.fft.fftn(A) / (Nx*Ny*Nz))
|
|
16
|
-
|
|
17
|
-
p_0 = Nx // 2
|
|
18
|
-
q_0 = Ny // 2
|
|
19
|
-
r_0 = Nz // 2
|
|
20
|
-
|
|
21
|
-
C = np.zeros((NH, NH), dtype=complex)
|
|
22
|
-
|
|
23
|
-
for r_row in range(R):
|
|
24
|
-
for q_row in range(Q):
|
|
25
|
-
for p_row in range(P):
|
|
26
|
-
row = r_row*Q*P + q_row*P + p_row
|
|
27
|
-
|
|
28
|
-
for r_col in range(R):
|
|
29
|
-
for q_col in range(Q):
|
|
30
|
-
for p_col in range(P):
|
|
31
|
-
col = r_col*Q*P + q_col*P + p_col
|
|
32
|
-
|
|
33
|
-
p_fft = p[p_row] - p[p_col]
|
|
34
|
-
q_fft = q[q_row] - q[q_col]
|
|
35
|
-
r_fft = r[r_row] - r[r_col]
|
|
36
|
-
|
|
37
|
-
C[row, col] = A[p_0 + p_fft, q_0 + q_fft,
|
|
38
|
-
r_0 + r_fft]
|
|
39
|
-
return C
|
morpho.py-0.0.3/pyproject.toml
DELETED
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
[tool.poetry]
|
|
2
|
-
name = "morpho.py"
|
|
3
|
-
version = "0.0.3"
|
|
4
|
-
description = ""
|
|
5
|
-
authors = ["Tiago Vilela <tiagovla@gmail.com>"]
|
|
6
|
-
|
|
7
|
-
packages = [
|
|
8
|
-
{include = "morpho", from="."}
|
|
9
|
-
]
|
|
10
|
-
|
|
11
|
-
[tool.poetry.dependencies]
|
|
12
|
-
python = "^3.8"
|
|
13
|
-
numpy = "^1.20.1"
|
|
14
|
-
matplotlib = "^3.3.4"
|
|
15
|
-
scipy = "^1.6.1"
|
|
16
|
-
|
|
17
|
-
[tool.poetry.dev-dependencies]
|
|
18
|
-
pytest = "^5.2"
|
|
19
|
-
Sphinx = "^3.5.1"
|
|
20
|
-
sphinx-rtd-theme = "^0.5.1"
|
|
21
|
-
sphinx-autodoc-typehints = "^1.11.1"
|
|
22
|
-
|
|
23
|
-
[build-system]
|
|
24
|
-
requires = ["poetry-core>=1.0.0"]
|
|
25
|
-
build-backend = "poetry.core.masonry.api"
|
morpho.py-0.0.3/setup.py
DELETED
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
# -*- coding: utf-8 -*-
|
|
2
|
-
from setuptools import setup
|
|
3
|
-
|
|
4
|
-
package_dir = \
|
|
5
|
-
{'': '.'}
|
|
6
|
-
|
|
7
|
-
packages = \
|
|
8
|
-
['morpho']
|
|
9
|
-
|
|
10
|
-
package_data = \
|
|
11
|
-
{'': ['*']}
|
|
12
|
-
|
|
13
|
-
install_requires = \
|
|
14
|
-
['matplotlib>=3.3.4,<4.0.0', 'numpy>=1.20.1,<2.0.0', 'scipy>=1.6.1,<2.0.0']
|
|
15
|
-
|
|
16
|
-
setup_kwargs = {
|
|
17
|
-
'name': 'morpho.py',
|
|
18
|
-
'version': '0.0.3',
|
|
19
|
-
'description': '',
|
|
20
|
-
'long_description': None,
|
|
21
|
-
'author': 'Tiago Vilela',
|
|
22
|
-
'author_email': 'tiagovla@gmail.com',
|
|
23
|
-
'maintainer': None,
|
|
24
|
-
'maintainer_email': None,
|
|
25
|
-
'url': None,
|
|
26
|
-
'package_dir': package_dir,
|
|
27
|
-
'packages': packages,
|
|
28
|
-
'package_data': package_data,
|
|
29
|
-
'install_requires': install_requires,
|
|
30
|
-
'python_requires': '>=3.8,<4.0',
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
setup(**setup_kwargs)
|
|
File without changes
|
|
File without changes
|