emerge 0.4.6__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.

Files changed (80) hide show
  1. emerge/__init__.py +54 -0
  2. emerge/__main__.py +5 -0
  3. emerge/_emerge/__init__.py +42 -0
  4. emerge/_emerge/bc.py +197 -0
  5. emerge/_emerge/coord.py +119 -0
  6. emerge/_emerge/cs.py +523 -0
  7. emerge/_emerge/dataset.py +36 -0
  8. emerge/_emerge/elements/__init__.py +19 -0
  9. emerge/_emerge/elements/femdata.py +212 -0
  10. emerge/_emerge/elements/index_interp.py +64 -0
  11. emerge/_emerge/elements/legrange2.py +172 -0
  12. emerge/_emerge/elements/ned2_interp.py +645 -0
  13. emerge/_emerge/elements/nedelec2.py +140 -0
  14. emerge/_emerge/elements/nedleg2.py +217 -0
  15. emerge/_emerge/geo/__init__.py +24 -0
  16. emerge/_emerge/geo/horn.py +107 -0
  17. emerge/_emerge/geo/modeler.py +449 -0
  18. emerge/_emerge/geo/operations.py +254 -0
  19. emerge/_emerge/geo/pcb.py +1244 -0
  20. emerge/_emerge/geo/pcb_tools/calculator.py +28 -0
  21. emerge/_emerge/geo/pcb_tools/macro.py +79 -0
  22. emerge/_emerge/geo/pmlbox.py +204 -0
  23. emerge/_emerge/geo/polybased.py +529 -0
  24. emerge/_emerge/geo/shapes.py +427 -0
  25. emerge/_emerge/geo/step.py +77 -0
  26. emerge/_emerge/geo2d.py +86 -0
  27. emerge/_emerge/geometry.py +510 -0
  28. emerge/_emerge/howto.py +214 -0
  29. emerge/_emerge/logsettings.py +5 -0
  30. emerge/_emerge/material.py +118 -0
  31. emerge/_emerge/mesh3d.py +730 -0
  32. emerge/_emerge/mesher.py +339 -0
  33. emerge/_emerge/mth/common_functions.py +33 -0
  34. emerge/_emerge/mth/integrals.py +71 -0
  35. emerge/_emerge/mth/optimized.py +357 -0
  36. emerge/_emerge/periodic.py +263 -0
  37. emerge/_emerge/physics/__init__.py +0 -0
  38. emerge/_emerge/physics/microwave/__init__.py +1 -0
  39. emerge/_emerge/physics/microwave/adaptive_freq.py +279 -0
  40. emerge/_emerge/physics/microwave/assembly/assembler.py +569 -0
  41. emerge/_emerge/physics/microwave/assembly/curlcurl.py +448 -0
  42. emerge/_emerge/physics/microwave/assembly/generalized_eigen.py +426 -0
  43. emerge/_emerge/physics/microwave/assembly/robinbc.py +433 -0
  44. emerge/_emerge/physics/microwave/microwave_3d.py +1150 -0
  45. emerge/_emerge/physics/microwave/microwave_bc.py +915 -0
  46. emerge/_emerge/physics/microwave/microwave_data.py +1148 -0
  47. emerge/_emerge/physics/microwave/periodic.py +82 -0
  48. emerge/_emerge/physics/microwave/port_functions.py +53 -0
  49. emerge/_emerge/physics/microwave/sc.py +175 -0
  50. emerge/_emerge/physics/microwave/simjob.py +147 -0
  51. emerge/_emerge/physics/microwave/sparam.py +138 -0
  52. emerge/_emerge/physics/microwave/touchstone.py +140 -0
  53. emerge/_emerge/plot/__init__.py +0 -0
  54. emerge/_emerge/plot/display.py +394 -0
  55. emerge/_emerge/plot/grapher.py +93 -0
  56. emerge/_emerge/plot/matplotlib/mpldisplay.py +264 -0
  57. emerge/_emerge/plot/pyvista/__init__.py +1 -0
  58. emerge/_emerge/plot/pyvista/display.py +931 -0
  59. emerge/_emerge/plot/pyvista/display_settings.py +24 -0
  60. emerge/_emerge/plot/simple_plots.py +551 -0
  61. emerge/_emerge/plot.py +225 -0
  62. emerge/_emerge/projects/__init__.py +0 -0
  63. emerge/_emerge/projects/_gen_base.txt +32 -0
  64. emerge/_emerge/projects/_load_base.txt +24 -0
  65. emerge/_emerge/projects/generate_project.py +40 -0
  66. emerge/_emerge/selection.py +596 -0
  67. emerge/_emerge/simmodel.py +444 -0
  68. emerge/_emerge/simulation_data.py +411 -0
  69. emerge/_emerge/solver.py +993 -0
  70. emerge/_emerge/system.py +54 -0
  71. emerge/cli.py +19 -0
  72. emerge/lib.py +57 -0
  73. emerge/plot.py +1 -0
  74. emerge/pyvista.py +1 -0
  75. {emerge-0.4.6.dist-info → emerge-0.4.8.dist-info}/METADATA +1 -1
  76. emerge-0.4.8.dist-info/RECORD +78 -0
  77. emerge-0.4.8.dist-info/entry_points.txt +2 -0
  78. emerge-0.4.6.dist-info/RECORD +0 -4
  79. emerge-0.4.6.dist-info/entry_points.txt +0 -2
  80. {emerge-0.4.6.dist-info → emerge-0.4.8.dist-info}/WHEEL +0 -0
emerge/__init__.py ADDED
@@ -0,0 +1,54 @@
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
+ NTHREADS = "1"
22
+
23
+ os.environ["OMP_NUM_THREADS"] = NTHREADS
24
+ os.environ["MKL_NUM_THREADS"] = NTHREADS
25
+ os.environ["OPENBLAS_NUM_THREADS"] = NTHREADS
26
+ os.environ["VECLIB_MAXIMUM_THREADS"] = NTHREADS
27
+ os.environ["NUMEXPR_NUM_THREADS"] = NTHREADS
28
+
29
+
30
+ from loguru import logger
31
+ from ._emerge.logsettings import logger_format
32
+ import sys
33
+
34
+ logger.remove()
35
+ logger.add(sys.stderr, format=logger_format)
36
+
37
+ logger.debug('Importing modules')
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
51
+ from . import lib
52
+ from ._emerge.howto import _HowtoClass
53
+ howto = _HowtoClass()
54
+ logger.debug('Importing complete!')
emerge/__main__.py ADDED
@@ -0,0 +1,5 @@
1
+ # src/emerge/__main__.py
2
+ from _emerge.cli import main
3
+
4
+ if __name__ == "__main__":
5
+ main()
@@ -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
+
@@ -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)