Antenna simulation driver and optimizer
What? 🔗
This is a way of running the NEC2 antenna simulation
nec2++. You need to install that
yourself. See to it that the pull request
82 is on board, which is
true for versions 1.7.6 and 2.0.0 and later; otherwiese, this won't
work. Finally, see to it that the nec2++ executable can be called,
that is, is reachable via PATH.
What not? 🔗
There is a Python binding for
nec2++. I tried to get that to work, but failed. If that stuff
works for you, you probably don't need this.
So this is a pedestrian approach: Run nec2++ in a separate
process and communicate with that process via stdin/stdout.
Why? 🔗
- Parameter studies. Terrible things happen if you make your antenna shorter and shorter, or lower and lower, or whatever - but what exactly are these terrible things?
- Optimization. Put this project and an appropriate scipy optimization algorithm into a tumbler and stir well. Voila: An optimal antenna! And it is you who gets to say what is optimal.
Both are demonstrated in the showcase.
How? 🔗
Overview 🔗
The run_nec2pp method of the antenna_simulation_driver package is
your entry point. You need to feed it with a string that contains the
content you would usually put into a .nec file and feed to nec2++
from the nec2pp . That run_nec2pp method will call nec2++ and
parse the output into a Python friendly data structure. More on that
return value later.
NEC2 input data 🔗
The problem with the input data: That format for the input string is
neither documented here nor in the context of the nec2pp project.
The NEC software traces its origin to the 1970s, when punched (paper)
cards were commonplace. The original input format for NEC2 software
was defined in terms of such puched cards. Old printed documentation
of the day has been OCR'ed, manually polished, and is now available at
www.nec2.org. In particular, the NEC-2
manual (converted from paper
September 1996) is a good reference.
Today's nec2 implementations still read that stuff, even though they are no longer as picky about column numbers as the old code used to be.
Limits of what this Python package currently reads 🔗
run_nec2pp presently assumes a certain output format. To achieve
that:
- Run the analysis for one frequency only. I tend to use something
like
f"FR 0 1 0 0 {qrg*1e-6:.6f} 0.00\n"(where qrg is in Hz). - Run the analysis driven with a single voltage source. I use
f"EX 0 {tag_nr} {seg_nr} 0 1.0\n", wheretag_nrandseg_nrspecify which segment is to be driven. These indexes are 1-based, not 0-based. - Calculation itself is to be triggered by an
RPcard. I typically use"RP 0 37 144 1003 0.0 0.0 2.5 2.5 0.0 0.0\n"unless I want more detail in far field radiation. A lot of detail can be had with"RP 0 181 721 1003 0.0 0.0 0.5 0.5 0.0 0.0\n", at the expense of considerably slower execution. Typically, both 0° and 90° are wanted asthetavalues, so this may need one value more than one might think. Similarly, both 0° and 360° are wanted forphi.
Summary 🔗
So you put your cards you want nec2++ to process all into one Python
string, and used an RP card to actually start the simulation process
somewhere in there. Run method run_nec2pp of package
antenna_simulation_driver with that string as its only argument.
Limited precision 🔗
On the path from nec2++ to Python, all numbers are printed as
strings with limited precision and then read in. So results as
presented by this software inherit that limited precision.
The output data structure 🔗
The run_nec2pp will run the simulation for you and give you a
Nec2ppOutput object back (defined in package
nec2pp_output_parser). That structure is a big bucket containing
information about the simulation run's result:
@dataclass
class Nec2ppOutput:
frequency_and_wavelength: FrequencyAndWavelength
input_and_impedance: AntennaInputParameters
currents: list[SegmentCurrent]
power_budget: PowerBudget
radiation_pattern: RadiationPattern
average_gain: AverageGain
raw_output: Optional[str]
The following sections describe this in more detail.
FrequencyAndWavelength 🔗
Currently, you have to call run_nec2pp once for each frequency, so
you probably know which frequency you called it with. But that data
is available in case you want to make sure:
@dataclass
class FrequencyAndWavelength:
"""Frequency in Hz, wavelength in m."""
frequency: float
wavelength: float
AntennaInputParameters 🔗
A summary of the antenna's input. The impedance member I use
frequently:
@dataclass
class AntennaInputParameters:
tag: int
seg: int
voltage: complex
current: complex
impedance: complex
admittance: complex
power: float
SegmentCurrent 🔗
If you want to study current distribution on your segments, that information is available here:
@dataclass
class SegmentCurrent:
"""A segment and the current that flows through it."""
seg: int
tag: int
seg_center: SegmentCenter
seg_length: float
current: complex
current_magnitude: float
current_phase: float
Here, SegmentCenter is simply coordinates, but in wavelengths,
not in meter:
@dataclass
class SegmentCenter:
"""Values are in wavelengths, not in meter."""
x: float
y: float
z: float
PowerBudget 🔗
The PowerBudget data is much less useful than one would initially
think:
@dataclass
class PowerBudget:
"""All powers in W, efficiency as a number between 0 and 1."""
input_power: float
radiated_power: float
structure_loss: float
network_loss: float
efficiency: float
The efficiency is almost always very close to 1.0. It includes
ohmic wire loss, but not ground loss. See AverageGain below.
RadiationPattern 🔗
The RadiationPattern gives you many instances of what I call a
RadiationPatternRay. There is one such ray for every direction you
asked to be calculated via your RP card.
@dataclass
class RadiationPattern:
rays: list[RadiationPatternRay]
Here is what the individual ray gives you:
@dataclass
class RadiationPatternRay:
theta: float # ray's vertical angle in degrees from z-axis (straight up)
phi: float # ray's horizontal angle in degrees, measured between x-axis and projection of ray to xy-plane
power_gain_v_db: float
power_gain_h_db: float
power_gain_total: float
polarization_axial_ratio: float
polarization_axial_tilt: float
polarization_sense: PolarizationSense
e_theta_magnitude: float
e_theta_phase_degrees: float
e_phi_magnitude: float
e_phi_phase_degrees: float
In Nec2, theta is the angle, in degrees, measured from straight up,
or, equivalently, measured from the z-axis of the coordinate system.
So theta is 90 if the ray points to the horizon. Secondly, phi is
the angle from the x-axis, counterclockwise, of the projection of the
ray onto the x-y-plane. If you think the x-axis points East and the
y-axis North, then phy values are as follows: 0° is East, 90° is
North, 180° is West and 270° is South.
Of the other members of RadiationPatternRay, I find
power_gain_total the most useful. These values can be used to draw
the usual gain diagrams. However, these numbers are not super
precise. I somewhere found the remark that 0.3 dB deviation is to be
expected. A fairly thin ideal dipole in free space came up with a
maximum gain of 2.06 dB, whereas the expected (correct) value was 2.15
dB.
AverageGain 🔗
To actually know which part of your precious TX power does not heat
the wires or the ground, but is actually radiated (to what direction
whatever), you need to look at AverageGain, more precisely, the
average_power_gain member.
@dataclass
class AverageGain:
average_power_gain: float
solid_angle_used_div_by_pi: float
If your antenna lives in free space, the average power gain and your antenna's efficiency are the same number.
If your antenna is employed above ideal or real ground, the average
power gain would become 2 if the antenna had now loss, as your
antenna's task is now reduced to only illuminate the upper half of
space. Correspondingly, your efficiency now becomes
average_power_gain / 2. The efficiency number thus calculated takes
both wire and ground losses into consideration.
raw_output 🔗
Finally, you can provide an optional second argument True to
run_nec2pp (default value False, can also be supplied as a named
argument capture_output). If this is True, then the raw_output
member of Nec2ppOutput will contain the (stdout) output of
nec2++ as one big string. This can be useful for debugging.
Error handling 🔗
If nec2++ fails, run_nec2pp raises CalledProcessError. As
run_nec2pp does not touch where nec2++'s stderr goes to, you
will probably want to read that to find out what the problem is.
SWR convenient function 🔗
Besides the power horse run_nec2pp, the package
antenna_simulation_driver also contains the convenience function
swr that calculates just that. Its signature is:
def swr(z: complex, z0: complex = 50) -> float:
Showcase 🔗
There is a showcase of how to actually use this stuff to do parameter studies and to rediscover the ZS6KBW multiband antenna. This showcase is publicized via my personal block at https://dj3ei.famsik.de/blog/antennas/antenna_simulation_driver_showcase.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file antenna_simulation_driver-0.2.1.tar.gz.
File metadata
- Download URL: antenna_simulation_driver-0.2.1.tar.gz
- Upload date:
- Size: 17.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.13.5
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
509e0bd42496b668050be9110b5c633c5b5fd92d2181c1bc4644f91b2dccb8d5
|
|
| MD5 |
230d8265081bd1a3088b6d3d58c04456
|
|
| BLAKE2b-256 |
603b2ddbd62bb16f979dda74f4b57cfd408ae74d78766823de0d785a477e6707
|
File details
Details for the file antenna_simulation_driver-0.2.1-py3-none-any.whl.
File metadata
- Download URL: antenna_simulation_driver-0.2.1-py3-none-any.whl
- Upload date:
- Size: 12.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.13.5
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c26dc10db984fac32bd9a61d11f4218d74e8312d58cca32d914a0cbd163dc43f
|
|
| MD5 |
688628acff7e6ea2433167e84b56ecc8
|
|
| BLAKE2b-256 |
3e57a7c6dfd909d821fad98c6c40c72fbff8d50a97aea57f5d81c682f5eb0c74
|