Skip to main content

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 showcases.

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", where tag_nr and seg_nr specify which segment is to be driven. These indexes are 1-based, not 0-based.
  • Calculation itself is to be triggered by an RP card. 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 as theta values, so this may need one value more than one might think. Similarly, both 0° and 360° are wanted for phi.

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

antenna_simulation_driver-0.2.0.tar.gz (17.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

antenna_simulation_driver-0.2.0-py3-none-any.whl (12.9 kB view details)

Uploaded Python 3

File details

Details for the file antenna_simulation_driver-0.2.0.tar.gz.

File metadata

File hashes

Hashes for antenna_simulation_driver-0.2.0.tar.gz
Algorithm Hash digest
SHA256 9850ba9a2a104b69f10db9ba550c225befc9e52ad73c7160059b4e206a9d492a
MD5 ba879c1415793110e39d176fa8b5ccc2
BLAKE2b-256 2ba8127e89e446cff14b72aefb95b906d6d7cd760ae21c2b2e1aeed91d201701

See more details on using hashes here.

File details

Details for the file antenna_simulation_driver-0.2.0-py3-none-any.whl.

File metadata

File hashes

Hashes for antenna_simulation_driver-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 e11a4999698dd8cd915e14a641ee9914779aee8b50015489e0f897bef2ab75d3
MD5 bd97e0ecc80f6e6437efad9cc0219256
BLAKE2b-256 9e4a65327b46cd0fb59f3cb408624ab56a3e2899b0bcbb44ff41db0f30803831

See more details on using hashes here.

Release history Release notifications | RSS feed

0.2.1

2 files

This release

0.2.0 This release

2 files

0.1.3

2 files

0.1.2

2 files

0.1.1

2 files

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page