API Example pymininec, plot-antenna


This is an example of using the API of pymininec together with the API of plot-antenna. So we're computing an antenna with pymininec and then we display the antenna pattern, voltage standing wave ratio (VSWR), and antenna geometry using plot-antenna. There is already a small API description for plot-antenna, for pymininec we use the main() function in mininec/mininec.py – which contains calls to the mininec API as an example. Note that the API uses features of the currently not-yet-released version of pymininec from github.

Read more…

Water: A Multi-Objective Benchmark Problem


This is a write-up that really should go into the documentation of PGAPack but I was unable to include plotly graphics into a github README.rst file, even using the proxy-site raw.githack.com (which is a page that serves html and javascript files with the correct content type so that it can be displayed in a web browser – github choses to serve these files with content-type text/plain). Github refuses to render an iframe inside a github page.

The "Water Resource Planning" problem [RTS01] is an engineering problem that has been used as a benchmark for multi-objective optimization in the literature including classical papers like the original article on NSGA_II [DPAM02]. The problem consists of seven constraints and a five-dimensional objective function.

I have used this for quite some time to test my NSGA-II implementation in PGAPack. It is in the examples/nsgaii directory and can (after compilation) be called with test-problem index 12:

examples/nsgaii/optimize 12

The output of this program can also be found in the file

test/nsgaii_optimize_12.data

For testing I'm shipping a script called crowdingplot to plot objectives in a multi-objective problem. It is in the same directory as the code for the example.

Read more…

Update on non-dominated sorting measurements


This is an update on a previous post [Sch25] about a new implementation of non-dominated sorting for PGAPack. Non-dominated sorting is a central component of most multi-objective optimization implementations.

In the recent post I did not mention how many generations my test ran. The number of generations in the last and in the following tests was 200. In the last test the code was compiled without optimization. The following measurements were made with -O3 the best optimization gcc offers. I noticed that my implementation of the evaluation function for the DTLZ-1 test [DTLZ05] is quadratic in the number of objectives so I feared this would influence the tests. It turns out it didn't, the overhead even for 20 objectives is still too low to be noticeable.

In addition to the first tests I'm now measuring – in addition to the overall time – the time just for the non-dominated sorting. So in the following we have two types of graphics, the ones where the title is starting with "Overall" include the time of evaluation function and whatever the algorithm is doing for bookkeeping. The ones starting with "Non-dominated" just measure the time for the non-dominated sorting.

Read more…

Non-dominated Sorting for Multi-Objective Optimization


Some of you know that I'm maintaining PGApack, a genetic algorithm package and a corresponding Python wrapper, PGApy. PGApack implements multi-objective optimization using NSGA-II [DPAM02] and NSGA-III [DJ14], [JD14].

Multi-objective optimization means that we have more than one objective (or fitness-) function. Since typically these functions can contradict each other – when a candidate solution becomes better in one objective it might become worse in another objective – we need to establish what better means in the context of multi-objective optimization. An individual \(A\) is strictly better than another individual \(B\) – we say individual \(A\) dominates individual \(B\) – when \(A\) is better or equal to \(B\) in all objectives and strictly better in at least one of them.

Since we do not have a single fitness for individuals, multi-objective algorithms typically establish a ranking of individuals by dominance. Individuals which are not dominated by any other individual get dominance rank 0. Then we remove all rank-0 individuals and all individuals that are then non-dominated get rank 1 etc. This procedure is typically called non-dominated ranking or non-dominated sorting. This rank is then one of the components of the new fitness function.

The NSGA-II paper [DPAM02] implements this by checking each individual in a population against every other individual to see if one dominates the other. Then it removes the current non-dominated individuals one-by-one and establishes ranks as outlined above. This algorithm has a run-time that grows quadratically with the number of individuals to sort. In computer science we write this in the big O notation as \(O(n^2)\) where \(n\) is the number of individuals.

In 2003 Jensen [Jen03] published an algorithm that can perform the dominance-ranking of all individuals with effort \(O\left(n\cdot(\log n)^{m-1}\right)\) where \(m\) is the number of objectives (and \(n\) is the population size as before). Jensen's algorithm was updated 10 years later because he had formulated his algorithm in a way that could not handle individuals with the same evaluation in one objective which he claimed was easy to remedy but was not so easy for others [FGP13]. A year later a slight modification was made to formally prove that the algorithm runtime complexity actually fits the formula above [BS14].

