emerge 0.4.6__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


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

Files changed (78) hide show
  1. emerge-0.4.6/.gitignore +39 -0
  2. emerge-0.4.6/PKG-INFO +75 -0
  3. emerge-0.4.6/README.md +54 -0
  4. emerge-0.4.6/_emerge/__init__.py +42 -0
  5. emerge-0.4.6/_emerge/bc.py +197 -0
  6. emerge-0.4.6/_emerge/cli.py +19 -0
  7. emerge-0.4.6/_emerge/coord.py +119 -0
  8. emerge-0.4.6/_emerge/cs.py +523 -0
  9. emerge-0.4.6/_emerge/dataset.py +36 -0
  10. emerge-0.4.6/_emerge/elements/__init__.py +19 -0
  11. emerge-0.4.6/_emerge/elements/femdata.py +212 -0
  12. emerge-0.4.6/_emerge/elements/index_interp.py +64 -0
  13. emerge-0.4.6/_emerge/elements/legrange2.py +172 -0
  14. emerge-0.4.6/_emerge/elements/ned2_interp.py +645 -0
  15. emerge-0.4.6/_emerge/elements/nedelec2.py +140 -0
  16. emerge-0.4.6/_emerge/elements/nedleg2.py +217 -0
  17. emerge-0.4.6/_emerge/geo/__init__.py +24 -0
  18. emerge-0.4.6/_emerge/geo/horn.py +107 -0
  19. emerge-0.4.6/_emerge/geo/modeler.py +449 -0
  20. emerge-0.4.6/_emerge/geo/operations.py +254 -0
  21. emerge-0.4.6/_emerge/geo/pcb.py +1244 -0
  22. emerge-0.4.6/_emerge/geo/pcb_tools/calculator.py +28 -0
  23. emerge-0.4.6/_emerge/geo/pcb_tools/macro.py +79 -0
  24. emerge-0.4.6/_emerge/geo/pmlbox.py +204 -0
  25. emerge-0.4.6/_emerge/geo/polybased.py +529 -0
  26. emerge-0.4.6/_emerge/geo/shapes.py +427 -0
  27. emerge-0.4.6/_emerge/geo/step.py +77 -0
  28. emerge-0.4.6/_emerge/geo2d.py +86 -0
  29. emerge-0.4.6/_emerge/geometry.py +510 -0
  30. emerge-0.4.6/_emerge/howto.py +214 -0
  31. emerge-0.4.6/_emerge/logsettings.py +5 -0
  32. emerge-0.4.6/_emerge/material.py +118 -0
  33. emerge-0.4.6/_emerge/mesh3d.py +730 -0
  34. emerge-0.4.6/_emerge/mesher.py +339 -0
  35. emerge-0.4.6/_emerge/mth/common_functions.py +33 -0
  36. emerge-0.4.6/_emerge/mth/integrals.py +71 -0
  37. emerge-0.4.6/_emerge/mth/optimized.py +357 -0
  38. emerge-0.4.6/_emerge/periodic.py +263 -0
  39. emerge-0.4.6/_emerge/physics/__init__.py +0 -0
  40. emerge-0.4.6/_emerge/physics/microwave/__init__.py +1 -0
  41. emerge-0.4.6/_emerge/physics/microwave/adaptive_freq.py +279 -0
  42. emerge-0.4.6/_emerge/physics/microwave/assembly/assembler.py +569 -0
  43. emerge-0.4.6/_emerge/physics/microwave/assembly/curlcurl.py +448 -0
  44. emerge-0.4.6/_emerge/physics/microwave/assembly/generalized_eigen.py +426 -0
  45. emerge-0.4.6/_emerge/physics/microwave/assembly/robinbc.py +433 -0
  46. emerge-0.4.6/_emerge/physics/microwave/microwave_3d.py +1150 -0
  47. emerge-0.4.6/_emerge/physics/microwave/microwave_bc.py +915 -0
  48. emerge-0.4.6/_emerge/physics/microwave/microwave_data.py +1148 -0
  49. emerge-0.4.6/_emerge/physics/microwave/periodic.py +82 -0
  50. emerge-0.4.6/_emerge/physics/microwave/port_functions.py +53 -0
  51. emerge-0.4.6/_emerge/physics/microwave/sc.py +175 -0
  52. emerge-0.4.6/_emerge/physics/microwave/simjob.py +147 -0
  53. emerge-0.4.6/_emerge/physics/microwave/sparam.py +138 -0
  54. emerge-0.4.6/_emerge/physics/microwave/touchstone.py +140 -0
  55. emerge-0.4.6/_emerge/plot/__init__.py +0 -0
  56. emerge-0.4.6/_emerge/plot/display.py +394 -0
  57. emerge-0.4.6/_emerge/plot/grapher.py +93 -0
  58. emerge-0.4.6/_emerge/plot/matplotlib/mpldisplay.py +264 -0
  59. emerge-0.4.6/_emerge/plot/pyvista/__init__.py +1 -0
  60. emerge-0.4.6/_emerge/plot/pyvista/display.py +931 -0
  61. emerge-0.4.6/_emerge/plot/pyvista/display_settings.py +24 -0
  62. emerge-0.4.6/_emerge/plot/simple_plots.py +551 -0
  63. emerge-0.4.6/_emerge/plot.py +225 -0
  64. emerge-0.4.6/_emerge/projects/__init__.py +0 -0
  65. emerge-0.4.6/_emerge/projects/_gen_base.txt +32 -0
  66. emerge-0.4.6/_emerge/projects/_load_base.txt +24 -0
  67. emerge-0.4.6/_emerge/projects/generate_project.py +40 -0
  68. emerge-0.4.6/_emerge/selection.py +596 -0
  69. emerge-0.4.6/_emerge/simmodel.py +444 -0
  70. emerge-0.4.6/_emerge/simulation_data.py +411 -0
  71. emerge-0.4.6/_emerge/solver.py +993 -0
  72. emerge-0.4.6/_emerge/system.py +54 -0
  73. emerge-0.4.6/emerge/__init__.py +54 -0
  74. emerge-0.4.6/emerge/__main__.py +5 -0
  75. emerge-0.4.6/emerge/lib.py +57 -0
  76. emerge-0.4.6/emerge/plot.py +1 -0
  77. emerge-0.4.6/emerge/pyvista.py +1 -0
  78. emerge-0.4.6/pyproject.toml +65 -0
