Git Product home page Git Product logo

pyems's Introduction

Table of Contents

  1. About
  2. Installation
  3. Tutorial
  4. Usage
  5. Automatic Mesh Generation Algorithm

About

Pyems is a Python interface to the electromagnetic field solver, OpenEMS. It uses OpenEMS’s own Python interface to hook into CSXCAD and OpenEMS. However, unlike that interface, whose primary purpose is to expose the underlying C++ interface as a Python API, pyems provides high-level functionality intended to facilitate and accelerate the simulation process. Moreover, OpenEMS contains a number of subtle usage traps that can be confusing to new users, and requires that those users understand certain limitations of the FDTD algorithm. Pyems attempts (when possible) to enforce correct usage in these situations and in other cases to make the usage tradeoffs more visible.

To accomplish this goal, pyems provides:

  1. Configurable, automatic mesh generation.
  2. KiCad footprint creation.
  3. Simple port impedance and S-parameter calculations.
  4. A collection of cooperative classes for building commonly-needed microwave structures (many of which are PCB-based).
  5. Functions performing frequently-needed calculations involved with microwave design.
  6. Methods to optimize a simulation structure to achieve a desired result for any arbitrary parameter.
  7. A simple, expressive interface intended to make the simulation process more intuitive.

Although ease of use is one of pyems’s primary goals, another one of its major goals is to not restrict the power of OpenEMS. To this effect, the underlying OpenEMS Python interface is still directly accessible to the user. Indeed, there are cases in which it is necessary to use the OpenEMS interface (e.g. applying transformations and constructing some simple shapes). Additionally, there are some cases where the interface has been designed to allow feature accessibility in a way that can be misused by the casual user. Pyems will always prioritize expressivity over protection against misuse. These cases should be properly documented.

Installation

Pypi installation is planned but not yet available. Pyems must currently be installed manually. It requires a working installation of OpenEMS, including its Python interfaces for CSXCAD and OpenEMS. Pyems also requires the NumPy and SciPy Python libraries. If you’d like to view simulation field dumps (recommended) you must also have a copy of ParaView. All of this software is (of course) open source and free to download and use.

Tutorial

I have not yet written a proper reference documentation for pyems. To help new users get acquainted with it, I’ve written a tutorial presented in this section. This should help users understand the API and what can be done with pyems. Additionally, there are numerous examples present in the examples directory that can be referenced. Finally, I’ve made a significant effort to thoroughly document class initialization and function parameters in docstrings. Please reference them and file bugs when ambiguities exist.

This tutorial will describe how to simulate a directional coupler fabricated on the top layer of a PCB fabricated with OSHPark’s 4-layer process. The simulation file is available in the examples directory. The user will be able to view the simulation structure as well as a movie of the current density. The post-simulation analysis will include the calculated scattering parameters for all 4 ports. Finally, the simulation will generate a PCB footprint that can be imported directly into KiCAD.

import numpy as np
from pyems.structure import Microstrip, PCB, MicrostripCoupler
from pyems.simulation import Simulation
from pyems.pcb import common_pcbs
from pyems.calc import phase_shift_length, microstrip_effective_dielectric
from pyems.utilities import print_table
from pyems.coordinate import Coordinate2, Axis, Box3, Coordinate3
from pyems.mesh import Mesh
from pyems.field_dump import FieldDump, DumpType
from pyems.kicad import write_footprint

freq = np.linspace(0e9, 18e9, 501)
ref_freq = 5.6e9
unit = 1e-3
sim = Simulation(freq=freq, unit=unit, reference_frequency=ref_freq)

This first code block instantiates a Simulation() object. Simulation initializes the underlying OpenEMS objects for the simulation structure and the FDTD algorithm. freq specifies the frequency values to simulate. This simulation uses a granularity of 501 values from DC to 18GHz. The post-simulation analysis results (such as the S-parameters) will be given for these frequency values. Bear in mind that using a smaller granularity results in a longer simulation time. unit specifies the length unit to use in meters. By using 1e-3, we’ve told pyems that all of our subsequent dimensions will be given in mm.

Certain dielectric properties are frequency-dependent. However, OpenEMS requires that all dielectric properties be constant in a simulation. The reference_frequency parameter specifies the frequency used to determine these values. It can be ommitted in which case the center frequency of freq is used.

Simulation also takes a number of other parameters. They have been given reasonable defaults but will sometimes need to be changed. See the code documentation for these parameters. In particular, it will frequently be necessary to specify the boundary_conditions parameter.

pcb_prop = common_pcbs["oshpark4"]
trace_width = 0.38
trace_spacing = 0.2
eeff = microstrip_effective_dielectric(
    pcb_prop.substrate.epsr_at_freq(ref_freq),
    substrate_height=pcb_prop.substrate_thickness(0, unit=unit),
    trace_width=trace_width,
)
coupler_length = phase_shift_length(90, eeff, ref_freq)
print("coupler_length: {:.4f}".format(coupler_length))

pcb_prop is an object that contains details about the OSHPark 4-layer process. It knows the thickness of all metal and dielectric layers as well as the dielectric frequency-dependent electrical properties. Only a few PCB processes are supported at the moment, but more will be added in the future.

eeff is the effective dielectric of the top PCB layer. It correctly accounts for the fact that the microstrip is bounded below by the substrate and above by air.

coupler_length is the length (in our chosen unit, which is mm) required for a signal (specified by the reference frequency) to undergo a quarter-wavelength phase shift. Since this coupler is a backward-wave directional coupler, the quarter wave maximizes the coupling coefficient and bandwidth at our reference frequency.

The effective dielectric equation (and by virtue the coupler length) is approximate, not based on a proper simulation. Although the approximation should be more than adequate for most cases, we could optimize the length later (and calculate a more precise effective dielectric) with the OpenEMS simulation if we wanted.

pcb_len = 2 * coupler_length
pcb_width = 0.5 * pcb_len
pcb = PCB(
    sim=sim,
    pcb_prop=pcb_prop,
    length=pcb_len,
    width=pcb_width,
    layers=range(3),
    omit_copper=[0],
)

PCB creates a PCB object as part of the simulation structure. PCB is our first example of what pyems refers to as a structure, which is a collection of primitives (the OpenEMS terminology for simple shapes with associated electrical properties) and other pyems structures that present a useful abstraction as a single object. In practice, structures allow you to quickly instantiate frequently-needed physical objects while using OpenEMS best-practices. They also make it easy to apply transformations (physical rotations and translations) to these objects.

Structures play well together. For instance, there is a via structure which requires an associated PCB structure. Instead of having to worry about the 3-dimensional position and orientation of the via, you can simply specify its 2-dimensional coordinates on the PCB. The via will then be automatically oriented correctly on the PCB.

The via also serves to illustrate the benefits of structures over the underlying OpenEMS primitives. Instead of having to instantiate a cylinder for the via drill, another cylinder or cylindrical shell for the via plating and then flat cylindrical shells for the each of the pads and antipads, we can simply instantiate a Via object with the desired attributes. Pyems fully supports blind and buried vias too, as well as physically-inaccurate approximations of vias that shorten simulation time.

Let’s return to the PCB object we instantiated above. This is a core structure of many simulations, since many simulations instantiate microwave structures on a PCB. We must tell the PCB object what process we are using (so that it can automatically determine certain dimensional and electrical properties) as well as the simulation object we instantiated at the beginning. Additionally, we must specify the x-dimensional length and y-dimensional width of the PCB. Although our PCB process is a 4-layer process, by building a microstrip directional coupler, we really only care about the first and second metal layers and the substrate layer in-between. This is what the layers parameter does. range(3) specifies that we only want to include layers 0, 1, and 2, where 0 and 2 correspond to the first and second metal layers and 1 corresponds to the top substrate layer. This is an important feature since it leads to shorter simulation times with virtually zero accuracy cost. By default all layers are included. Pyems does not presently support layers other than dielectric and metal layers (such as soldermask or silkscreen layers). These may be added later if desired.

Finally, PCB by default fills all metal layers with a copper pour. This is often useful and obviates the need for the user to do this manually. We can use the omit_copper parameter to specify metal layers where all the metal should be etched away. Although the layers and omit_copper parameters may seem similar, there are a few subtle differences. Firstly, layers requires a Python range object wherease omit_copper requires a list. While it is reasonable for us to include/disclude a copper pour on any metal layer, it doesn’t make sense for us to use construct our PCB from the first and second metal layers and the second substrate layer (omitting the first substrate layer). Secondly, layers considers all layers (metal and dielectric) when considering indices for the layers. By contrast, omit_copper only cares about the metal layers and thus ignores dielectric layers. As a result, the first and second metal layers are indicated by 0 and 2 when passed to layers and by 0 and 1 when passed to omit_copper.