Read more…

Dynamic DNS with Bind Stopped Working


In 2021 I set up a dynamic DNS server using the popular ISC bind DNS server and documented my configuration in a blog article [1].

Recently (around the second half of the year 2024) the DNS updates stopped working. In my configurations I was using the public key method for signing DNS updates also known as SIG(0). This was long supported in bind and is still documented to workin the latest version.

All DNS update requests were suddenly denied with a REFUSED status. Even turning on debugging with the -d 15 option to named I was not able to get any further information, not even when logging to standard output with -g.

It turns out that the public key signatures using SIG(0) have been disabled due to a denial of service attack vector documented in CVE-2024-1975. DNS updates with shared TSIG keys still work. Unfortunately this seemed not documented and no appropriate log messages are seen. I've made a bug report for bind.

As it turns out I would have to look at the documentation for the version 9.18 shipped with Debian stable aka bookworm at the time of this writing: It documents that SIG(0) has been removed due to CVE-2024-1975.

It looks like later versions of bind (e.g. version 9.20.6 and up) again support SIG(0) but with a quota mechanism to prevent a denial of service attack. But for prior versions it seems that the "fix" involves completely disabling SIG(0).

Modeling a Wire Antenna with Insulation


[Update 2024-08-20: Correction of formula for L and equivalent radius]

[Update 2024-09-19: Correction of citation of article by Stearns, Supplement to Antenna Book [5]]

For my antenna modeling code pymininec (which is a re-implementation of the original Mininec code in Basic) I was researching how to model insulated wires. I had asked Roy Lewallen, W7EL, the author of EZNEC what he is using in EZNEC and received a pointer to a paper by J. H. Richmond [1] which seems the first paper to propose an algorithm for modeling insulated wires in an antenna and actually implementing it. The algorithm was implemented by Jerry McCormack in his masters thesis [2] and later incorporated into a contractor report [3] (the contractor report is almost identical with the thesis except for updated Fortran code but has a different author). The original Fortran code is not only listed in the reports but is still made available by Ray L. Cross on his "Antenna Scatterers Analysis Program" (ASAP) page. The subroutine "DSHELL" implements the insulated wire algorithm. With some modifications I was able to run this code today. Many thanks go to Roy, W7EL for sending me this information.

To test my implementation in pymininec against real measured data, I dug up a paper by David Lamensdorf [4] where the effect of insulation were actually measured. Since at the time the insulation could only be modelled as an infinite cylinder, the experiment used insulation that goes beyond the end of the wire until no more changes in the measured parameters were observed.

Experiments with the formulation by Richmond [1] indicated that the resulting impedance matrix is very ill-conditioned: For short wire segments the formulation yields very large absolute values that are added to the main diagonal of the impedance matrix and subtracted from the second diagonals (the matrix is symmetric and the second diagonal is the same for the lower and upper triangle). Since these large values are subtracted during the matrix inversion this leads to Catastrophic Cancellation and results that are dominated by cancellation effects.

So I investigated further and found a paper [5] and a presentation [6] by Steve Stearns, K6OIK which gives distributed inductance and an equivalent-radius formula for modeling insulation. His presentation also has a great history of various approaches to modeling insulation with programs that originally do not support it (like the original NEC-2 and the original Mininec):

\begin{align*} a_e &= a \left(\frac{b}{a}\right)^{\left(1- \frac{1}{\varepsilon_r}\right)} \\ L &= \frac{\mu_0}{2\pi}\left(1-\frac{1}{\varepsilon_r} \right)\log\left(\frac{b}{a}\right) \\ \sigma_e &= \sigma\left(\frac{a}{b}\right)^{2\left(1-\frac{1}{\varepsilon_r}\right)} \\ \end{align*}

where \(a\) is the original radius of the wire, \(b\) is the radius of the wire including insulation, \(\sigma\) is the conductivity of the wire, \(\varepsilon_r\) is the relative dieelectric constant of the insulation, \(a_e\) is the equivalent radius and \(\sigma_e\) is the equivalent conductivity that is used for removing the effect of the changed radius on the computation of the skin effect loading of the wire. The inductance \(L\) is the inductance per length of the insulated wire (or wire segment).

I then searched for a paper that would confirm the formulas by Steve Stearns and found an even older paper by Tai Tsun Wu [7] which has exactly the formula for the equivalent radius \(a_e\) and for the distributed inductance \(L\).