@@ -0,0 +1,39 @@
1
+ # Python-generated files
2
+ __pycache__/
3
+ *.py[oc]
4
+ build/
5
+ dist/
6
+ wheels/
7
+ *.egg-info
8
+
9
+ # Virtual environments
10
+ .venv
11
+
12
+ # paths
13
+ meshes/
14
+
15
+ # Meshes
16
+ *.msh
17
+
18
+ # tests
19
+ **/test_*
20
+
21
+ version_update.txt
22
+
23
+ # EMerge data files
24
+ *.emerge
25
+ *.stl
26
+ *.step
27
+ *.s1p
28
+ *.s2p
29
+ *.s3p
30
+ *.s4p
31
+ *.snp
32
+ *.csv
33
+ *.fig
34
+
35
+ manual_scripts/
36
+
37
+ *.EMResults
38
+
39
+ tests/
emerge-0.4.6/PKG-INFO ADDED
@@ -0,0 +1,75 @@
1
+ Metadata-Version: 2.4
2
+ Name: emerge
3
+ Version: 0.4.6
4
+ Summary: An open source EM FEM simulator in Python
5
+ Project-URL: Homepage, https://github.com/FennisRobert/EMerge
6
+ Project-URL: Issues, https://github.com/FennisRobert/EMerge/issues
7
+ Requires-Python: <4.0,>=3.10
8
+ Requires-Dist: gmsh>=4.13.1
9
+ Requires-Dist: joblib>=1.5.1
10
+ Requires-Dist: loguru>=0.7.3
11
+ Requires-Dist: numba-progress>=1.1.3
12
+ Requires-Dist: numba>=0.61.2
13
+ Requires-Dist: numpy>=2.2.6
14
+ Requires-Dist: pyvista>=0.45.2
15
+ Requires-Dist: scipy>=1.15.3
16
+ Provides-Extra: pypardiso
17
+ Requires-Dist: pypardiso; extra == 'pypardiso'
18
+ Provides-Extra: umfpack
19
+ Requires-Dist: scikit-umfpack; extra == 'umfpack'
20
+ Description-Content-Type: text/markdown
21
+
22
+ ## Introduction
23
+
24
+ Hello everybody. Thanks for showing interest in this repository.
25
+
26
+ Feel free to download your version of EMerge and start playing around with it!
27
+ If you have suggestions/changes/questions either use the Github issue system or join the Discord using the following link:
28
+
29
+ **https://discord.gg/7PF4WcS6uA**
30
+
31
+ ## How to install
32
+
33
+ Clone this repository or download the files. While in the EMerge path containing the src/emerge folder, install the module using:
34
+ ```
35
+ pip install .
36
+ ```
37
+ If you want to install the library with PyPardiso on Intel machines, you can install the optional dependency with EMerge using:
38
+ ```
39
+ pip install ".[pypardiso]"
40
+ ```
41
+
42
+ ## Compatibility
43
+
44
+ As far as I know, the library should work on all systems. PyPARDISO is not supported on ARM but the current SuperLU and UMFPACK solvers work on ARM as well. Both SuperLU and UMFPACK can run on multi-processing implementations as long as you do entry-point protection:
45
+ ```
46
+ import emerge as em
47
+
48
+ def main():
49
+ # setup simulation
50
+
51
+ model.mw.frequency_domain(True, ..., multi_processing=True)
52
+
53
+ if __name__ == "__main__":
54
+ main()
55
+ ```
56
+ Otherwise, the parallel solver will default to SuperLU which is significantly slower on larger problems.
57
+
58
+ ## Required libraries
59
+
60
+ To run this FEM library you need the following libraries
61
+
62
+ - numpy
63
+ - scipy
64
+ - pypardiso
65
+ - gmsh
66
+ - loguru
67
+ - numba
68
+ - matplotlib (for the matplotlib base display)
69
+ - pyvista (for the PyVista base display)
70
+ - numba-progress
71
+ - scikit-umfpack
72
+
73
+ ## NOTICE
74
+
75
+ First time runs will be very slow because Numba needs to generate local C-compiled functions of the assembler and other mathematical functions. These compilations are chached so this should only take time once.
emerge-0.4.6/README.md ADDED
@@ -0,0 +1,54 @@
1
+ ## Introduction
2
+
3
+ Hello everybody. Thanks for showing interest in this repository.
4
+
5
+ Feel free to download your version of EMerge and start playing around with it!
6
+ If you have suggestions/changes/questions either use the Github issue system or join the Discord using the following link:
7
+
8
+ **https://discord.gg/7PF4WcS6uA**
9
+
10
+ ## How to install
11
+
12
+ Clone this repository or download the files. While in the EMerge path containing the src/emerge folder, install the module using:
13
+ ```
14
+ pip install .
15
+ ```
16
+ If you want to install the library with PyPardiso on Intel machines, you can install the optional dependency with EMerge using:
17
+ ```
18
+ pip install ".[pypardiso]"
19
+ ```
20
+
21
+ ## Compatibility
22
+
23
+ As far as I know, the library should work on all systems. PyPARDISO is not supported on ARM but the current SuperLU and UMFPACK solvers work on ARM as well. Both SuperLU and UMFPACK can run on multi-processing implementations as long as you do entry-point protection:
24
+ ```
25
+ import emerge as em
26
+
27
+ def main():
28
+ # setup simulation
29
+
30
+ model.mw.frequency_domain(True, ..., multi_processing=True)
31
+
32
+ if __name__ == "__main__":
33
+ main()
34
+ ```
35
+ Otherwise, the parallel solver will default to SuperLU which is significantly slower on larger problems.
36
+
37
+ ## Required libraries
38
+
39
+ To run this FEM library you need the following libraries
40
+
41
+ - numpy
42
+ - scipy
43
+ - pypardiso
44
+ - gmsh
45
+ - loguru
46
+ - numba
47
+ - matplotlib (for the matplotlib base display)
48
+ - pyvista (for the PyVista base display)
49
+ - numba-progress
50
+ - scikit-umfpack
51
+
52
+ ## NOTICE
53
+
54
+ First time runs will be very slow because Numba needs to generate local C-compiled functions of the assembler and other mathematical functions. These compilations are chached so this should only take time once.
@@ -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!')
@@ -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
+
@@ -0,0 +1,19 @@
1
+ import sys
2
+ import argparse
3
+ from _emerge.projects.generate_project import generate_project
4
+
5
+ def main():
6
+ parser = argparse.ArgumentParser(description="Emerge Project Generator CLI")
7
+ subparsers = parser.add_subparsers(dest="command")
8
+
9
+ # Subcommand: new
10
+ new_parser = subparsers.add_parser("new", help="Create a new project")
11
+ new_parser.add_argument("projectname", type=str, help="Name of the project directory")
12
+ new_parser.add_argument("filename", type=str, help="Base name for files")
13
+
14
+ args = parser.parse_args()
15
+
16
+ if args.command == "new":
17
+ generate_project(args.projectname, args.filename)
18
+ else:
19
+ parser.print_help()
@@ -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)