coupler = MicrostripCoupler(
    pcb=pcb,
    position=Coordinate2(0, 0),
    trace_layer=0,
    gnd_layer=1,
    trace_width=trace_width,
    trace_gap=trace_spacing,
    length=coupler_length,
    miter=None,
)

MicrostripCoupler instantiates coupled microstrip lines. It is another example of a pyems structure. It acquires information about the PCB object and simulation via the pcb parameter, since microstrip couplers will always be instantiated on a PCB. position specifies its center position. trace_layer and gnd_layer specify the PCB metal layers of the trace and backing ground plane. trace_width is the width of each microstrip and trace_gap is the perpendicular distance between the inside of each trace. length is the x-dimensional length, which we set to the desired coupler length. The last parameter, miter specifies the amount to miter the corners of ports 3 and 4. By specifying None we’ve chosen an approximate, optimial miter (see the Miter structure for more information). The use of miter here may be changed in the future for something more general, since it is conceivable that a user might not want to miter these corners, or do something else to them like rounding. It is worth mentioning that MicrostripCoupler also takes a transform parameter that we could use to rotate it.

coupler_port_positions = coupler.port_positions()
port0_x = coupler_port_positions[0].x
port0_y = coupler_port_positions[0].y

Microstrip(
    pcb=pcb,
    position=Coordinate2(np.average([port0_x, -pcb_len / 2]), port0_y),
    length=port0_x + pcb_len / 2,
    width=trace_width,
    propagation_axis=Axis("x"),
    port_number=1,
    excite=True,
    ref_impedance=50,
    feed_shift=0.3,
)

Microstrip creates a microstrip port. Microstrip is another structure, but it is also an example of another important concept in pyems: a port. Ports are conceptually identical to the OpenEMS concept (and there is a significant degree of overlap in the implementation) except that they integrate better with the rest of pyems. A port is essentially a point of interface to the outside world. Ports are locations where signal excitations are created and where voltages and currents are measured.

The notion of ports used here is analogous to the notion of ports used by a VNA. For instance (although it is not the case in this simulation) we might have added SMA connectors at each port (pyems provides a structure for this too). Then, if we wanted to measure S₂₁ we’d terminate ports 3 and 4 with matching loads, attach the transmission port of the VNA to port 1 via an SMA cable and the other port of the VNA (assuming a 2-port VNA) to port 2. If the VNA is properly calibrated for the SMA cables, it will measure the signal as “starting” at the SMA connector of port 1 and “ending” at the SMA connector of port 2. Pyems will do exactly the same thing and should yield the same results.

There are a few aspects to the instantiation of Microstrip that indicate this is used as a port. The first (and most obvious) is port_number. As should be evident, this tells the simulation that this microstrip structure acts as port 1. The numbering will be important in the post-simulation analysis when calculating our S-parameters. Next, the excite parameter tells the simulation that we’d like to perform a signal excitation at this port. The excitation is a Gaussian excitation whose frequency range is determined by the Simulation freq parameter used at the beginning of this tutorial. ref_impedance specifies the impedance value to use when calculating the port’s voltage and current values. We could also have omitted this parameter in which case the calculated value of the microstrip’s characteristic impedance would have been used. Typically, this should be set to the desired characteristic impedance as is done here. feed_shift specifies the position of the signal excitation along the port as a fraction of the port length. The feed needs to be placed far enough along the port such that it is not contained within a boundary (see the OpenEMS documentation for boundary conditions). Pyems will notify you if the excitation is placed in a boundary.

The propagation_axis parameter specifies the direction the port faces. Because of the way the FDTD rectilinear grid works, we cannot place the port in any arbitrary orientation. Finally, we can see that the position and length parameters were used to place the port as extending from the lowermost x-position of the PCB to the edge of the MicrostripCoupler structure.

port1_x = coupler_port_positions[1].x
Microstrip(
    pcb=pcb,
    position=Coordinate2(np.average([port1_x, pcb_len / 2]), port0_y),
    length=pcb_len / 2 - port1_x,
    width=trace_width,
    propagation_axis=Axis("x", direction=-1),
    port_number=2,
    excite=False,
    ref_impedance=50,
)

port2_x = coupler_port_positions[2].x
port2_y = coupler_port_positions[2].y
Microstrip(
    pcb=pcb,
    position=Coordinate2(port2_x, np.average([port2_y, -pcb_width / 2])),
    length=port2_y + pcb_width / 2,
    width=trace_width,
    propagation_axis=Axis("y"),
    ref_impedance=50,
    port_number=3,
)

port3_x = coupler_port_positions[3].x
Microstrip(
    pcb=pcb,
    position=Coordinate2(port3_x, np.average([port2_y, -pcb_width / 2])),
    length=port2_y + pcb_width / 2,
    width=trace_width,
    propagation_axis=Axis("y"),
    ref_impedance=50,
    port_number=4,
)

Ports 2, 3 and 4 are instantiated in much the same way as port 1. There are two main differences, however. The first is that ports 3 and 4 face in the y-direction. This rotates the structure and measurement probes by 90 degrees relative to an x-orientation. The other difference is that port 2 faces in the negative x-direction. This ensures that the voltage and current calculations are performed correctly for its orientation.

Mesh(
    sim=sim,
    metal_res=1 / 80,
    nonmetal_res=1 / 40,
    smooth=(1.1, 1.5, 1.5),
    min_lines=5,
    expand_bounds=((0, 0), (0, 0), (10, 40)),
)

At this point we’ve finished the entire physical structure used in the simulation. In other words if we viewed the structure with AppCSXCAD (which we’ll do shortly), it would look like it would if you were holding the PCB in front of you. Additionally, we’ve imbued that structure with all the electrical properties it needs for simulation.

However, OpenEMS’s FDTD algorithm needs to know where in that structure it should be calculating the solutions to Maxwell’s equations at each timestep. This is where the simulation mesh comes in and is, in my opinion, one of the greatest advantages of pyems over OpenEMS’s default Python interface. Traditionally, creating the mesh has been one of the hardest and most cumbersome parts of the OpenEMS simulation process. There are a number of implementation-specific reasons for this. For instance, the FDTD algorithm performs badly when a mesh line is placed at the boundary of a conductor and insulator. Instead, something called the thirds rule should be applied to achieve a more accurate simulation result without simply adding more mesh lines (which would increase the simulation time). Pyems takes care of this and a bunch of other implementation-specific details for you. For instance it ensures a proper smoothness between adjacent mesh line spacings and makes sure that mesh lines work well with voltage and current probes (there are a number of important considerations in this regard that I won’t go into now).

metal_res specifies the maximum spacing between mesh lines inside a metal. It is specified as a fraction of the minimum simulation wavelength, which in turn is determined by the maximum frequency of freq from the beginning of this tutorial. nonmetal_res does the same thing but for non-metal areas such as the substrate and surrounding air. smooth ensures that adjacent spacings are within a multiplicative factor of one another. Each dimension abides by its own smoothness factor, which is why we pass a tuple of 3 elements corresponding to (x, y, z). In this example, we’ve kept the x lines “smoother” than the y or z lines since the signal propagates primarily in the x-direction. The min_lines parameter specifies the minimum number of mesh lines that must be present in one dimension of a primitive. For instance, the width of a microstrip trace (given the resolution we’ve provided) would normally contain fewer than 5 mesh lines. However, if there are too few mesh lines the simulation will give incorrect results, believing that the microstrip structure is a different width than it actually is. Finally, expand_bounds specifies the number of additional lines we’d like outside our simulation structure. This creates an air layer between the structure and the boundary. The parameter is passes as a tuple of 3 tuples each of 2 elements. It signifies

((xmin, xmax), (ymin, ymax), (zmin, zmax))

We can see from our example that we’ve only added an air layer in the z-dimension. We haven’t done this in the x-, or y-dimensions because the ports must terminate in a perfectly-matched layer (PML). This ensures that we don’t get signal reflections at the ports, making our post-simulation analysis more accurate.

FieldDump(
    sim=sim,
    box=Box3(
        Coordinate3(-pcb_len / 2, -pcb_width / 2, 0),
        Coordinate3(pcb_len / 2, pcb_width / 2, 0),
    ),
    dump_type=DumpType.current_density_time,
)

FieldDump adds a non-physical structure to our simulation, which will record and allow us to view the current density at the top PCB metal layer. box specifies the region to record. We have made it 2-dimensional though we could have made it 3-dimensional. dump_type specifies the type of field to record, for which there are a number of possibilities. See DumpType for other options.

