emerge 0.4.6__tar.gz → 0.4.8__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 (99) hide show
  1. emerge-0.4.8/.bumpversion.toml +27 -0
  2. emerge-0.4.8/.github/ISSUE_TEMPLATE/bug_report.md +38 -0
  3. emerge-0.4.8/.github/ISSUE_TEMPLATE/feature_request.md +20 -0
  4. emerge-0.4.8/.python-version +1 -0
  5. emerge-0.4.8/.vscode/settings.json +3 -0
  6. {emerge-0.4.6 → emerge-0.4.8}/PKG-INFO +1 -1
  7. {emerge-0.4.6 → emerge-0.4.8}/emerge/__init__.py +14 -14
  8. {emerge-0.4.6 → emerge-0.4.8/emerge}/_emerge/physics/microwave/assembly/curlcurl.py +1 -1
  9. {emerge-0.4.6 → emerge-0.4.8}/emerge/lib.py +1 -1
  10. emerge-0.4.8/emerge/plot.py +1 -0
  11. emerge-0.4.8/examples/demo10_sgh.py +111 -0
  12. emerge-0.4.8/examples/demo11_lumped_element_filter.py +118 -0
  13. emerge-0.4.8/examples/demo1_stepped_imp_filter.py +149 -0
  14. emerge-0.4.8/examples/demo2_combline_filter.py +130 -0
  15. emerge-0.4.8/examples/demo3_coupled_line_filter.py +133 -0
  16. emerge-0.4.8/examples/demo3_patch_antenna.py +141 -0
  17. emerge-0.4.8/examples/demo4_boundary_selection.py +136 -0
  18. emerge-0.4.8/examples/demo5_revolve.py +24 -0
  19. emerge-0.4.8/examples/demo6_striplines_with_vias.py +95 -0
  20. emerge-0.4.8/examples/demo7_periodic_cells.py +111 -0
  21. emerge-0.4.8/examples/demo8_waveguide_bpf_synthesis.py +191 -0
  22. emerge-0.4.8/examples/demo9_dielectric_resonator.py +95 -0
  23. {emerge-0.4.6 → emerge-0.4.8}/pyproject.toml +2 -13
  24. emerge-0.4.8/src/__init__.py +0 -0
  25. emerge-0.4.8/src/_img/logo.jpeg +0 -0
  26. emerge-0.4.8/uv.lock +955 -0
  27. emerge-0.4.6/emerge/plot.py +0 -1
  28. {emerge-0.4.6 → emerge-0.4.8}/.gitignore +0 -0
  29. {emerge-0.4.6 → emerge-0.4.8}/README.md +0 -0
  30. {emerge-0.4.6 → emerge-0.4.8}/emerge/__main__.py +0 -0
  31. {emerge-0.4.6 → emerge-0.4.8/emerge}/_emerge/__init__.py +0 -0
  32. {emerge-0.4.6 → emerge-0.4.8/emerge}/_emerge/bc.py +0 -0
  33. {emerge-0.4.6 → emerge-0.4.8/emerge}/_emerge/coord.py +0 -0
  34. {emerge-0.4.6 → emerge-0.4.8/emerge}/_emerge/cs.py +0 -0
  35. {emerge-0.4.6 → emerge-0.4.8/emerge}/_emerge/dataset.py +0 -0
  36. {emerge-0.4.6 → emerge-0.4.8/emerge}/_emerge/elements/__init__.py +0 -0
  37. {emerge-0.4.6 → emerge-0.4.8/emerge}/_emerge/elements/femdata.py +0 -0
  38. {emerge-0.4.6 → emerge-0.4.8/emerge}/_emerge/elements/index_interp.py +0 -0
  39. {emerge-0.4.6 → emerge-0.4.8/emerge}/_emerge/elements/legrange2.py +0 -0
  40. {emerge-0.4.6 → emerge-0.4.8/emerge}/_emerge/elements/ned2_interp.py +0 -0
  41. {emerge-0.4.6 → emerge-0.4.8/emerge}/_emerge/elements/nedelec2.py +0 -0
  42. {emerge-0.4.6 → emerge-0.4.8/emerge}/_emerge/elements/nedleg2.py +0 -0
  43. {emerge-0.4.6 → emerge-0.4.8/emerge}/_emerge/geo/__init__.py +0 -0
  44. {emerge-0.4.6 → emerge-0.4.8/emerge}/_emerge/geo/horn.py +0 -0
  45. {emerge-0.4.6 → emerge-0.4.8/emerge}/_emerge/geo/modeler.py +0 -0
  46. {emerge-0.4.6 → emerge-0.4.8/emerge}/_emerge/geo/operations.py +0 -0
  47. {emerge-0.4.6 → emerge-0.4.8/emerge}/_emerge/geo/pcb.py +0 -0
  48. {emerge-0.4.6 → emerge-0.4.8/emerge}/_emerge/geo/pcb_tools/calculator.py +0 -0
  49. {emerge-0.4.6 → emerge-0.4.8/emerge}/_emerge/geo/pcb_tools/macro.py +0 -0
  50. {emerge-0.4.6 → emerge-0.4.8/emerge}/_emerge/geo/pmlbox.py +0 -0
  51. {emerge-0.4.6 → emerge-0.4.8/emerge}/_emerge/geo/polybased.py +0 -0
  52. {emerge-0.4.6 → emerge-0.4.8/emerge}/_emerge/geo/shapes.py +0 -0
  53. {emerge-0.4.6 → emerge-0.4.8/emerge}/_emerge/geo/step.py +0 -0
  54. {emerge-0.4.6 → emerge-0.4.8/emerge}/_emerge/geo2d.py +0 -0
  55. {emerge-0.4.6 → emerge-0.4.8/emerge}/_emerge/geometry.py +0 -0
  56. {emerge-0.4.6 → emerge-0.4.8/emerge}/_emerge/howto.py +0 -0
  57. {emerge-0.4.6 → emerge-0.4.8/emerge}/_emerge/logsettings.py +0 -0
  58. {emerge-0.4.6 → emerge-0.4.8/emerge}/_emerge/material.py +0 -0
  59. {emerge-0.4.6 → emerge-0.4.8/emerge}/_emerge/mesh3d.py +0 -0
  60. {emerge-0.4.6 → emerge-0.4.8/emerge}/_emerge/mesher.py +0 -0
  61. {emerge-0.4.6 → emerge-0.4.8/emerge}/_emerge/mth/common_functions.py +0 -0
  62. {emerge-0.4.6 → emerge-0.4.8/emerge}/_emerge/mth/integrals.py +0 -0
  63. {emerge-0.4.6 → emerge-0.4.8/emerge}/_emerge/mth/optimized.py +0 -0
  64. {emerge-0.4.6 → emerge-0.4.8/emerge}/_emerge/periodic.py +0 -0
  65. {emerge-0.4.6 → emerge-0.4.8/emerge}/_emerge/physics/__init__.py +0 -0
  66. {emerge-0.4.6 → emerge-0.4.8/emerge}/_emerge/physics/microwave/__init__.py +0 -0
  67. {emerge-0.4.6 → emerge-0.4.8/emerge}/_emerge/physics/microwave/adaptive_freq.py +0 -0
  68. {emerge-0.4.6 → emerge-0.4.8/emerge}/_emerge/physics/microwave/assembly/assembler.py +0 -0
  69. {emerge-0.4.6 → emerge-0.4.8/emerge}/_emerge/physics/microwave/assembly/generalized_eigen.py +0 -0
  70. {emerge-0.4.6 → emerge-0.4.8/emerge}/_emerge/physics/microwave/assembly/robinbc.py +0 -0
  71. {emerge-0.4.6 → emerge-0.4.8/emerge}/_emerge/physics/microwave/microwave_3d.py +0 -0
  72. {emerge-0.4.6 → emerge-0.4.8/emerge}/_emerge/physics/microwave/microwave_bc.py +0 -0
  73. {emerge-0.4.6 → emerge-0.4.8/emerge}/_emerge/physics/microwave/microwave_data.py +0 -0
  74. {emerge-0.4.6 → emerge-0.4.8/emerge}/_emerge/physics/microwave/periodic.py +0 -0
  75. {emerge-0.4.6 → emerge-0.4.8/emerge}/_emerge/physics/microwave/port_functions.py +0 -0
  76. {emerge-0.4.6 → emerge-0.4.8/emerge}/_emerge/physics/microwave/sc.py +0 -0
  77. {emerge-0.4.6 → emerge-0.4.8/emerge}/_emerge/physics/microwave/simjob.py +0 -0
  78. {emerge-0.4.6 → emerge-0.4.8/emerge}/_emerge/physics/microwave/sparam.py +0 -0
  79. {emerge-0.4.6 → emerge-0.4.8/emerge}/_emerge/physics/microwave/touchstone.py +0 -0
  80. {emerge-0.4.6 → emerge-0.4.8/emerge}/_emerge/plot/__init__.py +0 -0
  81. {emerge-0.4.6 → emerge-0.4.8/emerge}/_emerge/plot/display.py +0 -0
  82. {emerge-0.4.6 → emerge-0.4.8/emerge}/_emerge/plot/grapher.py +0 -0
  83. {emerge-0.4.6 → emerge-0.4.8/emerge}/_emerge/plot/matplotlib/mpldisplay.py +0 -0
  84. {emerge-0.4.6 → emerge-0.4.8/emerge}/_emerge/plot/pyvista/__init__.py +0 -0
  85. {emerge-0.4.6 → emerge-0.4.8/emerge}/_emerge/plot/pyvista/display.py +0 -0
  86. {emerge-0.4.6 → emerge-0.4.8/emerge}/_emerge/plot/pyvista/display_settings.py +0 -0
  87. {emerge-0.4.6 → emerge-0.4.8/emerge}/_emerge/plot/simple_plots.py +0 -0
  88. {emerge-0.4.6 → emerge-0.4.8/emerge}/_emerge/plot.py +0 -0
  89. {emerge-0.4.6 → emerge-0.4.8/emerge}/_emerge/projects/__init__.py +0 -0
  90. {emerge-0.4.6 → emerge-0.4.8/emerge}/_emerge/projects/_gen_base.txt +0 -0
  91. {emerge-0.4.6 → emerge-0.4.8/emerge}/_emerge/projects/_load_base.txt +0 -0
  92. {emerge-0.4.6 → emerge-0.4.8/emerge}/_emerge/projects/generate_project.py +0 -0
  93. {emerge-0.4.6 → emerge-0.4.8/emerge}/_emerge/selection.py +0 -0
  94. {emerge-0.4.6 → emerge-0.4.8/emerge}/_emerge/simmodel.py +0 -0
  95. {emerge-0.4.6 → emerge-0.4.8/emerge}/_emerge/simulation_data.py +0 -0
  96. {emerge-0.4.6 → emerge-0.4.8/emerge}/_emerge/solver.py +0 -0
  97. {emerge-0.4.6 → emerge-0.4.8/emerge}/_emerge/system.py +0 -0
  98. {emerge-0.4.6/_emerge → emerge-0.4.8/emerge}/cli.py +0 -0
  99. {emerge-0.4.6 → emerge-0.4.8}/emerge/pyvista.py +0 -0