The formula for the equivalent conductivity is just used to cancel the effect of the equivalent radius on the computation of the skin effect load. In a previous blog post [8] I had given the formula for the skin effect load. In that formula, the conductivity \(\sigma\) appears as its reciprocal \(\rho\), the resistivity. The square root of \(\sigma\) is always used as a product with the radius. So we can undo the equivalent radius correction by multiplying \(\sigma\) with the reciprocal of the equivalent radius factor. We get

\begin{align*} \sqrt{\sigma}a &= \sqrt{\sigma_e}a\left(\frac{b}{a}\right)^{\left (1-\frac{1}{\varepsilon_r}\right)} \\ \frac{\sigma_e}{\sigma} &= \frac{1}{\left(\frac{b}{a}\right)^{2\left (1-\frac{1}{\varepsilon_r}\right)}} = \left(\frac{a}{b}\right)^{2\left(1-\frac{1}{\varepsilon_r}\right)} \\ \end{align*}

In pymininec I don't need to correct the equivalent radius for the skin effect computation, I can simply use the original radius there.

Read more…

Plotting Antenna Pattern


I'm the author of an antenna plotting program called plot-antenna. It is written in python and has graphics backends for both, matplotlib and plotly. As of this writing the version available on pypi does not yet support different frequency resolution for pattern and impedance plots. The matplotlib backend is better when generating graphics for use in printed documentation while the plotly backend can generate interactive graphics for the web. The documentation contains some matplotlib examples, so I'm going into more detail here for the plotly backend.

I've recently added support for the "Antenna Scatterers Analysis Program" ASAP, an antenna simulation program written before the Fortran-77 Standard in 1974. It is still interesting today because it uses a formulation for the Method of Moments [1] [2] that is different from both, NEC [3] and Mininec [4]. And it pioneered the simulation of wire insulation when an antenna uses insulated elements.

In the following I'm using a 3-Element Yagi-Uda antenna that is in the Example Section of ASAP. The input file I'm using exports antenna pattern for 280 MHz to 305 MHz in 5 MHz steps. In addition it exports antenna impedance in 1 MHz steps.

Read more…

Skin Effect Load Update


In my last post [1] I was wondering why my implementation of skin effect load in pymininec did not match the results we get from NEC-2. Turns out I was wrong in assuming that the skin effect load is purely resistive, it has a large reactive component.

Note: This article contains a lot of math. You may want to skip to the end and check how the new implementation in pymininec behaves compared to may last article which used only a purely resistive load from the skin effect resistivity.

The english Wikipedia article on skin effect does have the correct formula:

\begin{equation*} \newcommand{\Int}{{\mathrm\scriptscriptstyle int}} \newcommand{\ber}{\mathop{\mathrm{ber}}\nolimits} \newcommand{\bei}{\mathop{\mathrm{bei}}\nolimits} \end{equation*}
\begin{equation*} Z_\Int = \frac{k\rho}{2\pi r}\frac{J_0 (kr)}{J_1 (kr)} \end{equation*}

where

\begin{equation*} k = \sqrt{\frac{-j\omega\mu}{\rho}} \end{equation*}

and \(r\) is the wire radius, \(J_v\) are the Bessel functions of the first kind of order \(v\). \(Z_\Int\) is the impedance per unit length of wire.

NEC-2 uses a different formulation [2] (p.75):

\begin{equation*} Z_i = \frac{j\Delta_i}{a_i} \sqrt{\frac{\omega\mu}{2\pi\sigma}} \left[\frac{\ber (q) + j\bei (q)} {\ber^\prime(q)+ j\bei^\prime(q)}\right] \end{equation*}

with

\begin{equation*} q = (\omega\mu\sigma)^{1/2} a_i \end{equation*}

and \(a_i\) the wire radius, \(\Delta_i\) the length of the wire segment, \(\sigma\) the wire conductivity and \(\ber\) and \(\bei\) the Kelvin functions. In the following we will use \(r\) for the wire radius, not \(a_i\). Note that \(\ber^\prime\) is the derivative of \(\ber\), we can also write \(\ber\) as \(\ber_0\) (order 0) and \(\ber^\prime\) as \(\ber_1\) (order 1) and similar for \(\bei\).