write_footprint(coupler, "coupler_20db", "coupler_20db.kicad_mod")

write_footprint writes a KiCAD-compatible footprint relative to the current directory.

sim.run()

Calling the run method of our Simulation object first displays our CSXCAD object with AppCSXCAD (this can be turned off for usage in scripts) and then asks us if we’d like to proceed with the OpenEMS simulation.

At this point you should have an AppCSXCAD window open with the following structure

.img/coupler_csxcad.png

sim.view_field()

view_field() runs ParaView on the recorded field dump. Here’s a GIF of the result

.img/coupler_current_time.gif

print_table(
    data=[
        sim.freq / 1e9,
        np.abs(sim.ports[0].impedance()),
        sim.s_param(1, 1),
        sim.s_param(2, 1),
        sim.s_param(3, 1),
        sim.s_param(4, 1),
    ],
    col_names=["freq", "z0", "s11", "s21", "s31", "s41"],
    prec=[4, 4, 4, 4, 4, 4],
)

print_table is a convenience method to print tabular data in nicely-spaced columns. This displays the calculated port 1 impedance and all S-parameters for each frequency value of the simulation.

If we had plotted this and additionally computed the directivity, we would see

.img/coupler_plot.svg

Usage

This section is very incomplete.

Structures

Transformations

Many structure objects accept optional transformation parameters. They also generally accept position coordinates. The object is first created at the origin, then the transform is applied, finally followed by a translation of the center of the structure to the supplied position. As a result translation transformations should not be needed, although pyems will accept them.

Automatic Mesh Generation Algorithm

This section describes how the automatic mesh generation algorithm works. Although I intend to keep it up to date, since the mesher is still evolving this description may at times lag behind development. If you find an inconsistency, please submit an issue.

In order to generate a mesh from a CSXCAD geometry, pyems starts by getting a list of all physical CSXCAD primitives (i.e., CSXCAD primitives that have an effect on the simulation). Then, for each dimension it extracts a list of locations for mesh lines that must be fixed at those locations. These correspond to zero-dimension structures (e.g., a planar structure created by AddConductingSheet). Next, pyems iterates through the full list of physical primitives and extracts 3 lists of boundary positions, one for each dimension. For example, a boundary position in the x-dimension would correspond to a location where the physical properties of the structure change anywhere at that location. This change could occur in the y-dimension or the z-dimension. Boundary positions in the y-dimension and z-dimension lists are analogous.