@@ -0,0 +1,27 @@
1
+ [tool.bumpversion]
2
+ current_version = "0.4.8"
3
+ parse = "(?P<major>\\d+)\\.(?P<minor>\\d+)\\.(?P<patch>\\d+)"
4
+ serialize = ["{major}.{minor}.{patch}"]
5
+ search = "{current_version}"
6
+ replace = "{new_version}"
7
+ regex = false
8
+ ignore_missing_version = false
9
+ ignore_missing_files = false
10
+ tag = true
11
+ sign_tags= false
12
+ tag_name = "v{new_version}"
13
+ tag_message = "Bump version: {current_version} → {new_version}"
14
+ allow_dirty = false
15
+ commit = true
16
+ message = "Bump version: {current_version} → {new_version}"
17
+ moveable_tags = []
18
+ commit_args = ""
19
+ setup_hooks = []
20
+ pre_commit_hooks = []
21
+ post_commit_hooks = []
22
+
23
+ [[tool.bumpversion.files]]
24
+ filename = "uv.lock"
25
+
26
+ [[tool.bumpversion.files]]
27
+ filename = "pyproject.toml"
@@ -0,0 +1,38 @@
1
+ ---
2
+ name: Bug report
3
+ about: Create a report to help us improve
4
+ title: ''
5
+ labels: ''
6
+ assignees: ''
7
+
8
+ ---
9
+
10
+ **Describe the bug**
11
+ A clear and concise description of what the bug is.
12
+
13
+ **To Reproduce**
14
+ Steps to reproduce the behavior:
15
+ 1. Go to '...'
16
+ 2. Click on '....'
17
+ 3. Scroll down to '....'
18
+ 4. See error
19
+
20
+ **Expected behavior**
21
+ A clear and concise description of what you expected to happen.
22
+
23
+ **Screenshots**
24
+ If applicable, add screenshots to help explain your problem.
25
+
26
+ **Desktop (please complete the following information):**
27
+ - OS: [e.g. iOS]
28
+ - Browser [e.g. chrome, safari]
29
+ - Version [e.g. 22]
30
+
31
+ **Smartphone (please complete the following information):**
32
+ - Device: [e.g. iPhone6]
33
+ - OS: [e.g. iOS8.1]
34
+ - Browser [e.g. stock browser, safari]
35
+ - Version [e.g. 22]
36
+
37
+ **Additional context**
38
+ Add any other context about the problem here.
@@ -0,0 +1,20 @@
1
+ ---
2
+ name: Feature request
3
+ about: Suggest an idea for this project
4
+ title: ''
5
+ labels: ''
6
+ assignees: ''
7
+
8
+ ---
9
+
10
+ **Is your feature request related to a problem? Please describe.**
11
+ A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
12
+
13
+ **Describe the solution you'd like**
14
+ A clear and concise description of what you want to happen.
15
+
16
+ **Describe alternatives you've considered**
17
+ A clear and concise description of any alternative solutions or features you've considered.
18
+
19
+ **Additional context**
20
+ Add any other context or screenshots about the feature request here.
@@ -0,0 +1 @@
1
+ 3.10
@@ -0,0 +1,3 @@
1
+ {
2
+ "python.REPL.enableREPLSmartSend": false
3
+ }
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: emerge
3
- Version: 0.4.6
3
+ Version: 0.4.8
4
4
  Summary: An open source EM FEM simulator in Python
