emerge 0.4.7__py3-none-any.whl → 0.4.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.
- emerge/__init__.py +14 -14
- emerge/_emerge/__init__.py +42 -0
- emerge/_emerge/bc.py +197 -0
- emerge/_emerge/coord.py +119 -0
- emerge/_emerge/cs.py +523 -0
- emerge/_emerge/dataset.py +36 -0
- emerge/_emerge/elements/__init__.py +19 -0
- emerge/_emerge/elements/femdata.py +212 -0
- emerge/_emerge/elements/index_interp.py +64 -0
- emerge/_emerge/elements/legrange2.py +172 -0
- emerge/_emerge/elements/ned2_interp.py +645 -0
- emerge/_emerge/elements/nedelec2.py +140 -0
- emerge/_emerge/elements/nedleg2.py +217 -0
- emerge/_emerge/geo/__init__.py +24 -0
- emerge/_emerge/geo/horn.py +107 -0
- emerge/_emerge/geo/modeler.py +449 -0
- emerge/_emerge/geo/operations.py +254 -0
- emerge/_emerge/geo/pcb.py +1244 -0
- emerge/_emerge/geo/pcb_tools/calculator.py +28 -0
- emerge/_emerge/geo/pcb_tools/macro.py +79 -0
- emerge/_emerge/geo/pmlbox.py +204 -0
- emerge/_emerge/geo/polybased.py +529 -0
- emerge/_emerge/geo/shapes.py +427 -0
- emerge/_emerge/geo/step.py +77 -0
- emerge/_emerge/geo2d.py +86 -0
- emerge/_emerge/geometry.py +510 -0
- emerge/_emerge/howto.py +214 -0
- emerge/_emerge/logsettings.py +5 -0
- emerge/_emerge/material.py +118 -0
- emerge/_emerge/mesh3d.py +730 -0
- emerge/_emerge/mesher.py +339 -0
- emerge/_emerge/mth/common_functions.py +33 -0
- emerge/_emerge/mth/integrals.py +71 -0
- emerge/_emerge/mth/optimized.py +357 -0
- emerge/_emerge/periodic.py +263 -0
- emerge/_emerge/physics/__init__.py +0 -0
- emerge/_emerge/physics/microwave/__init__.py +1 -0
- emerge/_emerge/physics/microwave/adaptive_freq.py +279 -0
- emerge/_emerge/physics/microwave/assembly/assembler.py +569 -0
- emerge/_emerge/physics/microwave/assembly/curlcurl.py +448 -0
- emerge/_emerge/physics/microwave/assembly/generalized_eigen.py +426 -0
- emerge/_emerge/physics/microwave/assembly/robinbc.py +433 -0
- emerge/_emerge/physics/microwave/microwave_3d.py +1150 -0
- emerge/_emerge/physics/microwave/microwave_bc.py +915 -0
- emerge/_emerge/physics/microwave/microwave_data.py +1148 -0
- emerge/_emerge/physics/microwave/periodic.py +82 -0
- emerge/_emerge/physics/microwave/port_functions.py +53 -0
- emerge/_emerge/physics/microwave/sc.py +175 -0
- emerge/_emerge/physics/microwave/simjob.py +147 -0
- emerge/_emerge/physics/microwave/sparam.py +138 -0
- emerge/_emerge/physics/microwave/touchstone.py +140 -0
- emerge/_emerge/plot/__init__.py +0 -0
- emerge/_emerge/plot/display.py +394 -0
- emerge/_emerge/plot/grapher.py +93 -0
- emerge/_emerge/plot/matplotlib/mpldisplay.py +264 -0
- emerge/_emerge/plot/pyvista/__init__.py +1 -0
- emerge/_emerge/plot/pyvista/display.py +931 -0
- emerge/_emerge/plot/pyvista/display_settings.py +24 -0
- emerge/_emerge/plot/simple_plots.py +551 -0
- emerge/_emerge/plot.py +225 -0
- emerge/_emerge/projects/__init__.py +0 -0
- emerge/_emerge/projects/_gen_base.txt +32 -0
- emerge/_emerge/projects/_load_base.txt +24 -0
- emerge/_emerge/projects/generate_project.py +40 -0
- emerge/_emerge/selection.py +596 -0
- emerge/_emerge/simmodel.py +444 -0
- emerge/_emerge/simulation_data.py +411 -0
- emerge/_emerge/solver.py +993 -0
- emerge/_emerge/system.py +54 -0
- emerge/cli.py +19 -0
- emerge/lib.py +1 -1
- emerge/plot.py +1 -1
- {emerge-0.4.7.dist-info → emerge-0.4.8.dist-info}/METADATA +1 -1
- emerge-0.4.8.dist-info/RECORD +78 -0
- emerge-0.4.8.dist-info/entry_points.txt +2 -0
- emerge-0.4.7.dist-info/RECORD +0 -9
- emerge-0.4.7.dist-info/entry_points.txt +0 -2
- {emerge-0.4.7.dist-info → emerge-0.4.8.dist-info}/WHEEL +0 -0
emerge/__init__.py
CHANGED
|
@@ -28,7 +28,7 @@ os.environ["NUMEXPR_NUM_THREADS"] = NTHREADS
|
|
|
28
28
|
|
|
29
29
|
|
|
30
30
|
from loguru import logger
|
|
31
|
-
from _emerge.logsettings import logger_format
|
|
31
|
+
from ._emerge.logsettings import logger_format
|
|
32
32
|
import sys
|
|
33
33
|
|
|
34
34
|
logger.remove()
|
|
@@ -36,19 +36,19 @@ logger.add(sys.stderr, format=logger_format)
|
|
|
36
36
|
|
|
37
37
|
logger.debug('Importing modules')
|
|
38
38
|
|
|
39
|
-
from _emerge.simmodel import Simulation3D
|
|
40
|
-
from _emerge.material import Material
|
|
41
|
-
from _emerge import bc
|
|
42
|
-
from _emerge.solver import SolverBicgstab, SolverGMRES, SolveRoutine, ReverseCuthillMckee, Sorter, SolverPardiso, SolverUMFPACK, SolverSuperLU, EMSolver
|
|
43
|
-
from _emerge.cs import CoordinateSystem, CS, GCS, Plane, Axis, XAX, YAX, ZAX, XYPLANE, XZPLANE, YZPLANE, YXPLANE, ZXPLANE, ZYPLANE
|
|
44
|
-
from _emerge.coord import Line
|
|
45
|
-
from _emerge import geo
|
|
46
|
-
from _emerge.selection import Selection, FaceSelection, DomainSelection, EdgeSelection
|
|
47
|
-
from _emerge.mth.common_functions import norm, coax_rout, coax_rin
|
|
48
|
-
from _emerge.physics.microwave.sc import stratton_chu
|
|
49
|
-
from _emerge.periodic import RectCell, HexCell
|
|
50
|
-
from _emerge.mesher import Algorithm2D, Algorithm3D
|
|
39
|
+
from ._emerge.simmodel import Simulation3D
|
|
40
|
+
from ._emerge.material import Material
|
|
41
|
+
from ._emerge import bc
|
|
42
|
+
from ._emerge.solver import SolverBicgstab, SolverGMRES, SolveRoutine, ReverseCuthillMckee, Sorter, SolverPardiso, SolverUMFPACK, SolverSuperLU, EMSolver
|
|
43
|
+
from ._emerge.cs import CoordinateSystem, CS, GCS, Plane, Axis, XAX, YAX, ZAX, XYPLANE, XZPLANE, YZPLANE, YXPLANE, ZXPLANE, ZYPLANE
|
|
44
|
+
from ._emerge.coord import Line
|
|
45
|
+
from ._emerge import geo
|
|
46
|
+
from ._emerge.selection import Selection, FaceSelection, DomainSelection, EdgeSelection
|
|
47
|
+
from ._emerge.mth.common_functions import norm, coax_rout, coax_rin
|
|
48
|
+
from ._emerge.physics.microwave.sc import stratton_chu
|
|
49
|
+
from ._emerge.periodic import RectCell, HexCell
|
|
50
|
+
from ._emerge.mesher import Algorithm2D, Algorithm3D
|
|
51
51
|
from . import lib
|
|
52
|
-
from _emerge.howto import _HowtoClass
|
|
52
|
+
from ._emerge.howto import _HowtoClass
|
|
53
53
|
howto = _HowtoClass()
|
|
54
54
|
logger.debug('Importing complete!')
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# """A Python based FEM solver.
|
|
2
|
+
# Copyright (C) 2025 name of Robert Fennis
|
|
3
|
+
|
|
4
|
+
# This program is free software; you can redistribute it and/or
|
|
5
|
+
# modify it under the terms of the GNU General Public License
|
|
6
|
+
# as published by the Free Software Foundation; either version 2
|
|
7
|
+
# of the License, or (at your option) any later version.
|
|
8
|
+
|
|
9
|
+
# This program is distributed in the hope that it will be useful,
|
|
10
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
11
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
12
|
+
# GNU General Public License for more details.
|
|
13
|
+
|
|
14
|
+
# You should have received a copy of the GNU General Public License
|
|
15
|
+
# along with this program; if not, see
|
|
16
|
+
# <https://www.gnu.org/licenses/>.
|
|
17
|
+
|
|
18
|
+
# """
|
|
19
|
+
# import os
|
|
20
|
+
|
|
21
|
+
# os.environ["OMP_NUM_THREADS"] = "1"
|
|
22
|
+
# os.environ["MKL_NUM_THREADS"] = "1"
|
|
23
|
+
# os.environ["OPENBLAS_NUM_THREADS"] = "1"
|
|
24
|
+
|
|
25
|
+
# from loguru import logger
|
|
26
|
+
# from .logsettings import logger_format
|
|
27
|
+
# import sys
|
|
28
|
+
|
|
29
|
+
# logger.remove()
|
|
30
|
+
# logger.add(sys.stderr, format=logger_format)
|
|
31
|
+
|
|
32
|
+
# logger.debug('Importing modules')
|
|
33
|
+
# from _emerge.simmodel import Simulation3D
|
|
34
|
+
# from _emerge.material import Material, FR4, AIR, VACUUM, COPPER
|
|
35
|
+
# import bc
|
|
36
|
+
# from _emerge.solver import superlu_info, SolverBicgstab, SolverGMRES, SolveRoutine, ReverseCuthillMckee, Sorter, SolverPardiso, SolverUMFPACK
|
|
37
|
+
# from _emerge.cs import CoordinateSystem, Plane, Axis, XAX, YAX, ZAX, XYPLANE, XZPLANE, YZPLANE, YXPLANE, ZXPLANE, ZYPLANE
|
|
38
|
+
# from _emerge.coord import Line
|
|
39
|
+
# import geo
|
|
40
|
+
# from _emerge.selection import Selection, FaceSelection, DomainSelection, EdgeSelection
|
|
41
|
+
# from _emerge.mth.common_functions import norm
|
|
42
|
+
# logger.debug('Importing complete!')
|
emerge/_emerge/bc.py
ADDED
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
# EMerge is an open source Python based FEM EM simulation module.
|
|
2
|
+
# Copyright (C) 2025 Robert Fennis.
|
|
3
|
+
|
|
4
|
+
# This program is free software; you can redistribute it and/or
|
|
5
|
+
# modify it under the terms of the GNU General Public License
|
|
6
|
+
# as published by the Free Software Foundation; either version 2
|
|
7
|
+
# of the License, or (at your option) any later version.
|
|
8
|
+
|
|
9
|
+
# This program is distributed in the hope that it will be useful,
|
|
10
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
11
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
12
|
+
# GNU General Public License for more details.
|
|
13
|
+
|
|
14
|
+
# You should have received a copy of the GNU General Public License
|
|
15
|
+
# along with this program; if not, see
|
|
16
|
+
# <https://www.gnu.org/licenses/>.
|
|
17
|
+
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
from enum import Enum
|
|
20
|
+
from loguru import logger
|
|
21
|
+
from .selection import Selection, FaceSelection
|
|
22
|
+
import numpy as np
|
|
23
|
+
from .geometry import GeoObject
|
|
24
|
+
|
|
25
|
+
class BCDimension(Enum):
|
|
26
|
+
ANY = -1
|
|
27
|
+
NODE = 0
|
|
28
|
+
EDGE = 1
|
|
29
|
+
FACE = 2
|
|
30
|
+
DOMAIN = 3
|
|
31
|
+
|
|
32
|
+
def _unique(input: list[int]) -> list:
|
|
33
|
+
""" Returns a sorted list of all unique integers/floats in a list."""
|
|
34
|
+
output = sorted(list(set(input)))
|
|
35
|
+
return output
|
|
36
|
+
|
|
37
|
+
class BoundaryCondition:
|
|
38
|
+
"""A generalized class for all boundary condition objects.
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
def __init__(self, assignment: GeoObject | Selection):
|
|
42
|
+
|
|
43
|
+
self.dimension: BCDimension = BCDimension.ANY
|
|
44
|
+
self.indices: list[int] = []
|
|
45
|
+
self.face_indices: list[int] = []
|
|
46
|
+
self.edge_indices: list[int] = []
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
if isinstance(assignment, GeoObject):
|
|
50
|
+
assignment = assignment.select
|
|
51
|
+
|
|
52
|
+
self.selection: Selection = assignment
|
|
53
|
+
self.tags: list[int] = self.selection.tags
|
|
54
|
+
|
|
55
|
+
@property
|
|
56
|
+
def dim(self) -> int:
|
|
57
|
+
''' The dimension of the boundary condition as integer (0,1,2,3).'''
|
|
58
|
+
return self.dimension.value
|
|
59
|
+
|
|
60
|
+
def __repr__(self) -> str:
|
|
61
|
+
if self.dimension is BCDimension.ANY:
|
|
62
|
+
return f'{type(self).__name__}{self.tags}'
|
|
63
|
+
elif self.dimension is BCDimension.EDGE:
|
|
64
|
+
return f'{type(self).__name__}{self.tags}'
|
|
65
|
+
elif self.dimension is BCDimension.NODE:
|
|
66
|
+
return f'{type(self).__name__}{self.tags}'
|
|
67
|
+
elif self.dimension is BCDimension.FACE:
|
|
68
|
+
return f'{type(self).__name__}{self.tags}'
|
|
69
|
+
elif self.dimension is BCDimension.DOMAIN:
|
|
70
|
+
return f'{type(self).__name__}{self.tags}'
|
|
71
|
+
|
|
72
|
+
def __str__(self) -> str:
|
|
73
|
+
return self.__repr__()
|
|
74
|
+
|
|
75
|
+
def check_dimension(self, tags: list[tuple[int,int]]) -> None:
|
|
76
|
+
# check if all tags have the same timension (dim, tag)
|
|
77
|
+
if not isinstance(tags, list):
|
|
78
|
+
raise TypeError(f'Argument tags must be of type list, instead its {type(tags)}')
|
|
79
|
+
if len(tags) == 0:
|
|
80
|
+
return
|
|
81
|
+
if not all(isinstance(x, tuple) and len(x) == 2 for x in tags):
|
|
82
|
+
raise TypeError(f'Argument tags must be of type list of tuples, instead its {type(tags)}')
|
|
83
|
+
if not all(isinstance(x[0], int) and isinstance(x[1], int) for x in tags):
|
|
84
|
+
raise TypeError(f'Argument tags must be of type list of tuples of ints, instead its {type(tags)}')
|
|
85
|
+
if not all(x[0] == tags[0][0] for x in tags):
|
|
86
|
+
raise ValueError(f'All tags must have the same dimension, instead its {tags}')
|
|
87
|
+
dimension = tags[0][0]
|
|
88
|
+
if self.dimension is BCDimension.ANY:
|
|
89
|
+
logger.info(f'Assigning dimension {BCDimension(dimension)} to {self}')
|
|
90
|
+
self.dimension = BCDimension(dimension)
|
|
91
|
+
elif self.dimension != BCDimension(dimension):
|
|
92
|
+
raise ValueError(f'Current boundary condition has dimension {self.dimension}, but tags have dimension {BCDimension(dimension)}')
|
|
93
|
+
|
|
94
|
+
def add_tags(self, tags: list[tuple[int,int]]) -> None:
|
|
95
|
+
"""Adds the given taggs to this boundary condition.
|
|
96
|
+
|
|
97
|
+
Args:
|
|
98
|
+
tags (list[tuple[int,int]]): The tags to include
|
|
99
|
+
"""
|
|
100
|
+
self.check_dimension(tags)
|
|
101
|
+
tags = [x[1] for x in tags]
|
|
102
|
+
self.tags = _unique(self.tags + tags)
|
|
103
|
+
|
|
104
|
+
def remove_tags(self, tags: list[int]) -> list[int]:
|
|
105
|
+
"""Removes the tags provided by tags from this boundary condition.
|
|
106
|
+
|
|
107
|
+
Return sonly the tags that are actually excluded from this face.
|
|
108
|
+
|
|
109
|
+
Args:
|
|
110
|
+
tags (list[int]): The tags to exclude.
|
|
111
|
+
|
|
112
|
+
Returns:
|
|
113
|
+
list[int]: A list of actually excluded tags.
|
|
114
|
+
"""
|
|
115
|
+
excluded_edges = [x for x in self.tags if x in tags]
|
|
116
|
+
self.tags = [x for x in self.tags if x not in tags]
|
|
117
|
+
return excluded_edges
|
|
118
|
+
|
|
119
|
+
def exclude_bc(self, other: BoundaryCondition) -> list[int]:
|
|
120
|
+
"""Excludes all faces for a provided boundary condition object from this boundary condition assignment.
|
|
121
|
+
|
|
122
|
+
Args:
|
|
123
|
+
other (BoundaryCondition): The boundary condition of which the faces should be excluded
|
|
124
|
+
|
|
125
|
+
Returns:
|
|
126
|
+
list[int]: A list of excluded face tags.
|
|
127
|
+
"""
|
|
128
|
+
return self.remove_tags(other.tags)
|
|
129
|
+
|
|
130
|
+
class BoundaryConditionSet:
|
|
131
|
+
|
|
132
|
+
def __init__(self):
|
|
133
|
+
|
|
134
|
+
self.boundary_conditions: list[BoundaryCondition] = []
|
|
135
|
+
self._initialized: bool = False
|
|
136
|
+
|
|
137
|
+
def _construct_bc(self, constructor: type):
|
|
138
|
+
def constr(*args, **kwargs):
|
|
139
|
+
obj = constructor(*args, **kwargs)
|
|
140
|
+
self.assign(obj)
|
|
141
|
+
return obj
|
|
142
|
+
return constr
|
|
143
|
+
|
|
144
|
+
def count(self, bctype: type) -> int:
|
|
145
|
+
return len(self.oftype(bctype))
|
|
146
|
+
|
|
147
|
+
def oftype(self, bctype: type) -> list[BoundaryCondition]:
|
|
148
|
+
return [item for item in self. boundary_conditions if isinstance(item, bctype)]
|
|
149
|
+
|
|
150
|
+
def reset(self) -> None:
|
|
151
|
+
self.boundary_conditions = []
|
|
152
|
+
|
|
153
|
+
def assign(self,
|
|
154
|
+
bc: BoundaryCondition) -> None:
|
|
155
|
+
"""Assign a boundary-condition object to a domain or list of domains.
|
|
156
|
+
This method must be called to submit any boundary condition object you made to the physics.
|
|
157
|
+
|
|
158
|
+
Args:
|
|
159
|
+
bcs *(BoundaryCondition): A list of boundary condition objects.
|
|
160
|
+
"""
|
|
161
|
+
self._initialized = True
|
|
162
|
+
wordmap = {
|
|
163
|
+
0: 'point',
|
|
164
|
+
1: 'edge',
|
|
165
|
+
2: 'face',
|
|
166
|
+
3: 'domain'
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
bc.add_tags(bc.selection.dimtags)
|
|
170
|
+
|
|
171
|
+
logger.info('Excluding other possible boundary conditions')
|
|
172
|
+
|
|
173
|
+
for existing_bc in self.boundary_conditions:
|
|
174
|
+
excluded = existing_bc.exclude_bc(bc)
|
|
175
|
+
if excluded:
|
|
176
|
+
logger.debug(f'Removed the {excluded} tags from {wordmap[bc.dim]} BC {existing_bc}')
|
|
177
|
+
self.boundary_conditions.append(bc)
|
|
178
|
+
|
|
179
|
+
class Periodic(BoundaryCondition):
|
|
180
|
+
|
|
181
|
+
def __init__(self,
|
|
182
|
+
selection1: FaceSelection,
|
|
183
|
+
selection2: FaceSelection,
|
|
184
|
+
dv: tuple[float,float,float],
|
|
185
|
+
):
|
|
186
|
+
self.face1: BoundaryCondition = BoundaryCondition(selection1)
|
|
187
|
+
self.face2: BoundaryCondition = BoundaryCondition(selection2)
|
|
188
|
+
super().__init__(FaceSelection(selection1.tags + selection2.tags))
|
|
189
|
+
self.dv: tuple[float,float,float] = dv
|
|
190
|
+
self.ux: float = 0
|
|
191
|
+
self.uy: float = 0
|
|
192
|
+
self.uz: float = 0
|
|
193
|
+
|
|
194
|
+
def phi(self, k0) -> complex:
|
|
195
|
+
dx, dy, dz = self.dv
|
|
196
|
+
return np.exp(-1j*k0*(self.ux*dx+self.uy*dy+self.uz*dz))
|
|
197
|
+
|
emerge/_emerge/coord.py
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
# EMerge is an open source Python based FEM EM simulation module.
|
|
2
|
+
# Copyright (C) 2025 Robert Fennis.
|
|
3
|
+
|
|
4
|
+
# This program is free software; you can redistribute it and/or
|
|
5
|
+
# modify it under the terms of the GNU General Public License
|
|
6
|
+
# as published by the Free Software Foundation; either version 2
|
|
7
|
+
# of the License, or (at your option) any later version.
|
|
8
|
+
|
|
9
|
+
# This program is distributed in the hope that it will be useful,
|
|
10
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
11
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
12
|
+
# GNU General Public License for more details.
|
|
13
|
+
|
|
14
|
+
# You should have received a copy of the GNU General Public License
|
|
15
|
+
# along with this program; if not, see
|
|
16
|
+
# <https://www.gnu.org/licenses/>.
|
|
17
|
+
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
import numpy as np
|
|
20
|
+
from typing import Callable
|
|
21
|
+
|
|
22
|
+
def gauss3_composite(x: np.ndarray, y: np.ndarray) -> float:
|
|
23
|
+
"""
|
|
24
|
+
Composite 3-point Gauss (order-2) integral
|
|
25
|
+
on *equally spaced* 1-D data.
|
|
26
|
+
|
|
27
|
+
Sections:
|
|
28
|
+
(x0,x1,x2), (x2,x3,x4), (x4,x5,x6), ...
|
|
29
|
+
|
|
30
|
+
The rule on one section [x_i, x_{i+2}] is
|
|
31
|
+
|
|
32
|
+
∫ f dx ≈ (h/2) Σ_k w_k · f( x̄ + (h/2) ξ_k )
|
|
33
|
+
|
|
34
|
+
where h = x_{i+2}−x_i and
|
|
35
|
+
ξ = (−√3/5, 0, +√3/5),
|
|
36
|
+
w = (8/9, 5/9, 8/9) ← as requested.
|
|
37
|
+
|
|
38
|
+
Because f is only known at the three nodes, we
|
|
39
|
+
rebuild the quadratic that interpolates them and
|
|
40
|
+
evaluate that polynomial at the Gauss points.
|
|
41
|
+
"""
|
|
42
|
+
x = np.asarray(x, dtype=np.complex128)
|
|
43
|
+
y = np.asarray(y, dtype=np.complex128)
|
|
44
|
+
|
|
45
|
+
if x.ndim != 1 or y.ndim != 1 or x.size != y.size:
|
|
46
|
+
raise ValueError("x and y must be 1-D arrays of equal length.")
|
|
47
|
+
if x.size % 2 == 0:
|
|
48
|
+
raise ValueError("Number of samples must be odd (… 0,1,2; 2,3,4; …).")
|
|
49
|
+
|
|
50
|
+
# constant spacing
|
|
51
|
+
h = x[1] - x[0]
|
|
52
|
+
|
|
53
|
+
# Gauss–Legendre nodes and *your* weights
|
|
54
|
+
xi = np.sqrt(3/5)
|
|
55
|
+
nodes = np.array([-xi, 0.0, +xi])
|
|
56
|
+
weights = np.array([5/9, 8/9, 5/9])
|
|
57
|
+
|
|
58
|
+
total = 0.0
|
|
59
|
+
for i in range(0, x.size - 2, 2):
|
|
60
|
+
y0, y1, y2 = y[i:i+3]
|
|
61
|
+
# array([[ 5.00000000e-01, -1.00000000e+00, 5.00000000e-01],
|
|
62
|
+
# [-5.00000000e-01, 3.71914213e-17, 5.00000000e-01],
|
|
63
|
+
# [-5.55111512e-17, 1.00000000e+00, 5.55111512e-17]])
|
|
64
|
+
# coefficients of the quadratic passing through
|
|
65
|
+
# (-1,y0), (0,y1), (1,y2) in local coords t
|
|
66
|
+
a = y0*0.5 - y1 + 0.5*y2
|
|
67
|
+
b = -y0*0.5 + 0.5*y2
|
|
68
|
+
c = y1
|
|
69
|
+
|
|
70
|
+
# local → global mapping
|
|
71
|
+
poly_vals = a*nodes**2 + b*nodes + c
|
|
72
|
+
total += np.dot(weights, poly_vals)
|
|
73
|
+
|
|
74
|
+
return total
|
|
75
|
+
|
|
76
|
+
class Line:
|
|
77
|
+
""" A Line class used for convenient definition of integration lines"""
|
|
78
|
+
def __init__(self, xpts: np.ndarray,
|
|
79
|
+
ypts: np.ndarray,
|
|
80
|
+
zpts: np.ndarray):
|
|
81
|
+
self.xs: np.ndarray = xpts
|
|
82
|
+
self.ys: np.ndarray = ypts
|
|
83
|
+
self.zs: np.ndarray = zpts
|
|
84
|
+
self.dxs: np.ndarray = xpts[1:] - xpts[:-1]
|
|
85
|
+
self.dys: np.ndarray = ypts[1:] - ypts[:-1]
|
|
86
|
+
self.dzs: np.ndarray = zpts[1:] - zpts[:-1]
|
|
87
|
+
self.dl = np.sqrt(self.dxs**2 + self.dys**2 + self.dzs**2)
|
|
88
|
+
self.length: float = np.sum(np.sqrt(self.dxs**2 + self.dys**2 + self.dzs**2))
|
|
89
|
+
self.l: np.ndarray = np.concatenate((np.array([0,]), np.cumsum(self.dl)))
|
|
90
|
+
self.xmid: np.ndarray = 0.5*(xpts[:-1] + xpts[1:])
|
|
91
|
+
self.ymid: np.ndarray = 0.5*(ypts[:-1] + ypts[1:])
|
|
92
|
+
self.zmid: np.ndarray = 0.5*(zpts[:-1] + zpts[1:])
|
|
93
|
+
|
|
94
|
+
self.dx = self.dxs[0]
|
|
95
|
+
self.dy = self.dys[0]
|
|
96
|
+
self.dz = self.dzs[0]
|
|
97
|
+
|
|
98
|
+
@property
|
|
99
|
+
def cmid(self) -> tuple[np.ndarray, np.ndarray, np.ndarray]:
|
|
100
|
+
return self.xmid, self.ymid, self.zmid
|
|
101
|
+
|
|
102
|
+
@property
|
|
103
|
+
def cpoint(self) -> tuple[np.ndarray, np.ndarray, np.ndarray]:
|
|
104
|
+
return self.xs, self.ys, self.zs
|
|
105
|
+
|
|
106
|
+
@staticmethod
|
|
107
|
+
def from_points(start: np.ndarray, end: np.ndarray, Npts: int) -> Line:
|
|
108
|
+
x1, y1, z1 = start
|
|
109
|
+
x2, y2, z2 = end
|
|
110
|
+
xs = np.linspace(x1, x2, Npts)
|
|
111
|
+
ys = np.linspace(y1, y2, Npts)
|
|
112
|
+
zs = np.linspace(z1, z2, Npts)
|
|
113
|
+
return Line(xs, ys, zs)
|
|
114
|
+
|
|
115
|
+
def line_integral(self, evalfunc: Callable) -> complex:
|
|
116
|
+
"""Compute the line integral for a complex vector field function evalfunc."""
|
|
117
|
+
Ex, Ey, Ez = evalfunc(*self.cpoint)
|
|
118
|
+
EdotL = Ex*self.dx + Ey*self.dy + Ez*self.dz
|
|
119
|
+
return gauss3_composite(self.l, EdotL)
|