In the following I'll be a little bit sloppy about the usage of the permeability of free space \(\mu_0\) vs. the permeability of the wire \(\mu\), for non-ferromagnetic wires these are really (almost) the same: \(\mu = \mu_r * \mu_0\) with \(\mu_r\approx 1\) for non-ferromagnetic material.

The NEC-2 code, uses a Fortran function ZINT which gets two parameters, the conductivity multiplied by the wavelength \(\lambda\) (parameter SIGL) and the radius divided by the wavelength \(\lambda\) (parameter ROLAM). Apart from an approximation of the Kelvin functions (the term computing the fraction of the Kelvin functions is BR1), ZINT computes:

X = SQRT (TPCMU * SIGL) * ROLAM
ZINT = FJ * SQRT (CMOTP / SIGL) * BR1 / ROLAM

with TPCMU = 2368.705, CMOTP=60.0, and FJ is \(j\). The computed X is passed to the computation of the Kelvin functions resulting in BR1 for the fraction term. Note that the computed ZINT does not include the length, it is equivalent with the Wikipedia definition in unit length. With the reverse-engineered magic numbers

CMOTP \(\approx \frac{\mu_0 c}{2\pi}\)

TPCMU \(\approx 2\pi c\mu_0\)

(\(c\) is the speed of light) we get for X from the code (which we call \(q\) now consistent with the formula from [2]):

\begin{align*} q &= \frac{r}{\lambda}\sqrt{2368.705\sigma\lambda} \\ &= r\sqrt{\frac{2368.705\sigma}{\lambda}} \\ &\approx r\sqrt{\frac{2\pi c\mu_0\sigma}{\lambda}} \\ &= r\sqrt{\frac{2\pi c\mu_0\sigma f}{c}} \\ &= r\sqrt{\frac{2\pi \mu_0\sigma \omega}{2\pi}} \\ &= r\sqrt{\omega\mu_0\sigma} \\ \end{align*}

which matches the definition of \(q\) in [2] (see above).

for \(Z_\Int\) of the Fortran implementation we get:

\begin{align*} Z_\Int &= \frac{j}{r} \sqrt{\frac{\mu_0 c}{2\pi\sigma\lambda}} \left[\frac{\ber_0 (q) + j\bei_0 (q)} {\ber_1(q)+ j\bei_1(q)}\right] \\ &= \frac{j}{r} \sqrt{\frac{\mu_0 c f}{2\pi\sigma c}} \left[\frac{\ber_0 (q) + j\bei_0 (q)} {\ber_1(q)+ j\bei_1(q)}\right] \\ &= \frac{j}{r} \sqrt{\frac{\mu_0 f}{2\pi\sigma}} \left[\frac{\ber_0 (q) + j\bei_0 (q)} {\ber_1(q)+ j\bei_1(q)}\right] \\ &= \frac{j}{r} \sqrt{\frac{\mu_0 \omega}{2\pi 2\pi\sigma}} \left[\frac{\ber_0 (q) + j\bei_0 (q)} {\ber_1(q)+ j\bei_1(q)}\right] \\ &= \frac{j}{2\pi r} \sqrt{\frac{\mu_0 \omega}{\sigma}} \left[\frac{\ber_0 (q) + j\bei_0 (q)} {\ber_1(q)+ j\bei_1(q)}\right] \\ \end{align*}

Which does NOT match the formula for \(Z_i\) from [2] above. It looks like the formula from [2] (p. 75) should have the frequency \(f\) instead of \(\omega\) under the square root. The formula derived from the NEC-2 implementation, however, is consistent with the Wikipedia formula above.

