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.
- emerge-0.4.6/.gitignore +39 -0
- emerge-0.4.6/PKG-INFO +75 -0
- emerge-0.4.6/README.md +54 -0
- emerge-0.4.6/_emerge/__init__.py +42 -0
- emerge-0.4.6/_emerge/bc.py +197 -0
- emerge-0.4.6/_emerge/cli.py +19 -0
- emerge-0.4.6/_emerge/coord.py +119 -0
- emerge-0.4.6/_emerge/cs.py +523 -0
- emerge-0.4.6/_emerge/dataset.py +36 -0
- emerge-0.4.6/_emerge/elements/__init__.py +19 -0
- emerge-0.4.6/_emerge/elements/femdata.py +212 -0
- emerge-0.4.6/_emerge/elements/index_interp.py +64 -0
- emerge-0.4.6/_emerge/elements/legrange2.py +172 -0
- emerge-0.4.6/_emerge/elements/ned2_interp.py +645 -0
- emerge-0.4.6/_emerge/elements/nedelec2.py +140 -0
- emerge-0.4.6/_emerge/elements/nedleg2.py +217 -0
- emerge-0.4.6/_emerge/geo/__init__.py +24 -0
- emerge-0.4.6/_emerge/geo/horn.py +107 -0
- emerge-0.4.6/_emerge/geo/modeler.py +449 -0
- emerge-0.4.6/_emerge/geo/operations.py +254 -0
- emerge-0.4.6/_emerge/geo/pcb.py +1244 -0
- emerge-0.4.6/_emerge/geo/pcb_tools/calculator.py +28 -0
- emerge-0.4.6/_emerge/geo/pcb_tools/macro.py +79 -0
- emerge-0.4.6/_emerge/geo/pmlbox.py +204 -0
- emerge-0.4.6/_emerge/geo/polybased.py +529 -0
- emerge-0.4.6/_emerge/geo/shapes.py +427 -0
- emerge-0.4.6/_emerge/geo/step.py +77 -0
- emerge-0.4.6/_emerge/geo2d.py +86 -0
- emerge-0.4.6/_emerge/geometry.py +510 -0
- emerge-0.4.6/_emerge/howto.py +214 -0
- emerge-0.4.6/_emerge/logsettings.py +5 -0
- emerge-0.4.6/_emerge/material.py +118 -0
- emerge-0.4.6/_emerge/mesh3d.py +730 -0
- emerge-0.4.6/_emerge/mesher.py +339 -0
- emerge-0.4.6/_emerge/mth/common_functions.py +33 -0
- emerge-0.4.6/_emerge/mth/integrals.py +71 -0
- emerge-0.4.6/_emerge/mth/optimized.py +357 -0
- emerge-0.4.6/_emerge/periodic.py +263 -0
- emerge-0.4.6/_emerge/physics/__init__.py +0 -0
- emerge-0.4.6/_emerge/physics/microwave/__init__.py +1 -0
- emerge-0.4.6/_emerge/physics/microwave/adaptive_freq.py +279 -0
- emerge-0.4.6/_emerge/physics/microwave/assembly/assembler.py +569 -0
- emerge-0.4.6/_emerge/physics/microwave/assembly/curlcurl.py +448 -0
- emerge-0.4.6/_emerge/physics/microwave/assembly/generalized_eigen.py +426 -0
- emerge-0.4.6/_emerge/physics/microwave/assembly/robinbc.py +433 -0
- emerge-0.4.6/_emerge/physics/microwave/microwave_3d.py +1150 -0
- emerge-0.4.6/_emerge/physics/microwave/microwave_bc.py +915 -0
- emerge-0.4.6/_emerge/physics/microwave/microwave_data.py +1148 -0
- emerge-0.4.6/_emerge/physics/microwave/periodic.py +82 -0
- emerge-0.4.6/_emerge/physics/microwave/port_functions.py +53 -0
- emerge-0.4.6/_emerge/physics/microwave/sc.py +175 -0
- emerge-0.4.6/_emerge/physics/microwave/simjob.py +147 -0
- emerge-0.4.6/_emerge/physics/microwave/sparam.py +138 -0
- emerge-0.4.6/_emerge/physics/microwave/touchstone.py +140 -0
- emerge-0.4.6/_emerge/plot/__init__.py +0 -0
- emerge-0.4.6/_emerge/plot/display.py +394 -0
- emerge-0.4.6/_emerge/plot/grapher.py +93 -0
- emerge-0.4.6/_emerge/plot/matplotlib/mpldisplay.py +264 -0
- emerge-0.4.6/_emerge/plot/pyvista/__init__.py +1 -0
- emerge-0.4.6/_emerge/plot/pyvista/display.py +931 -0
- emerge-0.4.6/_emerge/plot/pyvista/display_settings.py +24 -0
- emerge-0.4.6/_emerge/plot/simple_plots.py +551 -0
- emerge-0.4.6/_emerge/plot.py +225 -0
- emerge-0.4.6/_emerge/projects/__init__.py +0 -0
- emerge-0.4.6/_emerge/projects/_gen_base.txt +32 -0
- emerge-0.4.6/_emerge/projects/_load_base.txt +24 -0
- emerge-0.4.6/_emerge/projects/generate_project.py +40 -0
- emerge-0.4.6/_emerge/selection.py +596 -0
- emerge-0.4.6/_emerge/simmodel.py +444 -0
- emerge-0.4.6/_emerge/simulation_data.py +411 -0
- emerge-0.4.6/_emerge/solver.py +993 -0
- emerge-0.4.6/_emerge/system.py +54 -0
- emerge-0.4.6/emerge/__init__.py +54 -0
- emerge-0.4.6/emerge/__main__.py +5 -0
- emerge-0.4.6/emerge/lib.py +57 -0
- emerge-0.4.6/emerge/plot.py +1 -0
- emerge-0.4.6/emerge/pyvista.py +1 -0
- emerge-0.4.6/pyproject.toml +65 -0
emerge-0.4.6/.gitignore
ADDED
|
@@ -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)
|