5
5
  Project-URL: Homepage, https://github.com/FennisRobert/EMerge
6
6
  Project-URL: Issues, https://github.com/FennisRobert/EMerge/issues
@@ -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!')
@@ -85,7 +85,7 @@ for I in range(NFILL):
85
85
  VOLUME_COEFF_CACHE = VOLUME_COEFF_CACHE_BASE
86
86
 
87
87
  @njit(types.Tuple((f8[:], f8[:], f8[:], f8))(f8[:], f8[:], f8[:]), cache = True, nogil=True)
88
- def tet_coefficients_bcd(xs: np.ndarray, ys: np.ndarray, zs: np.ndrray) -> tuple[np.ndarray, np.ndarray, np.ndarray, float]:
88
+ def tet_coefficients_bcd(xs: np.ndarray, ys: np.ndarray, zs: np.ndarray) -> tuple[np.ndarray, np.ndarray, np.ndarray, float]:
89
89
  """Computes the a,b,c and d coefficients of a tet barycentric coordinate functions and the volume
90
90
 
91
91
  Args:
@@ -1,4 +1,4 @@
1
- from _emerge.material import Material, AIR, COPPER
1
+ from ._emerge.material import Material, AIR, COPPER
2
2
 
3
3
  C0 = 299792458
4
4
  Z0 = 376.73031366857
@@ -0,0 +1 @@
1
+ from ._emerge.plot.simple_plots import smith, plot_sp, plot, plot_ff, plot_ff_polar
@@ -0,0 +1,111 @@
1
+ import emerge as em
2
+ from emerge.plot import plot_sp, plot_ff
3
+ import numpy as np
4
+
5
+ """ STANDARD GAIN HORN ANTENNA
6
+
7
+ This demo sets up and simulates a rectangular horn antenna in an
8
+ absorbing domain with PML layers. We compute return loss (S11) over a
9
+ 90–110 GHz band and plot the far-field radiation pattern.
10
+
11
+ The dimensions come from this paper:
12
+ https://pure.tue.nl/ws/portalfiles/portal/332971061/Uncertainties_in_the_Estimation_of_the_Gain_of_a_Standard_Gain_Horn_in_the_Frequency_Range_of_90_GHz_to_140_GHz.pdf
13
+ """
14
+
15
+ # --- Units ---------------------------------------------------------------
16
+ mm = 0.001 # meters per millimeter
17
+
18
+ # --- Horn and feed dimensions -------------------------------------------
19
+ wga = 2.01 * mm # waveguide width
20
+ wgb = 1.01 * mm # waveguide height
21
+ WH = 10 * mm # Aperture width
22
+ HH = 7 * mm # Aperture height
23
+ Lhorn = 21 * mm # Horn length
24
+
25
+ # --- Feed and simulation setup ------------------------------------------
26
+ Lfeed = 2 * mm # length of feed waveguide
27
+ th = 1 * mm # PML thickness
28
+ dx = 2 * mm # distance from horn exit to PML start
29
+
30
+ # Create simulation object
31
+ m = em.Simulation3D('HornAntenna', loglevel='DEBUG')
32
+
33
+ # --- Coordinate system for horn geometry -------------------------------
34
+ hornCS = em.CS(em.YAX, em.ZAX, em.XAX)
35
+
36
+ # Feed waveguide as rectangular box (metal)
37
+ feed = em.geo.Box(
38
+ Lfeed, # length along X
39
+ wga/2, # half-width along Y (centered)
40
+ wgb/2, # half-height along Z
41
+ position=(-Lfeed, 0, 0)
42
+ )
43
+
44
+ # --- Horn geometry ------------------------------------------------------
45
+ # Inner horn taper from (D,E) at throat to (B,C) at mouth over length F
46
+ horn_in = em.geo.Horn(
47
+ (wga, wgb), (WH, HH), Lhorn, hornCS,
48
+ )
49
+ # Outer horn (including metal thickness) helps define PML subtraction
50
+ horn_out = em.geo.Horn(
51
+ (wga+2*th, wgb+2*th), (WH+2*th, HH+2*th), Lhorn, hornCS
52
+ )
53
+
54
+ # --- Bounding objects and PML -------------------------------------------
55
+ # Define large intersection box to trim horn geometry
56
+ ibox = em.geo.Box(30*mm, 30*mm, 30*mm)
57
+ horn_in = em.geo.intersect(horn_in, ibox, remove_tool=False)
58
+ horn_out = em.geo.intersect(horn_out, ibox)
59
+
60
+ # Create airbox with PML layers on +X, +Y, +Z faces
61
+ rat = 1.6 # PML extension ratio
62
+ air, *pmls = em.geo.pmlbox(
63
+ 4*mm, # air padding before PML
64
+ rat*WH/2, # half-height in Y
65
+ rat*HH/2, # half-width in Z
66
+ (Lhorn - dx, 0, 0),# PML origin offset along X
67
+ thickness=4*mm,
68
+ N_mesh_layers=4,
69
+ top=True, right=True, back=True
70
+ )
71
+ # Subtract horn volume from airbox so PML does not cover metal
72
+ air2 = em.geo.subtract(air, horn_out)
73
+
74
+ # --- Solver parameters --------------------------------------------------
75
+ m.mw.set_frequency_range(90e9, 110e9, 11) # 90–110 GHz sweep
76
+ m.mw.set_resolution(0.27) # mesh resolution fraction
77
+
78
+ # --- Assemble geometry and mesh -----------------------------------------
79
+ m.generate_mesh()
80
+
81
+ # --- Boundary conditions ------------------------------------------------
82
+ p1 = m.mw.bc.ModalPort(feed.face('left'), 1) # excite TE10 in feed
83
+ PMC = m.mw.bc.PMC(m.select.face.inplane(0, 0, 0, 0, 1, 0)) # perfect magnetic on symmetry
84
+ radiation_boundary = air.outside('front', 'left', 'bottom') # open faces
85
+ abc = m.mw.bc.AbsorbingBoundary(m.select.face.inplane(Lhorn-dx,0,0,1,0,0))
86
+ # View mesh and BC selections
87
+ m.view(selections=[p1.selection, PMC.selection, radiation_boundary])
88
+
89
+ # --- Run frequency-domain solver ----------------------------------------
90
+ data = m.mw.frequency_domain(True, 2, frequency_groups=4)
91
+
92
+ # --- Plot return loss ---------------------------------------------------
93
+ scal = data.scalar.grid
94
+ plot_sp(scal.freq, scal.S(1,1)) # S11 vs frequency
95
+
96
+ # --- Far-field radiation pattern ----------------------------------------
97
+ # Compute E and H on 2D cut for phi=0 plane over -90° to 90°
98
+ ang, E, H = data.field[0].farfield_2d(
99
+ (1, 0, 0), (0, 1, 0), radiation_boundary,
100
+ (-90, 90), syms=['Ez','Hy']
101
+ )
102
+ # Normalize to free-space impedance and convert to dB
103
+
104
+ m.display.add_object(horn_in, opacity=0.1)
105
+ m.display.add_object(air2, opacity=0.1)
106
+ m.display.add_object(feed, opacity=0.1)
107
+ m.display.add_surf(*data.field[0].farfield_3d(radiation_boundary, syms=['Ez','Hy'])\
108
+ .surfplot('normE', True, True, -30, 5*mm, (Lhorn,0,0)), cmap='viridis', symmetrize=False)
109
+ m.display.add_surf(*data.field[0].cutplane(0.5*mm, z=0).scalar('Ez','real'))
110
+ m.display.show()
111
+
@@ -0,0 +1,118 @@
1
+ import emerge as em
2
+ from emerge.plot import plot_sp
3
+ import numpy as np
4
+
5
+ """ LUMPED COMPONENT FILTER DEMO
6
+
7
+ This demo constructs a microstrip filter using discrete lumped inductors and
8
+ capacitors mounted on a PCB. We extract the characteristic impedance, place
9
+ lumped elements in the trace, mesh the structure, and compute S-parameters.
10
+ """
11
+
12
+ # --- Unit definitions -----------------------------------------------------
13
+ mm = 0.001 # meters per millimeter
14
+ pF = 1e-12 # picofarad in farads
15
+ fF = 1e-15 # femtofarad in farads
16
+ pH = 1e-12 # picohenry in henrys
17
+ nH = 1e-9 # nanohenry in henrys
18
+
19
+ # --- Lumped element impedance functions ---------------------------------
20
+ def Lf(L):
21
+ """Return series impedance function for inductor L."""
22
+ return lambda f: 1j * 2 * np.pi * f * L
23
+
24
+ def Cf(C):
25
+ """Return shunt admittance function for capacitor C."""
26
+ return lambda f: 1 / (1j * 2 * np.pi * f * C)
27
+
28
+ # --- PCB and lumped-component parameters ---------------------------------
29
+ pack = '0603' # package footprint for lumped components
30
+ # Create simulation and PCB layouter with substrate thickness and material
31
+ m = em.Simulation3D('LumpedFilter', loglevel='DEBUG')
32
+ th = 0.5 # substrate thickness (meters)
33
+ Hair = 2.0
34
+ pcb = em.geo.PCBLayouter(th, unit=mm, cs=em.GCS,
35
+ material=em.lib.ROGERS_RO4003C, layers=2)
36
+ # Compute 50-ohm microstrip width
37
+ w0 = pcb.calc.z0(50)
38
+
39
+ # Define lengths and lumped element values
40
+ l0 = 2 # straight segment length (mm)
41
+ Lshunt = 35 * nH # first inductor value
42
+ Cshunt = 35 * pF # first capacitor value
43
+ Lseries = 70 * nH # second inductor
44
+ Cseries = 18 * pF # second capacitor
45
+
46
+ # --- Route trace with lumped elements via method chaining ----------------
47
+ # Input matching section
48
+ pcb.new(0, 0, w0, (1, 0)).store('p1').straight(3)\
49
+ .lumped_element(Cf(Cseries), pack).straight(l0)\
50
+ .lumped_element(Lf(Lseries), pack).straight(l0)\
51
+ .split((0, -1)).straight(l0)\
52
+ .lumped_element(Cf(Cshunt), pack).straight(l0 / 2, w0)\
53
+ .via(pcb.z(1), w0 / 6, False).merge()\
54
+ .split((0, 1)).straight(l0)\
55
+ .lumped_element(Lf(Lshunt), pack).straight(l0 / 2, w0)\
56
+ .via(pcb.z(1), w0 / 6, False).merge()\
57
+ .straight(l0).lumped_element(Cf(Cseries), pack).straight(l0)\
58
+ .lumped_element(Lf(Lseries), pack).straight(l0)\
59
+ .straight(3).store('p2')
60
+
61
+ # Retrieve lumped element and via objects
62
+ LEs = pcb.lumped_elements
63
+ vias = pcb.generate_vias(merge=True)
64
+ # Compile trace geometry and determine bounds
65
+ traces = pcb.compile_paths(merge=True)
66
+ pcb.determine_bounds(leftmargin=0, topmargin=1, rightmargin=0, bottommargin=1)
67
+
68
+ # --- Define modal ports and generate environment ------------------------
69
+ mp1 = pcb.modal_port(pcb.load('p1'), Hair)
70
+ mp2 = pcb.modal_port(pcb.load('p2'), Hair)
71
+ diel = pcb.gen_pcb() # substrate dielectric block
72
+ air = pcb.gen_air(Hair) # surrounding air block
73
+
74
+ # Add all geometry to simulation
75
+ m.define_geometry()
76
+
77
+ # --- Solver and mesh settings -------------------------------------------
78
+ m.mw.set_frequency_range(0.05e9, 0.3e9, 51) # 50–300 MHz sweep
79
+ m.mesher.set_boundary_size(traces, 0.5 * mm)
80
+ # Refine mesh around lumped component faces
81
+ for le in LEs:
82
+ m.mesher.set_face_size(le, 0.1 * mm)
83
+ # Domain mesh refinement
84
+ m.mesher.set_domain_size(diel, 1 * mm)
85
+ m.mesher.set_domain_size(air, 3 * mm)
86
+
87
+ # Build mesh and view
88
+ m.generate_mesh()
89
+ m.view()
90
+
91
+ # --- Boundary conditions -----------------------------------------------
92
+ # Define modal (TEM) ports at input and output
93
+ p1 = m.mw.bc.ModalPort(mp1, 1, TEM=True)
94
+ p2 = m.mw.bc.ModalPort(mp2, 2, TEM=True)
95
+ # Add lumped element BCs for each element
96
+ for le in LEs:
97
+ m.mw.bc.LumpedElement(le)
98
+ # Perfect conductor on copper traces and vias
99
+ m.mw.bc.PEC(traces)
100
+ m.mw.bc.PEC(vias.outside())
101
+
102
+ # --- Run frequency-domain simulation ------------------------------------
103
+ data = m.mw.frequency_domain(parallel=True, njobs=4, frequency_groups=8)
104
+
105
+ # --- Post-processing: plot S-parameters ---------------------------------
106
+ f = data.scalar.grid.freq
107
+ S11 = data.scalar.grid.S(1, 1)
108
+ S21 = data.scalar.grid.S(2, 1)
109
+ plot_sp(f, [S11, S21], xunit='MHz', labels=['S11', 'S21'])
110
+
111
+ # --- Visualize field distribution ---------------------------------------
112
+ m.display.add_object(diel, opacity=0.1)
113
+ m.display.add_object(traces, opacity=0.1)
114
+ # Cut-plane of Ez field through substrate center
115
+ cut = data.field.find(freq=2.25e9)\
116
+ .cutplane(0.1 * mm, z=-th/2 * mm)
117
+ m.display.add_surf(*cut.scalar('Ez', 'real'))
118
+ m.display.show()
@@ -0,0 +1,149 @@
1
+ import emerge as em
2
+ import numpy as np
3
+ from emerge.plot import smith, plot_sp
4
+
5
+ """ STEPPED IMPEDANCE FILTER
6
+
7
+ In this demo we will look at how we can construct a stepped impedance filter using the
8
+ PCB Layouter interface in EMerge.
9
+
10
+ """
11
+ # First we will define some constants/variables/parameters for our simulation.
12
+ mm = 0.001
13
+ mil = 0.0254*mm
14
+
15
+ L0, L1, L2, L3 = 400, 660, 660, 660 # The lengths of the sections in mil's
16
+ W0, W1, W2, W3 = 50, 128, 8, 224 # The widths of the sections in mil's
17
+
18
+ th = 62 # The PCB Thickness
19
+ er = 2.2 # The Dielectric constant
20
+
21
+ Hair = 60
22
+
23
+ ## Material definition
24
+
25
+ # We can define the material using the Material class. Just supply the dielectric properties and you are done!
26
+ pcbmat = em.Material(er=2.2, tand=0.00, color="#217627")
27
+
28
+ # We start by creating our simulation object.
29
+
30
+ m = em.Simulation3D('Demo1_SIF', loglevel='DEBUG')
31
+
32
+ # To accomodate PCB routing we make use of the PCBLayouter class. To use it we need to
33
+ # supply it with a thickness, the desired air-box height, the units at which we supply
34
+ # the dimensions and the PCB material.
35
+
36
+ layouter = em.geo.PCBLayouter(th, unit=mil, material=pcbmat, layers=3)
37
+
38
+ # We will route our PCB using the "method chaining" syntax. First we call the .new() method
39
+ # to start a new trace. This will returna StripPath object on which we may call methods that
40
+ # sequentially constructs our stripline trace. In this case, it is sipmly a sequence of straight
41
+ # sections.
42
+
43
+ layouter.new(0,0,W0, (1,0), z=layouter.z(2)).store('p1').straight(L0, W0).straight(L1,W1).straight(L2,W2).straight(L3,W3)\
44
+ .straight(L2,W2).straight(L1,W1).straight(L0,W0).store('p2')
45
+
46
+ # Next we generate a wave port surface to use for our simulation. A wave port can be automatically
47
+ # generated for a given stripoline section. To easily reference it we use the .ref() method to
48
+ # recall the sections we created earlier.
49
+ p1 = layouter.modal_port(layouter.load('p1'), height=0)
50
+ p2 = layouter.modal_port(layouter.load('p2'), height=0)
51
+
52
+ # Finally we compile the stirpline into a polygon. The compile_paths function will return
53
+ # GeoSurface objects that form the polygon. Additionally, we may turn on the Merge feature
54
+ # which will then return a single GeoSurface type object that we can use later.
55
+ polies = layouter.compile_paths(True)
56
+
57
+ # We can manually define blocks for the dielectric or air or let the PCBLayouter do it for us.
58
+ # First we must determine the bounds of our PCB. This function by default will make a PCB
59
+ # just large enough to contain all the coordinates in it (in the XY plane). By adding extra
60
+ # margins we can make sure to add sufficient space next to the trace. Just make sure that there
61
+ # is no margin where the wave ports need to go.
62
+ layouter.determine_bounds(leftmargin=0, topmargin=200, rightmargin=0, bottommargin=200)
63
+
64
+ # We can now generate the PCB and air box. The material assignment is automatic!
65
+
66
+ pcb = layouter.gen_pcb(True, merge=True)
67
+
68
+ # We now pass all the geometries we have created to the .define_geometry() method.
69
+ m.define_geometry()
70
+
71
+ # We set our desired resolution (fraction of the wavelength)
72
+ m.mw.set_resolution(0.08)
73
+
74
+ # And we define our frequency range
75
+ m.mw.set_frequency_range(0.2e9, 8e9, 41)
76
+
77
+ # EMerge also has a convenient interface to improve surface meshing quality.
78
+ # With the set_boundary_size(method) we can define a meshing resolution for the edges of boundaries.
79
+ # This is adviced for small stripline structures.
80
+ # The growth_rate setting allows us to change how fast the mesh size will recover to the original size.
81
+ m.mesher.set_boundary_size(polies, 1*mm, growth_rate=1.2)
82
+ m.mesher.set_face_size(p1, 1*mm)
83
+ m.mesher.set_face_size(p2, 1*mm)
84
+
85
+ # Finally we generate our mesh and view it
86
+ m.generate_mesh()
87
+
88
+ #m.view(use_gmsh=True)
89
+
90
+ # We can now define the modal ports for the in and outputs and set the conductor to PEC.
91
+ port1 = m.mw.bc.ModalPort(p1, 1, TEM=True)
92
+ port2 = m.mw.bc.ModalPort(p2, 2, TEM=True)
93
+ pec = m.mw.bc.PEC(polies)
94
+
95
+ ## OPTIONAL
96
+ # If we want to view the port mode we have to first know it. The modes are computed using a modal analysis.
97
+ # Since the latest version of EMerge. Modal analysis is executed automatically. Here we compute them manually
98
+ # as the mode is only calculated during the frequency domain solution.
99
+ if False:
100
+ # Make sure to set the TEM property to True so that
101
+ # EMerge knows to handle the port mode as a TEM boundary. This also includes the automatic
102
+ # determination of a voltage integration line used for computing the port impedance.
103
+ m.mw.modal_analysis(port1, 1, TEM=True)
104
+ m.mw.modal_analysis(port2, 1, TEM=True)
105
+
106
+ # Finally we import the display class to view the resultant modes
107
+ m.display.add_object(pcb, opacity=0.1)
108
+ m.display.add_object(polies)
109
+ m.display.add_portmode(port1, 21)
110
+ m.display.add_portmode(port2, 21)
111
+ m.display.show()
112
+
113
+ # Finally we execute the frequency domain sweep and compute the Scattering Parameters.
114
+ sol = m.mw.frequency_domain(parallel=True, njobs=4, frequency_groups=8)
115
+
116
+
117
+ # Our "sol" variable is of type MWData (Microwave Data). This contains a set of scalar data
118
+ # like S-parameters and field data like the E/H field. The scalar data is in sol.scalar and the
119
+ # field data in sol.field. Our data is currently a large list of entries in the dataset simply in order
120
+ # at which it is computed. We can structure our data in cases where we do a single frequency sweep
121
+ # or with a structured parameter sweep. In this case we can use the .grid property which will attempt
122
+ # to construct an N-dimensional grid from our results.
123
+
124
+ gritted_data = sol.scalar.grid
125
+
126
+ # The gritted_data is of type MWGlobalNdim which means its an N-dimensional set of data.
127
+ # From this we can simply take all that we need.
128
+
129
+ f = gritted_data.freq
130
+ S11 = gritted_data.S(1,1)
131
+ S21 = gritted_data.S(2,1)
132
+
133
+ # This extracts the actual simulation data.
134
+ plot_sp(f/1e9, [S11, S21], labels=['S11','S21'], dblim=[-40,6], logx=True)
135
+
136
+ # We can also supersample our data by constructing a model using the Vector Fitting algorithm
137
+
138
+ f = np.linspace(0.2e9, 8e9, 2001)
139
+ S11 = gritted_data.model_S(1,1,f)
140
+ S21 = gritted_data.model_S(2,1,f)
141
+
142
+ smith(f,S11)
143
+
144
+ plot_sp(f/1e9, [S11, S21], labels=['S11','S21'], dblim=[-40,6], logx=True)
145
+
146
+ m.display.add_object(pcb, opacity=0.1)
147
+ m.display.add_object(polies, opacity=0.5)
148
+ m.display.add_surf(*sol.field[0].cutplane(1*mm, z=-0.75*th*mil).scalar('Ez','real'))
149
+ m.display.show()
@@ -0,0 +1,130 @@
1
+ import emerge as em
2
+ import numpy as np
3
+ from emerge.plot import plot_sp
4
+
5
+ """ DEMO: COMBLINE FILTER
6
+
7
+ In this demo we will look at the design of a combline filter in EMerge. The filter design was taken from
8
+ the book "Modern RF and Microwave Filter Design" by Protap Pramanick and Prakash Bhartia.
9
+ Some of the dimensions where not clear.
10
+
11
+ In this demo we will look at the Modeler class and how it can help us quickly create complicated geometries.
12
+
13
+ """
14
+
15
+ # First we define some quantities for our simulation.
16
+ mm = 0.001
17
+ mil = 0.0254*mm
18
+
19
+ a = 240*mil
20
+ b = 248*mil
21
+ d1 = 10*mil
22
+ d2 = 10*mil
23
+ dc = 8.5*mil
24
+ lr1 = b-d1
25
+ lr2 = b-d2
26
+ W = 84*mil
27
+ S1 = 117*mil
28
+ S2 = 136*mil
29
+ C1 = b-dc
30
+ h = 74*mil
31
+ wi = 84*mil
32
+ Lbox = 5*W + 2*(S1+S2+wi)
33
+
34
+ x1 = wi+W/2
35
+ x2 = x1 + W + S1
36
+ x3 = x2 + W + S2
37
+ x4 = x3 + W + S2
38
+ x5 = x4 + W + S1
39
+
40
+ rout = 40.5*mil
41
+ rin = 12.5*mil
42
+ lfeed = 100*mil
43
+
44
+ # A usual we start our simulation file
45
+ model = em.Simulation3D('Combline_DEMO', loglevel='DEBUG')
46
+
47
+
48
+ # The filter consists of quarter lamba cylindrical pins inside an airbox.
49
+ # First we create the airbox
50
+ box = em.geo.Box(Lbox, a, b, position=(0,-a/2,0))
51
+
52
+ # Next we create 5 cyllinders using the Modeler class.
53
+ # The modeler class also implements a method chaining interface. In this example we stick to simpler features.
54
+ # The modeler class allows us to create a parameter series using the modeler.series() method. We provid it with quantities.
55
+ # We can do this for multiple at the same time (as you can also see with the position). The modeler class
56
+ # will recognize the multiple quantities and simply create 5 different cyllinders, one for each parameter pair.
57
+ stubs = model.modeler.cyllinder(W/2, model.modeler.series(C1, lr1, lr2, lr1, C1), position=(model.modeler.series(x1, x2, x3, x4, x5), 0, 0), NPoly=10)
58
+
59
+ # Next we create the in and output feed cyllinders for the coaxial cable. We will use the Nsections feature in order to guarantee a better
60
+ # adherence to the boundary.
61
+ feed1out = em.geo.Cyllinder(rout, lfeed, em.CoordinateSystem(em.ZAX, em.YAX, em.XAX, np.array([-lfeed, 0, h])), Nsections=12)
62
+ feed1in = em.geo.Cyllinder(rin, lfeed+wi+W/2, em.CoordinateSystem(em.ZAX, em.YAX, em.XAX, np.array([-lfeed, 0, h])), Nsections=8)
63
+ feed2out = em.geo.Cyllinder(rout, lfeed, em.CoordinateSystem(em.ZAX, em.YAX, em.XAX, np.array([Lbox, 0, h])), Nsections=12)
64
+ feed2in = em.geo.Cyllinder(rin, lfeed+wi+W/2, em.CoordinateSystem(em.ZAX, em.YAX, em.XAX, np.array([Lbox-wi-W/2, 0, h])), Nsections=8)
65
+
66
+ # Next we subtract the stubs and the center conductor from the box and feedline.
67
+ for ro in stubs:
68
+ box = em.geo.subtract(box, ro)
69
+ box = em.geo.subtract(box, feed1in, remove_tool=False)
70
+ box = em.geo.subtract(box, feed2in, remove_tool=False)
71
+ feed1out = em.geo.subtract(feed1out, feed1in)
72
+ feed2out = em.geo.subtract(feed2out, feed2in)
73
+
74
+ # Finally we may define our geometry
75
+ model.define_geometry()
76
+
77
+ model.view()
78
+
79
+ # We define our frequency range and a fine sampling.
80
+ model.mw.set_frequency_range(6e9, 8e9, 21)
81
+
82
+ model.mw.resolution = 0.05
83
+
84
+ # To improve simulation quality we refine the faces at the top of the cylinders.
85
+ for stub in stubs:
86
+ model.mesher.set_boundary_size(box.face('back', tool=stub), 0.0002)
87
+
88
+ # Finally we may create our mesh.
89
+ model.generate_mesh()
90
+
91
+ model.view()
92
+
93
+ # We define our modal ports, assign the boundary condition and execute a modal analysis to solve for the
94
+ # coaxial field mode.
95
+ port1 = model.mw.bc.ModalPort(model.select.face.near(-lfeed, 0, h), 1, TEM=True)
96
+ port2 = model.mw.bc.ModalPort(model.select.face.near(Lbox+lfeed, 0, h), 2, TEM=True)
97
+
98
+ # At last we can compute the frequency domain study
99
+ data = model.mw.frequency_domain(parallel=True)
100
+
101
+ # Next we will use the Vector Fitting algorithm to model our S-parameters with a Rational function
102
+
103
+ fdense = np.linspace(6e9, 9e9, 2001)
104
+
105
+ S11 = data.scalar.grid.model_S(1,1,fdense)
106
+ S21 = data.scalar.grid.model_S(2,1,fdense)
107
+
108
+ plot_sp(fdense/1e9, [S11, S21], labels=['S11','S21'])
109
+
110
+ # We can also plot the field inside. First we create a grid of sample point coordinates
111
+ xs = np.linspace(0, Lbox, 41)
112
+ ys = np.linspace(-a/2, a/2, 11)
113
+ zs = np.linspace(0, b, 15)
114
+
115
+ X, Y, Z = np.meshgrid(xs, ys, zs, indexing='ij')
116
+ X = X.flatten()
117
+ Y = Y.flatten()
118
+ Z = Z.flatten()
119
+
120
+ # The E-field can be interpolated by selecting a desired solution and then interpolating it.
121
+ Ex, Ey, Ez = data.field[3].interpolate(X,Y,Z).E
122
+
123
+ # We can add the objects we want and fields using the shown methods.
124
+ model.display.add_object(box, opacity=0.1, show_edges=True)
125
+ model.display.add_quiver(X,Y,Z, Ex.real, Ey.real, Ez.real)
126
+ model.display.add_object(feed1out, opacity=0.1)
127
+ model.display.add_portmode(port1, 21)
128
+ model.display.add_portmode(port2, 21)
129
+ model.display.add_surf(*data.field[3].cutplane(ds=0.5*mm, y=0).scalar('normE'))
130
+ model.display.show()