The term before the fraction of Kelvin functions differs by a factor from the Wikipedia formula (I'm leaving out \(\Delta_i\), making it a per unit length formula like the one from Wikipedia), also note that \(\rho=1/\sigma\):

From code:

\begin{equation*} \frac{j}{2\pi r}\sqrt{\frac{\omega\mu}{\sigma}} \end{equation*}

From Wikipedia:

\begin{equation*} \frac{k\rho}{2\pi r} = \sqrt{\frac{-j\omega\mu}{\rho}}\frac{\rho}{2\pi r} = \frac{1}{2\pi r}\sqrt{-j\omega\mu\rho} = \frac{1}{2\pi r}\sqrt{\frac{-j\omega\mu}{\sigma}} \end{equation*}

This is a factor of

\begin{equation*} \frac{\sqrt{-j}}{j} = -\frac{1+j}{\sqrt{2}} \end{equation*}

because \(\sqrt{-j} = \frac{1-j}{\sqrt{2}}\)

For the Kelvin functions we have the following equivalence to Bessel functions [3] (10.61, Kelvin Functions):

\begin{equation*} \ber_v x + j \bei_v x = J_v \left(xe^{3\pi j/4}\right) = e^{v\pi j} J_v \left(xe^{-\pi j / 4}\right) \end{equation*}

With the special cases of order 0 and 1:

\begin{align*} \ber_0 x + j \bei_0 x &= e^{0} J_0 \left(xe^{-\pi j / 4}\right) \\ &= J_0 \left(\frac{1-j}{\sqrt{2}}x\right) \\ \ber_1 x + j \bei_1 x &= e^{j\pi} J_1 \left(xe^{-\pi j / 4}\right) \\ &= -J_1 \left(\frac{1-j}{\sqrt{2}}x\right) \\ \end{align*}

The Wikipedia formula passes \(kr\) to the Bessel functions:

\begin{equation*} kr = r\sqrt{\frac{-j\omega\mu}{\rho}} = r\sqrt{-j\omega\mu\sigma} \end{equation*}

while the NEC-2 passes

\begin{equation*} q = r\sqrt{\omega\mu\sigma} \end{equation*}

which differ by the factor \(\sqrt{-j}\). This means that we pass a factor of \(\sqrt{2}\) too large to the Bessel functions. In turn due to the symmetry and the fact that the functions in the denominator are the derivative of the numerator, the output is too large by a factor of \(\sqrt{2}\) which compensates the factor of the first term.

Read more…

Skin Effect Load and Impedance


I've recently implemented skin effect loading of wires in pymininec, my re-implementation of the Mininec antenna modeling code. The original Mininec code did not have this feature.

To attempt verification of my implementation I chose to follow an ancient publication by R. P. Haviland, W4MB [1] where he, among other things, investigates effect of wire resistance on feed point impedance. He has two graphics where the real and the imaginary part of the feed point impedance are plotted against the length/diameter ratio of the wire of a dipole. He mentions Mininec in the text but reading closely it is not apparent if for the two graphics with the feed point impedance he really used Mininec or something else. I've tried to reproduce his graphs with both PyNEC, a Python wrapper for a C++ implementation of NEC-2 and my pymininec implementation of mininec. The results of Haviland have the same tendencies as the NEC results in the following but do not match closely.

Read more…

Beat


Musician know the effect when tuning instruments: If two instruments are playing not quite the same tune, it sounds like the tune has a periodic variation in volume. The period gets larger the better tuned the two instruments are and stops when they both have the same tune. We call this effect "Beat".

Ham radio operators know the "opposite" of that effect when amplitude-modulating a radio frequency with a sine wave in the audio range: The result are two radio frequencies, one lower by the frequency of the audio sine wave, one higher by that frequency.

Amplitude modulation is simply a multiplication: We multiply the radio wave with the audio. We have

$$m(t) = A(t) \cdot\cos(2\pi f t)$$

Where $t$ is the time, $m(t)$ is the modulated signal, $\cos(2\pi f_r t)$ is the radio frequency signal with frequency $f_r$ and $A(t)$ is the audio signal. For a simple audio sine wave we get:

$$m(t) = \cos(2\pi f_a t)\cdot\cos(2\pi f_r t)$$

with $f_a$ the audio frequency.

Now we want to show that both effects are the same thing. We do not need radio frequencies, we can do the same in the audio spectrum.

From Wikipedia we take one of the trigonometric Product sum identities:

$$\displaystyle\cos\theta \cos\phi = \frac{\cos(\theta-\phi) + \cos(\theta+\phi)}{2}$$

When we apply this to our modulation example, we get:

$$\displaystyle \begin{align} \cos(2\pi f_a t) \cos(2\pi f_r t) & = \frac{\cos(2\pi f_r t-2\pi f_a t) + \cos(2\pi f_r t+2\pi f_a t)}{2} \\ & = \frac{\cos(2\pi(f_r-f_a)t)) + \cos(2\pi(f_r+f_a)t)}{2} \\ \end{align} $$

So multiplying two cosines with different frequencies results in the sum of two cosine functions, one with the frequency of the difference of the original frequencies, one with the sum.

So no matter how we create the modulated signal, by multiplying two signals or adding two signals we get the same result.

Read more…