Pyems then converts each element of these lists of boundary positions in each dimension to a type that associates a boundary region (consisting of lower and upper bounds) with a CSXCAD property type, which it classifies as metal, nonmetal or air. This is called a BoundedType. To associate the property type it finds the type of the primitive corresponding to the smallest length in that dimension encompassing the bounded region. Where ties exist, the metal type gets priority. There is still at least one issue with this part of the process, which I will fix (e.g., see issue #2).

Pyems then adds peripheral BoundedType’s for simulation air space, records the location of boundaries between a metal, and nonmetal and orders the BoundedType’s by size (smallest first). Then, it iterates through this list of BoundedType’s and generates a list of mesh line locations inside each.

Generating this list of mesh lines in each BoundedType is, of course, where most of the work happens. Pyems starts by finding the mesh line spacing at the lower and upper boundaries of the boundary region. It also determines the maximum spacing in this region according to metal_res and nonmetal_res specified by the user and whether this BoundedType is a metal or not. Then it adjusts the upper and lower line positions if they correspond to metal-nonmetal boundaries, which must satisfy the thirds rule. For instance, if the upper boundary position corresponds to a metal-nonmetal boundary, we would move the upper position 1/3 the mesh spacing inside the boundary. Then pyems computes a geometric series whose constant factor is between 1 and the smoothness factor specified by the user for that dimension and whose distance is equal to the distance of the bounded region. Computing the geometric series uses a Scipy optimization routine and accounts for most of the time spent generating the mesh.

Finally, pyems trims the air mesh to the desired number of cells, smooths the mesh so that the mesh spacing inside the PML is uniform, calls hooks to other parts of pyems that need to know the final mesh location (e.g., probes need to align to the mesh), and generates the actual mesh in the CSXCAD structure. It then performs a number of checks for correctness.

Planned Features

The following set of features is planned, but not currently implemented.

  1. A tolerance analysis that incorporates variation in the input simulation parameters (e.g. prepreg thickness, etching precision, etc.).
  2. Support for independent dielectric properties for each substrate layer. Many PCB processes (especially in microwave contexts) require this. This is not difficult to implement. Please raise an issue if you’d like this.

Textbook References

A number of equations in this code base come from microwave design and theory textbooks. I’ve made an effort to make a comment in the code whenever an equation is taken from one of these textbooks so that users can look up the corresponding theory and to make it easier to find bugs in the code.

Here’s a list of the textbooks referenced:

  1. Pozar refers to “Microwave Engineering” by David Pozar, Fourth Edition.
  2. Wadell refers to “Transmission Line Design Handbook” by Brian Wadell, published 1991.

If you find a reference to a text not mentioned here, please submit a bug report or pull request.

To-Do

via wall should support nonzero dimensions

The via wall otherwise often gets ignored. I believe this is a result of the floating point precision errors.

probe should not hold onto freq

probe get_freq_data and get_time_data

These methods are poorly named. freq_data and time_data are better names. Additionally, they shouldn’t pass back frequency and time values. This should be retreived with other methods. Note that this will require adjustments to port.py too.

rectwaveguideport propagation axis

This should use the Axis object.

port calc requires self._propagation_axis set

self._propagation_axis is not currently required for the port base class. The interface must be changed in some way that is also compatible with the derived classes.

HOLD mesh should support primitive priorities

pyems's People

Contributors

batchdrake avatar biergaizi avatar ferdymercury avatar laberge1 avatar matthuszagh avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

pyems's Issues

precise meaning of PML_x

The openems BC page says the following about PML:

PML_x: Perfectly Matched Layer absorbing boundary condition, using x number of cells

I took this to mean x cells are part of the PML (which I think is correct) but it could be intended to mean x mesh lines are part of the PML. If this is the case I've taken one more mesh line than necessary to be part of the boundary. This should be verified in the forum.

rename polygons to fp_polygons or footprint_polygons?

Several structure classes keep track of conductingsheet polygons so that they can be provided to the kicad footprint exporter later. The name polygon is ambiguous here. It should be made clear that they are specifically for footprint export.

Ground Layer

Hi, can specify to our code that the bottom layer is to the ground ?

Different tapering's type

Hi, i would know if is it possible to create different type of tapering.
You are using linear tapering in structure.py.

Could we modify the trapezoid shape to create morte exotic taper like exponential one ?
Exponential-tapered-microstrip-transmission-line-27

thirds rule is incorrectly adhered to in certain cases

One example of this is the differential microstrip example, where a mesh line is placed 2/3 inside the inside edges of each trace and 1/3 inside the gap between those traces. Instead, the line should be 1/3 inside the trace and 2/3 inside the gap. This occurs because the gap is considered of metal type. The reason for this is that the mesh module finds two structures that contain this y-position: the substrate and the backing copper plane. They are each of identical size and metal structures get priority in a tie.

Tolerance analysis

Pyems should be able to perform a tolerance analysis based on PCB manufacturer tolerances. For instance, dielectric, prepreg thickness and copper etching all have associated tolerance values.

For each uncertainty value, we can generate a range. As part of the analysis, the user must specify some parameter of interest. Then, if we assume that the transfer function of that parameter with respect to the input parameter with an uncertainty is monotonically increasing or decreasing, we only need to test the boundaries of the uncertainty range. Additionally, if we assume that the direction of this monotonicity is not affected by the value of other parameters with uncertainty, then we only need to test 2 simulation inputs: (1) the simulation composed of all bounds that minimize the output parameter and (2) the simulation of all bounds that maximize it.

If the user knows the monotonicity direction for an input parameter, that user should be able to input it. If this direction is not input, pyems must compute it. For each parameter, pyems can compute the monotonicity direction by testing each bound. If we define n to be the number of input parameters with an uncertainty, the worst case requires 2n+2 simulations (2n to test the monotonicity directions and 2 for the final boundary simulations). We should, of course, perform these in parallel (see the sweep method).

As an example, we can imagine performing a tolerance analysis on a simple GCPW impedance simulation using the OSHPark 4-layer process. This consists of a backing ground plane, a dielectric and a top layer with the grounded coplanar waveguide. The parameter uncertainties are the prepreg thickness and the location of each "line" between copper and etched copper on the top layer. This amounts to 5 total uncertainty inputs. So, this analysis would require 12 simulations, which is very feasible. There are a number of other possible uncertainty values such as copper thickness, conductivity, roughness, etc. but these are not provided. Additionally, OpenEMS cannot model surface roughness.

microstrip_sma_transition.py: RuntimeError: PML mesh smoothing is increasing the simulation box size.

I was trying to run the demo microstrip_sma_transition.py, unfortunately it fails.

$ python3 microstrip_sma_transition.py
/usr/lib64/python3.9/site-packages/scipy/optimize/minpack.py:175: RuntimeWarning: The iteration is not making good progress, as measured by the 
  improvement from the last five Jacobian evaluations.
  warnings.warn(msg, RuntimeWarning)
/usr/lib64/python3.9/site-packages/scipy/optimize/minpack.py:175: RuntimeWarning: The iteration is not making good progress, as measured by the 
  improvement from the last ten iterations.
  warnings.warn(msg, RuntimeWarning)
Traceback (most recent call last):
  File "/home/user/code/pyems/examples/microstrip_sma_transition.py", line 216, in <module>
    mesh = Mesh(
  File "/home/user/.local/lib/python3.9/site-packages/pyems-0.1.0-py3.9.egg/pyems/mesh.py", line 594, in __init__
  File "/home/user/.local/lib/python3.9/site-packages/pyems-0.1.0-py3.9.egg/pyems/mesh.py", line 619, in generate_mesh
  File "/home/user/.local/lib/python3.9/site-packages/pyems-0.1.0-py3.9.egg/pyems/mesh.py", line 722, in _smooth_pml_mesh_lines
RuntimeError: PML mesh smoothing is increasing the simulation box size.  This should never happen.  Either structures in your PML are non-uniform in the direction of the PML in which case you'll get an error anyway, or this is a bug in pyems.

Reducing Upper Frequency Range Causes Meshing Failures (RuntimeError: Probe or feed overlaps PML)

I've noticed a peculiar behavior when exploring the example gcpw_blocking_cap.py. By default, the simulation frequency range is:

req = np.arange(0, 18e9, 1e7)

And pyems is able to generate a simulation model correctly. However, I'm only interested in the high-frequency behavior up to 10 GHz, thus I've change it to

req = np.arange(0, 10e9, 1e7)

Now it's unable to generate a model anymore due to PML overlap.

Traceback (most recent call last):
  File "/home/user/code/pyems/examples/gcpw_blocking_cap.py", line 134, in <module>
    mesh = Mesh(
  File "/home/user/.local/lib/python3.9/site-packages/pyems-0.1.0-py3.9.egg/pyems/mesh.py", line 594, in __init__
  File "/home/user/.local/lib/python3.9/site-packages/pyems-0.1.0-py3.9.egg/pyems/mesh.py", line 624, in generate_mesh
  File "/home/user/.local/lib/python3.9/site-packages/pyems-0.1.0-py3.9.egg/pyems/simulation.py", line 221, in post_mesh
  File "/home/user/.local/lib/python3.9/site-packages/pyems-0.1.0-py3.9.egg/pyems/simulation.py", line 229, in _mesh_errors
RuntimeError: Probe or feed overlaps PML. Please fix your simulation. CSX file has been saved so you can view the overlap.

I tried tweaking the parameter feed_shift on the excitation port but it doesn't seem to make any difference.

I guess reducing the frequency causes the meshing algorithm to use a reduced resolution, which affected the correct placement of PML.

Miter example doesn't work

Hi,
I'm experiencing an issue when using the Miter example that come with pyems.

---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-1-f4986749549a> in <module>
     26 )
     27 
---> 28 miter = Miter(
     29     pcb=pcb,
     30     position=Coordinate2(pcb_len / 4, pcb_len / 4),

~/.local/share/virtualenvs/install_7-trBMitfD/lib/python3.8/site-packages/pyems-0.1.0-py3.8.egg/pyems/structure.py in __init__(self, pcb, position, pcb_layer, gnd_layer, trace_width, gap, rotation, miter, transform)
   1987 
   1988         if self.position is not None:
-> 1989             self.construct(self.position)
   1990 
   1991     @property

~/.local/share/virtualenvs/install_7-trBMitfD/lib/python3.8/site-packages/pyems-0.1.0-py3.8.egg/pyems/structure.py in construct(self, position, transform)
   2041         self._position = c2_maybe_tuple(position)
   2042         self._construct_trace()
-> 2043         self._construct_gap()
   2044 
   2045     def _construct_trace(self) -> None:

~/.local/share/virtualenvs/install_7-trBMitfD/lib/python3.8/site-packages/pyems-0.1.0-py3.8.egg/pyems/structure.py in _construct_gap(self)
   2078         )
   2079         construct_polygon(
-> 2080             prop=prop,
   2081             points=self._gap_points(),
   2082             elevation=self.pcb.copper_layer_elevation(self._pcb_layer),

NameError: name 'prop' is not defined

Implementing hyperlynx imports to simulate

I was wondering if there were plans to try and easily implement importing hyperlynx files from openEMS:

https://openems.de/index.php/Tutorial:_Importing_with_hyp2mat.html

I wouldn't mind taking a crack at trying to do it myself but wanted to know if this functionality fits in with the project as you have built it so far.

Right now I am creating PCBs in KiCAD and using the octave script provided to simulate. Ideally I would try and leverage your optimization methods to iterate the layout.

Homogeneity of mesh inside PML

Thorsten states in this forum post that

The mesh inside the PML is always homogenous in pml-direction.

Does this mean that openems treats the mesh lines as if they were equally-spaced, or that they should be equally spaced? In either case, it would probably be beneficial for pyems to enforce this when it creates the mesh, since openems treating mesh lines as being equally-spaced has the potential to violate smoothness.

openems floating point errors for length=0 dimensions

The floating point used by openems can present problems when trying to line up mesh lines with primitives which are of exactly 0 length in at least 1 dimension. In these cases, floating point errors can accumulate and the mesh line will be placed a very small distance away from the primitive it should be coincident with.

Superficially, there seems be a good way of dealing with this. Specifically, keep all coordinates as full-precision floating point internally. Then, right before passing these coordinates to csxcad, round them to some limited precision (e.g. np.round(val, 10)). As long as the precision error is larger than the floating point rounding error by a sufficient margin, all values that should be the same will be the same. The exception where this approach fails is with transformations. Openems handles transformations internally within the C++ codebase and they do not affect GetBoundBox() and other similar coordinate-reporting functions for primitives.

Currently, my method for constructing primitives with transformations is to construct the primitive first at the origin then apply the transformation if there is one and finally translate the primitive (with a transformation) to the final position. This has the obvious downside that it leads to the issue stated above even when no transform has been requested. A better solution would be to only use this method when a transform has actually been requested. Otherwise, all transformations (including the translation) can be avoided and the primitive can be constructed directly at the final location. This gives me full control to round the values as I want.

It will still be permissible for a user to specify a transformation for these 0-length structures, since for instance, a 3D rotation to a planar structure can generate a result that has a nonzero length in every dimension. However, this leaves cases where the transformation will create issues. These transformations should never be needed since these structures can be created directly in the final transformed state. The correct solution, I believe, is to issue errors when such a case is detected. However, this is probably difficult and the temporary situation will be to allow the user to misuse the interface in this way.

mesh option to ignore very small structures

This is useful when your model inadvertently creates tiny structures that would increase the simulation time dramatically. This effect crops up with miter structures, which are used for example in the coupler simulation, shown below.

coupler

In this image the effect isn't terrible, but it could be much worse if the miter was configured in a slightly different way causing the inner corner to have almost the same x-position as the upper right corner. Probably, the best solution to this will be to add a parameter for mesh generation to "ignore" structures below a certain size (given in terms of the minimum simulation wavelength). For these small structures, I'll probably place one mesh line midway between the bounds so the structure is still somewhat registered and leave it at that. This could present problems when two very small structures are adjacent. I can't think of an instance when this would happen, but it could break this solution so it's worth considering.

Unable to import KiCAD files

Hi, I know your program could create .KiCAD files. I'm interesting to import files create with KiCAD to import and analyse them with pyems.
Is it possible to do it ?

Complex shape

Hi, i would know if is it possible to make striplines with complexe shape like the attached picture or if pyems just work with straight striplines.
Screenshot_20210604-161623

Mesh for arbitrary polygons

As a simple example consider a microstrip structure in an L-shape. More complicated examples would be more complicated shapes with arbitrary transformations.

gcpw_optimize_width.py: AttributeError: 'numpy.float64' object has no attribute 'rint'

I was trying to run the demo gcpw_optimize_width.py, unfortunately it fails.

$ python3 gcpw_optimize_width.py 
/usr/lib64/python3.9/site-packages/numpy/core/_asarray.py:83: VisibleDeprecationWarning: Creating an ndarray from ragged nested sequences (which is a list-or-tuple of lists-or-tuples-or ndarrays with different lengths or shapes) is deprecated. If you meant to do this, you must specify 'dtype=object' when creating the ndarray
  return array(a, dtype, copy=False, order=order)
AttributeError: 'numpy.float64' object has no attribute 'rint'

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "/home/user/code/pyems/examples/gcpw_optimize_width.py", line 77, in <module>
    res = minimize(
  File "/usr/lib64/python3.9/site-packages/scipy/optimize/_minimize.py", line 606, in minimize
    return _minimize_neldermead(fun, x0, args, callback, **options)
  File "/usr/lib64/python3.9/site-packages/scipy/optimize/optimize.py", line 689, in _minimize_neldermead
    fsim[k] = func(sim[k])
  File "/usr/lib64/python3.9/site-packages/scipy/optimize/optimize.py", line 427, in function_wrapper
    return function(*(wrapper_args + args))
  File "/home/user/code/pyems/examples/gcpw_optimize_width.py", line 35, in gcpw
    Microstrip(
  File "/home/user/.local/lib/python3.9/site-packages/pyems-0.1.0-py3.9.egg/pyems/structure.py", line 959, in __init__
  File "/home/user/.local/lib/python3.9/site-packages/pyems-0.1.0-py3.9.egg/pyems/structure.py", line 996, in construct
  File "/home/user/.local/lib/python3.9/site-packages/pyems-0.1.0-py3.9.egg/pyems/structure.py", line 1006, in _construct_port
  File "/home/user/.local/lib/python3.9/site-packages/pyems-0.1.0-py3.9.egg/pyems/port.py", line 456, in __init__
  File "/home/user/.local/lib/python3.9/site-packages/pyems-0.1.0-py3.9.egg/pyems/port.py", line 493, in _set_trace
  File "/home/user/.local/lib/python3.9/site-packages/pyems-0.1.0-py3.9.egg/pyems/csxcad.py", line 133, in construct_box
  File "/home/user/.local/lib/python3.9/site-packages/pyems-0.1.0-py3.9.egg/pyems/csxcad.py", line 513, in _add_box
  File "/home/user/.local/lib/python3.9/site-packages/pyems-0.1.0-py3.9.egg/pyems/fp.py", line 33, in fp_nearest
  File "<__array_function__ internals>", line 5, in around
  File "/usr/lib64/python3.9/site-packages/numpy/core/fromnumeric.py", line 3262, in around
    return _wrapfunc(a, 'round', decimals=decimals, out=out)
  File "/usr/lib64/python3.9/site-packages/numpy/core/fromnumeric.py", line 55, in _wrapfunc
    return _wrapit(obj, method, *args, **kwds)
  File "/usr/lib64/python3.9/site-packages/numpy/core/fromnumeric.py", line 44, in _wrapit
    result = getattr(asarray(obj), method)(*args, **kwds)
TypeError: loop of ufunc does not support argument 0 of type numpy.float64 which has no callable rint method

Installation of openems and csxcad python interfaces

Hi,
I'm excited about this project, if it really will allow free PCB FDTD EM simulation!
I don't understand how to install the python dependencies required.
Is this expected to work on Windows, or only in Linux? Or only Ubuntu?
It would be great to specify exactly how to install the needed dependencies (and on what platform), and install pyems, in order to get up and running.
Thanks!

Unable to Apply CSTransform when Creating a Microstrip (TypeError: unsupported operand type(s) for -: 'list' and 'list')

I was trying to simulate a Y-junction and I need a microstrip segment rotated by 45 degrees. Thus, I attempted to use the CSTransform argument in the class Microstrip(). Unfortunately it doesn't seem to work. Is it a bug or am I misusing this parameter? I looked at all the demos but none demonstrated its uses.

Here's the reproducer.

#!/usr/bin/env python3

import numpy as np
from pyems.pcb import common_pcbs
from CSXCAD.CSTransform import CSTransform
from pyems.simulation import Simulation
from pyems.structure import (
    PCB,
    Microstrip,
)
from pyems.coordinate import Axis

unit = 1e-3
freq = np.arange(0, 18e9, 1e7)
sim = Simulation(freq=freq, unit=unit)
pcb_prop = common_pcbs["oshpark4"]
pcb_len = 10
pcb_width = 5

pcb = PCB(
    sim=sim,
    pcb_prop=pcb_prop,
    length=pcb_len,
    width=pcb_width,
    layers=range(3),
    omit_copper=[0],
)

# Create a dummy CSTransform object, without even doing anything.
tr = CSTransform()

microstrip1 = Microstrip(
    pcb=pcb,
    position=(0, 0),
    length=1,
    width=1,
    transform=tr,
    propagation_axis=Axis("x"),
    trace_layer=0,
    gnd_layer=1,
)

Running this demo triggers the following exceptions.

Traceback (most recent call last):
  File "/home/user/code/pyems/examples/experiments/microstrip_transform.py", line 31, in <module>
    microstrip1 = Microstrip(
  File "/home/user/.local/lib/python3.9/site-packages/pyems-0.1.0-py3.9.egg/pyems/structure.py", line 959, in __init__
  File "/home/user/.local/lib/python3.9/site-packages/pyems-0.1.0-py3.9.egg/pyems/structure.py", line 998, in construct
  File "/home/user/.local/lib/python3.9/site-packages/pyems-0.1.0-py3.9.egg/pyems/structure.py", line 1046, in _construct_trace
  File "/home/user/.local/lib/python3.9/site-packages/pyems-0.1.0-py3.9.egg/pyems/csxcad.py", line 144, in construct_box
  File "/home/user/.local/lib/python3.9/site-packages/pyems-0.1.0-py3.9.egg/pyems/coordinate.py", line 623, in origin_start
TypeError: unsupported operand type(s) for -: 'list' and 'list'

Any hints will be greatly appreciated.

Document acronyms

I use (primarily openems) acronyms in the codebase and will probably also do so in the documentation. These should be documented and explained. For instance, it should be clear that PML means "perfectly matched layer" and there should be links what this means.

mesh test cases

Mesh generation seriously needs unit and integration tests since it has complex behavior and it is therefore very easy to unintentionally introduce regressions.

Customizable SMA connector

Structure that generates an SMA edge mount connector and is customizable. For instance, the shapes of the center conductor contact and the top and bottom metal pieces should be customizable. Since SMA edge mounts are laterally symmetrical, these 3 shapes are probably the only thing that need to be customized. This does ignore structures with metal prongs through the PCB, but I'm not sure that has much of a simulation effect anyway.

differential_gcpw.py & differential_gcpw_blocking_cap.py: TypeError: __init__() got an unexpected keyword argument 'via_gap'

I was trying to run the demo differential_gcpw.py & differential_gcpw_blocking_cap.py, unfortunately they fail.

differential_gcpw.py

Traceback (most recent call last):
  File "/home/user/code/pyems/examples/differential_gcpw.py", line 34, in <module>
    DifferentialMicrostrip(
TypeError: __init__() got an unexpected keyword argument 'via_gap'

differential_gcpw_blocking_cap.py

Traceback (most recent call last):
  File "/home/user/code/pyems/examples/gcpw_bypass_cap.py", line 33, in <module>
    Microstrip(
TypeError: __init__() got an unexpected keyword argument 'via_gap'

Write fully standalone XML files

See this comment and the surrounding context. In particular, pyems should add FDTD data to the simulation XML file so that this file can be directly invoked from the openems CLI. This has the benefit (among others) that XML files generated through pyems can be easily used by individuals not using pyems. This makes it easier to recruit help in the forum for issues related to openems rather than pyems's use of it.

It might be better to implement this upstream in openems/csxcad than in pyems. That way, the code won't need to be duplicated in octave/matlab (where it currently resides) and pyems won't have to stay abreast of any upstream openems changes in this regard.

Be able to return complex values from s_param function in Simulation Class

Today, only magnitude can be read out of the s_param function within the Simulation Class, despite setting the "dB" input bool to False. I am sure there are ways around this, but it seems limiting to not allow phase information to be passed out. I propose that by setting the "dB" input to "False" should return the complex value, versus the magnitude/absolute value. This is important for building your own touchstone file (which would also be a nice function to have, but easy enough to build using scikit-rf).

Issue with gcpw_sma_transition.py example

There are at least 2 issues with the gcpw_sma_transition.py example. The first is an incompatible parameter passed to the Microstrip structure. On line 65 of the example, "via_gap" is not a parameter defined. Commenting that line out allows the example to run, but then fails again with a mesh warning. See the output below. Running on Ubuntu 20.04 with Python 3.8.5.

usr/local/lib/python3.8/dist-packages/scipy/optimize/minpack.py:175: RuntimeWarning: The iteration is not making good progress, as measured by the
improvement from the last five Jacobian evaluations.
warnings.warn(msg, RuntimeWarning)
/usr/local/lib/python3.8/dist-packages/scipy/optimize/minpack.py:175: RuntimeWarning: The iteration is not making good progress, as measured by the
improvement from the last ten iterations.
warnings.warn(msg, RuntimeWarning)
/usr/local/lib/python3.8/dist-packages/pyems-0.1.0-py3.8.egg/pyems/mesh.py:219: RuntimeWarning: divide by zero encountered in long_scalars
/usr/local/lib/python3.8/dist-packages/pyems-0.1.0-py3.8.egg/pyems/mesh.py:669: UserWarning: Mesh line at pos 2.8798 for dimension 0 violates smoothness. Smoothness was set to 1.10 but this line creates a spacing with factor 1.13. For convenience the last three lines are: 2.8488, 2.8653 and 2.8798.
/usr/local/lib/python3.8/dist-packages/pyems-0.1.0-py3.8.egg/pyems/mesh.py:669: UserWarning: Mesh line at pos 2.9985 for dimension 0 violates smoothness. Smoothness was set to 1.10 but this line creates a spacing with factor 1.15. For convenience the last three lines are: 2.9672, 2.9817 and 2.9985.
/usr/local/lib/python3.8/dist-packages/pyems-0.1.0-py3.8.egg/pyems/mesh.py:669: UserWarning: Mesh line at pos 4.1464 for dimension 0 violates smoothness. Smoothness was set to 1.10 but this line creates a spacing with factor 1.11. For convenience the last three lines are: 4.0087, 4.0740 and 4.1464.
/usr/local/lib/python3.8/dist-packages/pyems-0.1.0-py3.8.egg/pyems/mesh.py:669: UserWarning: Mesh line at pos 4.7227 for dimension 0 violates smoothness. Smoothness was set to 1.10 but this line creates a spacing with factor 1.13. For convenience the last three lines are: 4.6425, 4.6850 and 4.7227.
/usr/local/lib/python3.8/dist-packages/pyems-0.1.0-py3.8.egg/pyems/mesh.py:669: UserWarning: Mesh line at pos 5.0278 for dimension 0 violates smoothness. Smoothness was set to 1.10 but this line creates a spacing with factor 1.10. For convenience the last three lines are: 4.9486, 4.9863 and 5.0278.
/usr/local/lib/python3.8/dist-packages/pyems-0.1.0-py3.8.egg/pyems/mesh.py:669: UserWarning: Mesh line at pos 5.5726 for dimension 0 violates smoothness. Smoothness was set to 1.10 but this line creates a spacing with factor 2.87. For convenience the last three lines are: 5.3842, 5.4329 and 5.5726.
/usr/local/lib/python3.8/dist-packages/pyems-0.1.0-py3.8.egg/pyems/mesh.py:669: UserWarning: Mesh line at pos 20.2366 for dimension 0 violates smoothness. Smoothness was set to 1.10 but this line creates a spacing with factor 4.21. For convenience the last three lines are: 20.0630, 20.2033 and 20.2366.
/usr/local/lib/python3.8/dist-packages/pyems-0.1.0-py3.8.egg/pyems/mesh.py:669: UserWarning: Mesh line at pos 0.8978 for dimension 1 violates smoothness. Smoothness was set to 1.50 but this line creates a spacing with factor 3.13. For convenience the last three lines are: 0.7155, 0.7596 and 0.8978.
/usr/local/lib/python3.8/dist-packages/pyems-0.1.0-py3.8.egg/pyems/mesh.py:669: UserWarning: Mesh line at pos 2.4183 for dimension 1 violates smoothness. Smoothness was set to 1.50 but this line creates a spacing with factor 7.62. For convenience the last three lines are: 2.2620, 2.4002 and 2.4183.
/usr/local/lib/python3.8/dist-packages/pyems-0.1.0-py3.8.egg/pyems/mesh.py:669: UserWarning: Mesh line at pos -2.1448 for dimension 2 violates smoothness. Smoothness was set to 1.50 but this line creates a spacing with factor 6.69. For convenience the last three lines are: -2.2933, -2.2740 and -2.1448.
/usr/local/lib/python3.8/dist-packages/pyems-0.1.0-py3.8.egg/pyems/mesh.py:669: UserWarning: Mesh line at pos -1.1691 for dimension 2 violates smoothness. Smoothness was set to 1.50 but this line creates a spacing with factor 1.81. For convenience the last three lines are: -1.3696, -1.2404 and -1.1691.
/usr/local/lib/python3.8/dist-packages/pyems-0.1.0-py3.8.egg/pyems/mesh.py:669: UserWarning: Mesh line at pos 1.1786 for dimension 2 violates smoothness. Smoothness was set to 1.50 but this line creates a spacing with factor 6.04. For convenience the last three lines are: 1.0131, 1.0366 and 1.1786.
/usr/local/lib/python3.8/dist-packages/pyems-0.1.0-py3.8.egg/pyems/mesh.py:669: UserWarning: Mesh line at pos 2.5433 for dimension 2 violates smoothness. Smoothness was set to 1.50 but this line creates a spacing with factor 7.77. For convenience the last three lines are: 2.3830, 2.5250 and 2.5433.
Traceback (most recent call last):
File "gcpw_sma_transition.py", line 196, in
mesh = Mesh(
File "/usr/local/lib/python3.8/dist-packages/pyems-0.1.0-py3.8.egg/pyems/mesh.py", line 594, in init
File "/usr/local/lib/python3.8/dist-packages/pyems-0.1.0-py3.8.egg/pyems/mesh.py", line 635, in generate_mesh
File "/usr/local/lib/python3.8/dist-packages/pyems-0.1.0-py3.8.egg/pyems/mesh.py", line 852, in _ensure_pml_structure_uniform
RuntimeError: ymax PML does not contain uniform structure. Offending coordinate is (4.859866322317939, -3.7949790761880777, 5.027818907946389)

[PATCH] feed_impedance attribute is broken (AttributeError: module 'numpy' has no attribute 'is_complex')

I was trying to set the feed_impedance parameter in pyems, unfortunately it triggers a Python exception.

#!/usr/bin/env python3

import numpy as np
from pyems.pcb import common_pcbs
from CSXCAD.CSTransform import CSTransform
from pyems.simulation import Simulation
from pyems.structure import (
    PCB,
    Microstrip,
)
from pyems.mesh import Mesh
from pyems.coordinate import Axis

unit = 1e-3
freq = np.arange(0, 18e9, 1e7)
sim = Simulation(freq=freq, unit=unit)
pcb_prop = common_pcbs["oshpark4"]
pcb_len = 10
pcb_width = 5

pcb = PCB(
    sim=sim,
    pcb_prop=pcb_prop,
    length=pcb_len,
    width=pcb_width,
    layers=range(3),
    omit_copper=[0],
)

microstrip1 = Microstrip(
    pcb=pcb,
    position=(0, 0),
    length=1,
    width=1,
    propagation_axis=Axis("x"),
    trace_layer=0,
    gnd_layer=1,
    port_number=1,
    excite=True,
    feed_impedance=50,
)

mesh = Mesh(
    sim=sim,
    metal_res=1 / 120,
    nonmetal_res=1 / 40,
    smooth=(1.5, 1.5, 1.5),
    min_lines=5,
    expand_bounds=((0, 0), (8, 8), (8, 8)),
)

Running this demo triggers the exceptions below.

Traceback (most recent call last):
  File "/home/user/code/bugfix/pyems/pyems/bug.py", line 43, in <module>
    mesh = Mesh(
  File "/home/user/.local/lib/python3.9/site-packages/pyems-0.1.0-py3.9.egg/pyems/mesh.py", line 594, in __init__
    self.generate_mesh()
  File "/home/user/.local/lib/python3.9/site-packages/pyems-0.1.0-py3.9.egg/pyems/mesh.py", line 624, in generate_mesh
    self.sim.post_mesh()
  File "/home/user/.local/lib/python3.9/site-packages/pyems-0.1.0-py3.9.egg/pyems/simulation.py", line 219, in post_mesh
    self._align_ports_to_mesh()
  File "/home/user/.local/lib/python3.9/site-packages/pyems-0.1.0-py3.9.egg/pyems/simulation.py", line 317, in _align_ports_to_mesh
    [port.snap_to_mesh(mesh=self.mesh) for port in self.ports]
  File "/home/user/.local/lib/python3.9/site-packages/pyems-0.1.0-py3.9.egg/pyems/simulation.py", line 317, in <listcomp>
    [port.snap_to_mesh(mesh=self.mesh) for port in self.ports]
  File "/home/user/.local/lib/python3.9/site-packages/pyems-0.1.0-py3.9.egg/pyems/port.py", line 112, in snap_to_mesh
    self._set_feed(mesh=mesh)
  File "/home/user/.local/lib/python3.9/site-packages/pyems-0.1.0-py3.9.egg/pyems/port.py", line 541, in _set_feed
    feed = Feed(
  File "/home/user/.local/lib/python3.9/site-packages/pyems-0.1.0-py3.9.egg/pyems/feed.py", line 55, in __init__
    self.set_feed()
  File "/home/user/.local/lib/python3.9/site-packages/pyems-0.1.0-py3.9.egg/pyems/feed.py", line 88, in set_feed
    rval, cval, lval = self._impedance_rcl()
  File "/home/user/.local/lib/python3.9/site-packages/pyems-0.1.0-py3.9.egg/pyems/feed.py", line 116, in _impedance_rcl
    if np.is_complex(self.impedance):
  File "/usr/lib64/python3.9/site-packages/numpy/__init__.py", line 214, in __getattr__
    raise AttributeError("module {!r} has no attribute "
AttributeError: module 'numpy' has no attribute 'is_complex'

A quick look at the code suggests is_complex is a typo. But after correcting this typo, it still triggers another exception.

/home/user/.local/lib/python3.9/site-packages/pyems-0.1.0-py3.9.egg/pyems/structure.py:1111: UserWarning: Reference impedance not set for port 1
Traceback (most recent call last):
  File "/home/user/code/bugfix/pyems/pyems/bug.py", line 43, in <module>
    mesh = Mesh(
  File "/home/user/.local/lib/python3.9/site-packages/pyems-0.1.0-py3.9.egg/pyems/mesh.py", line 594, in __init__
    self.generate_mesh()
  File "/home/user/.local/lib/python3.9/site-packages/pyems-0.1.0-py3.9.egg/pyems/mesh.py", line 624, in generate_mesh
    self.sim.post_mesh()
  File "/home/user/.local/lib/python3.9/site-packages/pyems-0.1.0-py3.9.egg/pyems/simulation.py", line 219, in post_mesh
    self._align_ports_to_mesh()
  File "/home/user/.local/lib/python3.9/site-packages/pyems-0.1.0-py3.9.egg/pyems/simulation.py", line 317, in _align_ports_to_mesh
    [port.snap_to_mesh(mesh=self.mesh) for port in self.ports]
  File "/home/user/.local/lib/python3.9/site-packages/pyems-0.1.0-py3.9.egg/pyems/simulation.py", line 317, in <listcomp>
    [port.snap_to_mesh(mesh=self.mesh) for port in self.ports]
  File "/home/user/.local/lib/python3.9/site-packages/pyems-0.1.0-py3.9.egg/pyems/port.py", line 112, in snap_to_mesh
    self._set_feed(mesh=mesh)
  File "/home/user/.local/lib/python3.9/site-packages/pyems-0.1.0-py3.9.egg/pyems/port.py", line 541, in _set_feed
    feed = Feed(
  File "/home/user/.local/lib/python3.9/site-packages/pyems-0.1.0-py3.9.egg/pyems/feed.py", line 55, in __init__
    self.set_feed()
  File "/home/user/.local/lib/python3.9/site-packages/pyems-0.1.0-py3.9.egg/pyems/feed.py", line 98, in set_feed
    prop=res, box=self.box, priority=priorities["component"],
NameError: name 'priorities' is not defined

This problem appears to be caused by a missing import in feed.py.

After applying this patch, both problems are fixed, though I can't be sure whether it's fully functional either...

diff --git a/pyems/feed.py b/pyems/feed.py
index 196a857..5b370e2 100644
--- a/pyems/feed.py
+++ b/pyems/feed.py
@@ -4,6 +4,7 @@ import numpy as np
 from pyems.simulation import Simulation
 from pyems.mesh import Mesh
 from pyems.utilities import max_priority
+from pyems.priority import priorities
 from pyems.coordinate import Box3, box_overlap
 from pyems.csxcad import construct_box
 
@@ -113,7 +114,7 @@ class Feed:
     def _impedance_rcl(self) -> Tuple[float, float, float]:
         """
         """
-        if np.is_complex(self.impedance):
+        if np.iscomplex(self.impedance):
             warn("Only feed resistances are currently supported.")
 
         return (np.real(self.impedance), 0, 0)

coax.py: UnboundLocalError: local variable 'start' referenced before assignment

I was trying to run the demo coax.py, unfortunately it fails.

Traceback (most recent call last):
  File "/home/user/code/pyems/examples/coax.py", line 34, in <module>
    Coax(
  File "/home/user/.local/lib/python3.9/site-packages/pyems-0.1.0-py3.9.egg/pyems/structure.py", line 2653, in __init__
  File "/home/user/.local/lib/python3.9/site-packages/pyems-0.1.0-py3.9.egg/pyems/structure.py", line 2663, in construct
  File "/home/user/.local/lib/python3.9/site-packages/pyems-0.1.0-py3.9.egg/pyems/structure.py", line 2671, in _construct_core
  File "/home/user/.local/lib/python3.9/site-packages/pyems-0.1.0-py3.9.egg/pyems/structure.py", line 2688, in _construct_port_core
  File "/home/user/.local/lib/python3.9/site-packages/pyems-0.1.0-py3.9.egg/pyems/port.py", line 1671, in __init__
  File "/home/user/.local/lib/python3.9/site-packages/pyems-0.1.0-py3.9.egg/pyems/port.py", line 1716, in _set_core
UnboundLocalError: local variable 'start' referenced before assignment

set property colors

Substrate should be a green color to match the typical soldermask color, conducting sheet should be a gold color to match the typical ENIG finish color and solid metal should either be a copper color or silver (probably silver).

validate smoothness after mesh generation

Pyems does not currently verify the user-specified mesh smoothness factor. Although the mesh is created to adhere to this, it's feasible that a bug in the mesh generation could create a mesh that violates smoothness. It's probably a good idea to add a function to verify smoothness after mesh generation. Additionally, there should be another function to fix smoothness if violated. This second part is more difficult.

Taper issue

Hi,
I'm experiencing an issue when using the Taper module.
I need to have a tapering between two microstrip with different width.

import numpy as np
from pyems.structure import Microstrip, PCB, MicrostripCoupler
from pyems.simulation import Simulation
from pyems.pcb import common_pcbs
from pyems.calc import phase_shift_length, microstrip_effective_dielectric
from pyems.utilities import print_table, mil_to_mm
from pyems.coordinate import Box2, Box3, Axis, Coordinate2, Coordinate3
from pyems.mesh import Mesh
from pyems.field_dump import FieldDump, DumpType
from pyems.kicad import write_footprint
from pyems.structure import (
    PCB,
    Microstrip,
    common_smd_passives,
    SMDPassive,
    Taper,
    ViaWall,
)
import time
import os
import glob






#The following part is useless regarding my issue, it is just an help to create new folder for simulation

filename="gcpw_taper_v1.1"
timestr= time.strftime("_%Y%m%d_%H%M%S")
sim_dir= filename + timestr
#print(sim_dir)
print("Voulez-vous recalculer la simulation\n Y/N")
answer = input().lower()
print('sans casse : ', answer)
if answer == "y" :
    if not os.path.exists(sim_dir):
      os.makedirs(sim_dir)
    calc_only=False
elif answer == "n" :
    calc_only=True
    print("la réponse est non")
    path ="./"
    only_dir = [ name for name in os.listdir(path) if os.path.isdir(os.path.join(path, name)) ]
    only_dir.sort(key=os.path.getmtime)
    only_dir.reverse()
    list_del = [] # on crée ici une liste pour pouvoir supprimer en une seule fois tous les éléments différents de filename* de la liste only_dir
    for index, i in enumerate(only_dir) :
      if i.startswith(filename)==False:
        list_del.append(index) 
      else : 
        print(index, i)       
    list_del.reverse() # on inverse l'ordre de la liste pour avoir les index dans l'ordre décroissant, sinon il y aura des problèmes d'indentation lors de la suppression
    for i in list_del :
      #print(i,"\n")
      only_dir.pop(int(i))
    print("Quelle simulation voulez-vous lancer ?\n")
    choix=int (input())
    sim_dir=only_dir[choix]

print ("Le programme va chercher la simulation dans",sim_dir)






#Here is the usefull code

unit = 1e-3
freq = np.arange(0, 18e9, 1e7)
# freq = np.linspace(4e9, 8e9, 501)
sim = Simulation(freq=freq, unit=unit, end_criteria=1e-2, sim_dir=sim_dir, calc_only=calc_only)
pcb_prop = common_pcbs["oshpark4"]
pcb_len = 10
pcb_width = 5
trace_width = 0.34
gap = mil_to_mm(6)
via_gap = 0.4
z0_ref = 50

cap_dim = common_smd_passives["0402C"]
cap_dim.set_unit(unit)
pad_length = 0.6
pad_width = cap_dim.width

pcb = PCB(
    sim=sim,
    pcb_prop=pcb_prop,
    length=pcb_len,
    width=pcb_width,
    layers=range(3),
    omit_copper=[0],
)

Microstrip1= Microstrip(
    pcb=pcb,
    position=Coordinate2(-pcb_len/4, 0),
    length= pcb_len/8,
    width=trace_width,
    propagation_axis=Axis("x"),
    port_number=1,
    excite=True,
    ref_impedance=50,
    feed_shift=0.3,
)

Microstrip2= Microstrip(
    pcb=pcb,
    position=Coordinate2(pcb_len/4, 0),
    length= pcb_len/8,
    width=trace_width*2,
    propagation_axis=Axis("x"),
    port_number=2,
    excite=False,
    ref_impedance=50,
    feed_shift=0.3,
)

taper = Taper(
    pcb=pcb,
    position=Coordinate2(0, 0),
    pcb_layer=0,
    width1=trace_width/2,
    width2=trace_width/2,
    length=pcb_len/2,
    gap=gap,
    transform=None,
)

FieldDump(
    sim=sim,
    box=Box3(
        Coordinate3(-pcb_len / 2, -pcb_width / 2, 0),
        Coordinate3(pcb_len / 2, pcb_width / 2, 0),
    ),
    dump_type=DumpType.current_density_time,
)

mesh = Mesh(
    sim=sim,
    metal_res=1 / 120,
    nonmetal_res=1 / 40,
    smooth=(1.5, 1.5, 1.5),
    min_lines=5,
    expand_bounds=((8, 8), (8, 8), (8, 8)),
)
sim.run()
sim.view_field()

After running this i have the join issue
issue.md

It's running fine if i put
position=None, instead of
position=Coordinate2(0, 0),

Have you any suggestions to help me ?

Limit threads in simulation

Is it possible to limit the number of threads through your interface? The python openems interface has this functionality in the simulation "run" call. Without it, my simulation runs painfully slow. Generally, oems recommends running on two threads anyway so perhaps it would be useful to set this as the default?

http://www.openems.de/forum/viewtopic.php?f=3&t=137

ModuleNotFoundError: No module named 'CSXCAD' (Ubuntu 20.04.4)

I have built OpenEMS successfully, and everything works fine from Octave on Ubuntu 20.04.4. However, when I try cloning:

git clone https://github.com/matthuszagh/pyems

and then running the horn_antenna.py example:

cd pyems/examples
cp horn_antenna.py .. # Moving one dir back so it identifies the pyems directory
cd ..
python3 horn_antenna.py

I get this error:

Traceback (most recent call last):
  File "horn_antenna.py", line 6, in <module>
    from pyems.utilities import print_table
  File "/home/apostolos/Desktop/oems_tests/pyems/pyems/utilities.py", line 5, in <module>
    from CSXCAD.CSXCAD import ContinuousStructure
ModuleNotFoundError: No module named 'CSXCAD'

Any ideas if I should try to install something in a different manner?

Thanks in advance!

PCB Properties

Hi, i would know if is it possible to use different pcb properties than oshpark4 like rf4 for example ?

merge adjacent bounding boxes for mesh when they are coincident and precisely the same size

The coupler example is a good demonstration of the suboptimal behavior of the mesh generation in this case. If you look at the coupler mesh, you will notice that there is a higher mesh density near the inside corners of the miters. The reason for this is that the small miter overhang gets treated as its own bounding box structure even though it is adjacent to a trace with the exact same width and the same y and z positions. The current behavior is desired in most cases, and would be here if the trace and miter did not exactly match. However, because they do match, this mesh density is unnecessary. Because we now handle floating point well (see this commit), we should be able to do this fine.

Run without user confirmation

Hi, i would run consecutively some simulation. Can i run the simulation without running Appcsxcad and without asking to the user : Continue simulation (y/n)?

option to customize color scheme?

The current color scheme looks nice and is realistic. However, it is hard to override. There should potentially be an option for the user to customize the color scheme or revert to openems's default behavior of a more or less random selection.

Various comments & repports

Hi! I have few little things to report from a first try, so I just open one issue.


I took a look at field_dump.py and I saw the dumps always use the Et_ prefix, disregarding the dump type, while it should be reserved for electric field in time domain.


I find the names used for PML materials, appearing in AppCSXCAD, ambigous. Here names are like PML_i, i being an index identifying each material. In the OpenEMS terminology, the same form is used PML_n, n being the number of cells compounding the PML. So I suggest you to use something like PML_n_i instead.


There is no margin between the structure edges and the boundaries, so when using a PML boundary condition, a part of the structure is in the PML, that is not meant to happen according to the OpenEMS documentation. (This problem does not exist with MUR and PEC conditions.)

pml_off
pml_on


Otherwise I'd like to say I think you go in the right direction to ease the use of OpenEMS and to create what will become its "next generation interface". The automatic mesher you are working on is actually the worst lack of OpenEMS, an optimizer would also be very nice.

I develop Qucs-RFlayout that currently export OpenEMS Octave scripts and I am considering using Python/pyems in future.

Plus, I want to provide a kind of generic script (through a script generator library?), relevant for most of the simulations (I focus on PCB things), so I started working on a script CLI and on a set of basic postprocessing functions (plot S parameters, plot feed point impedance, plot far field radiation, etc.). In fact I would like to provide some basics to somewhat2openems converters developers, to in the end harmonize scripts interfaces from the user side. I think this idea and your project could converge.

Finally, with some other community members, we started the Open-RFlab project to improve the free & open source RF design ecosystem. If you are interested in joining the discussion circle, you are totally welcome. :)

Installation instructions

Hey! Your tool looks great, but not being used to python, I can't manage to install it. Here is what I have done. (Ubuntu 18.04)

cd /usr/local/src/
sudo git clone https://github.com/thliebig/openEMS-Project --recurse-submodules
sudo git clone https://github.com/matthuszagh/pyems

cd /usr/local/src/openEMS-Project/
sudo ./update_openEMS.sh /usr/local --with-hyp2mat --with-CTB --with-MPI

cd /usr/local/src/openEMS-Project/CSXCAD/python/
sudo python3 setup.py install

cd /usr/local/src/openEMS-Project/openEMS/python/
sudo python3 setup.py install

cd /usr/local/src/pyems/
sudo python3 setup.py install

So if I run an example script, I got this error :

pyems/examples/ $
	./microstrip.py

		Traceback (most recent call last):
		  File "./microstrip.py", line 4, in <module>
			from pyems.structure import PCB, Microstrip
		  File "/usr/local/lib/python2.7/dist-packages/pyems-0.1.0-py2.7.egg/pyems/structure.py", line 25
			prop: CSProperties,
				^
		SyntaxError: invalid syntax

So I changed the first line from #!/usr/bin/env python to #!/usr/bin/env python3, and then :

pyems/examples/ $
	./microstrip.py

		Traceback (most recent call last):
		  File "./microstrip.py", line 4, in <module>
			from pyems.structure import PCB, Microstrip
		  File "/usr/local/lib/python3.6/dist-packages/pyems-0.1.0-py3.6.egg/pyems/structure.py", line 11, in <module>
		  File "/usr/local/lib/python3.6/dist-packages/CSXCAD/__init__.py", line 7, in <module>
			from CSXCAD.CSXCAD import ContinuousStructure
		ImportError: libCSXCAD.so.0: cannot open shared object file: No such file or directory

	locate libCSXCAD.so.0
		/usr/local/lib/libCSXCAD.so.0
		/usr/local/lib/libCSXCAD.so.0.6.2

The library is present on my system, I probably missed something. Do you have an idea?

Thank you in advance. :)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.