antennaknobs 0.5.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- antennaknobs-0.5.1/LICENSE +28 -0
- antennaknobs-0.5.1/PKG-INFO +515 -0
- antennaknobs-0.5.1/README.md +484 -0
- antennaknobs-0.5.1/pyproject.toml +99 -0
- antennaknobs-0.5.1/setup.cfg +4 -0
- antennaknobs-0.5.1/setup.py +26 -0
- antennaknobs-0.5.1/src/antennaknobs/__init__.py +53 -0
- antennaknobs-0.5.1/src/antennaknobs/__main__.py +3 -0
- antennaknobs-0.5.1/src/antennaknobs/builder.py +457 -0
- antennaknobs-0.5.1/src/antennaknobs/cli.py +666 -0
- antennaknobs-0.5.1/src/antennaknobs/core.py +8 -0
- antennaknobs-0.5.1/src/antennaknobs/designs/__init__.py +8 -0
- antennaknobs-0.5.1/src/antennaknobs/designs/arrays/__init__.py +1 -0
- antennaknobs-0.5.1/src/antennaknobs/designs/arrays/bowtiearray.py +41 -0
- antennaknobs-0.5.1/src/antennaknobs/designs/arrays/bowtiearray1x2.py +20 -0
- antennaknobs-0.5.1/src/antennaknobs/designs/arrays/bowtiearray2x4.py +56 -0
- antennaknobs-0.5.1/src/antennaknobs/designs/arrays/delta_looparray.py +61 -0
- antennaknobs-0.5.1/src/antennaknobs/designs/arrays/delta_looparray_1x4.py +40 -0
- antennaknobs-0.5.1/src/antennaknobs/designs/arrays/delta_looparray_1x4_grouped.py +42 -0
- antennaknobs-0.5.1/src/antennaknobs/designs/arrays/delta_looparray_2x2.py +41 -0
- antennaknobs-0.5.1/src/antennaknobs/designs/arrays/delta_looparray_network.py +97 -0
- antennaknobs-0.5.1/src/antennaknobs/designs/arrays/delta_looparray_with_tls.py +106 -0
- antennaknobs-0.5.1/src/antennaknobs/designs/arrays/folded_invveearray.py +40 -0
- antennaknobs-0.5.1/src/antennaknobs/designs/arrays/hentenna_array.py +22 -0
- antennaknobs-0.5.1/src/antennaknobs/designs/arrays/hourglass_array.py +22 -0
- antennaknobs-0.5.1/src/antennaknobs/designs/arrays/invveearray.py +66 -0
- antennaknobs-0.5.1/src/antennaknobs/designs/arrays/moxonarray.py +57 -0
- antennaknobs-0.5.1/src/antennaknobs/designs/arrays/yagiarray.py +48 -0
- antennaknobs-0.5.1/src/antennaknobs/designs/beams/__init__.py +1 -0
- antennaknobs-0.5.1/src/antennaknobs/designs/beams/hb9cv.py +121 -0
- antennaknobs-0.5.1/src/antennaknobs/designs/beams/hexbeam.py +90 -0
- antennaknobs-0.5.1/src/antennaknobs/designs/beams/moxon.py +100 -0
- antennaknobs-0.5.1/src/antennaknobs/designs/beams/yagi.py +88 -0
- antennaknobs-0.5.1/src/antennaknobs/designs/broadband/__init__.py +1 -0
- antennaknobs-0.5.1/src/antennaknobs/designs/broadband/discone.py +122 -0
- antennaknobs-0.5.1/src/antennaknobs/designs/broadband/g5rv.py +124 -0
- antennaknobs-0.5.1/src/antennaknobs/designs/broadband/lpda.py +161 -0
- antennaknobs-0.5.1/src/antennaknobs/designs/broadband/t2fd.py +121 -0
- antennaknobs-0.5.1/src/antennaknobs/designs/dipoles/__init__.py +1 -0
- antennaknobs-0.5.1/src/antennaknobs/designs/dipoles/dipole_turnstile.py +46 -0
- antennaknobs-0.5.1/src/antennaknobs/designs/dipoles/folded_invvee.py +82 -0
- antennaknobs-0.5.1/src/antennaknobs/designs/dipoles/invvee.py +137 -0
- antennaknobs-0.5.1/src/antennaknobs/designs/dipoles/koch_dipole.py +130 -0
- antennaknobs-0.5.1/src/antennaknobs/designs/dipoles/ocf_dipole.py +93 -0
- antennaknobs-0.5.1/src/antennaknobs/designs/dipoles/short_dipole_loaded.py +55 -0
- antennaknobs-0.5.1/src/antennaknobs/designs/loops/__init__.py +1 -0
- antennaknobs-0.5.1/src/antennaknobs/designs/loops/bisquare.py +98 -0
- antennaknobs-0.5.1/src/antennaknobs/designs/loops/delta_loop.py +83 -0
- antennaknobs-0.5.1/src/antennaknobs/designs/loops/delta_loop_drone.py +72 -0
- antennaknobs-0.5.1/src/antennaknobs/designs/loops/delta_loop_marked.py +93 -0
- antennaknobs-0.5.1/src/antennaknobs/designs/loops/delta_loop_reflected.py +79 -0
- antennaknobs-0.5.1/src/antennaknobs/designs/loops/delta_loop_slanted.py +100 -0
- antennaknobs-0.5.1/src/antennaknobs/designs/loops/delta_loop_solved.py +82 -0
- antennaknobs-0.5.1/src/antennaknobs/designs/loops/diamond_loop.py +95 -0
- antennaknobs-0.5.1/src/antennaknobs/designs/loops/diamond_loop_turnstile.py +85 -0
- antennaknobs-0.5.1/src/antennaknobs/designs/loops/horizontal_loop.py +111 -0
- antennaknobs-0.5.1/src/antennaknobs/designs/loops/horizontal_loop_drone.py +81 -0
- antennaknobs-0.5.1/src/antennaknobs/designs/loops/inv_delta_loop.py +84 -0
- antennaknobs-0.5.1/src/antennaknobs/designs/loops/quad.py +118 -0
- antennaknobs-0.5.1/src/antennaknobs/designs/multiband/__init__.py +1 -0
- antennaknobs-0.5.1/src/antennaknobs/designs/multiband/fandipole.py +207 -0
- antennaknobs-0.5.1/src/antennaknobs/designs/multiband/hexbeam_5band.py +388 -0
- antennaknobs-0.5.1/src/antennaknobs/designs/multiband/trap_dipole.py +139 -0
- antennaknobs-0.5.1/src/antennaknobs/designs/multiband/trap_fan_dipole.py +391 -0
- antennaknobs-0.5.1/src/antennaknobs/designs/multiband/twoband_fan_dipole.py +231 -0
- antennaknobs-0.5.1/src/antennaknobs/designs/specialty/__init__.py +1 -0
- antennaknobs-0.5.1/src/antennaknobs/designs/specialty/bowtie.py +48 -0
- antennaknobs-0.5.1/src/antennaknobs/designs/specialty/helix.py +131 -0
- antennaknobs-0.5.1/src/antennaknobs/designs/specialty/hentenna.py +91 -0
- antennaknobs-0.5.1/src/antennaknobs/designs/specialty/hentenna_slant.py +176 -0
- antennaknobs-0.5.1/src/antennaknobs/designs/specialty/hourglass.py +73 -0
- antennaknobs-0.5.1/src/antennaknobs/designs/specialty/hourglass_slant.py +94 -0
- antennaknobs-0.5.1/src/antennaknobs/designs/verticals/__init__.py +1 -0
- antennaknobs-0.5.1/src/antennaknobs/designs/verticals/bobtail.py +160 -0
- antennaknobs-0.5.1/src/antennaknobs/designs/verticals/bruce.py +157 -0
- antennaknobs-0.5.1/src/antennaknobs/designs/verticals/four_square.py +138 -0
- antennaknobs-0.5.1/src/antennaknobs/designs/verticals/half_square.py +105 -0
- antennaknobs-0.5.1/src/antennaknobs/designs/verticals/inverted_l.py +108 -0
- antennaknobs-0.5.1/src/antennaknobs/designs/verticals/jpole.py +125 -0
- antennaknobs-0.5.1/src/antennaknobs/designs/verticals/phased_verticals.py +105 -0
- antennaknobs-0.5.1/src/antennaknobs/designs/verticals/raised_vertical.py +49 -0
- antennaknobs-0.5.1/src/antennaknobs/designs/verticals/vertical.py +50 -0
- antennaknobs-0.5.1/src/antennaknobs/designs/wire/__init__.py +1 -0
- antennaknobs-0.5.1/src/antennaknobs/designs/wire/lazy_h.py +92 -0
- antennaknobs-0.5.1/src/antennaknobs/designs/wire/longwire.py +102 -0
- antennaknobs-0.5.1/src/antennaknobs/designs/wire/rhombic.py +115 -0
- antennaknobs-0.5.1/src/antennaknobs/designs/wire/sterba.py +170 -0
- antennaknobs-0.5.1/src/antennaknobs/designs/wire/sterba_center_driven.py +114 -0
- antennaknobs-0.5.1/src/antennaknobs/designs/wire/sterba_difftl.py +199 -0
- antennaknobs-0.5.1/src/antennaknobs/designs/wire/sterba_driven.py +148 -0
- antennaknobs-0.5.1/src/antennaknobs/designs/wire/sterba_tl.py +156 -0
- antennaknobs-0.5.1/src/antennaknobs/designs/wire/vbeam.py +96 -0
- antennaknobs-0.5.1/src/antennaknobs/designs/wire/w8jk.py +97 -0
- antennaknobs-0.5.1/src/antennaknobs/designs/wire/zepp.py +124 -0
- antennaknobs-0.5.1/src/antennaknobs/drone.py +205 -0
- antennaknobs-0.5.1/src/antennaknobs/engine.py +100 -0
- antennaknobs-0.5.1/src/antennaknobs/engines/__init__.py +8 -0
- antennaknobs-0.5.1/src/antennaknobs/engines/momwire.py +534 -0
- antennaknobs-0.5.1/src/antennaknobs/engines/pynec.py +499 -0
- antennaknobs-0.5.1/src/antennaknobs/far_field.py +225 -0
- antennaknobs-0.5.1/src/antennaknobs/geometry.py +290 -0
- antennaknobs-0.5.1/src/antennaknobs/nec_export.py +131 -0
- antennaknobs-0.5.1/src/antennaknobs/network.py +283 -0
- antennaknobs-0.5.1/src/antennaknobs/network_reduce.py +380 -0
- antennaknobs-0.5.1/src/antennaknobs/opt.py +166 -0
- antennaknobs-0.5.1/src/antennaknobs/sim.py +6 -0
- antennaknobs-0.5.1/src/antennaknobs/sweep.py +266 -0
- antennaknobs-0.5.1/src/antennaknobs/transform.py +65 -0
- antennaknobs-0.5.1/src/antennaknobs/user_designs.py +106 -0
- antennaknobs-0.5.1/src/antennaknobs/web/__init__.py +11 -0
- antennaknobs-0.5.1/src/antennaknobs/web/adapter.py +1323 -0
- antennaknobs-0.5.1/src/antennaknobs/web/examples/__init__.py +38 -0
- antennaknobs-0.5.1/src/antennaknobs/web/examples/_base.py +382 -0
- antennaknobs-0.5.1/src/antennaknobs/web/examples/_feedline.py +85 -0
- antennaknobs-0.5.1/src/antennaknobs/web/optimize.py +145 -0
- antennaknobs-0.5.1/src/antennaknobs/web/pynec_backend.py +204 -0
- antennaknobs-0.5.1/src/antennaknobs/web/server.py +920 -0
- antennaknobs-0.5.1/src/antennaknobs/web/static/assets/index-Cqvgb7YD.css +1 -0
- antennaknobs-0.5.1/src/antennaknobs/web/static/assets/index-RQE_Q_i5.js +10 -0
- antennaknobs-0.5.1/src/antennaknobs/web/static/index.html +34 -0
- antennaknobs-0.5.1/src/antennaknobs/web/user_design_assets/CLAUDE.md +124 -0
- antennaknobs-0.5.1/src/antennaknobs/web/user_design_assets/TEMPLATE.py +92 -0
- antennaknobs-0.5.1/src/antennaknobs/web/user_designs.py +131 -0
- antennaknobs-0.5.1/src/antennaknobs.egg-info/PKG-INFO +515 -0
- antennaknobs-0.5.1/src/antennaknobs.egg-info/SOURCES.txt +154 -0
- antennaknobs-0.5.1/src/antennaknobs.egg-info/dependency_links.txt +1 -0
- antennaknobs-0.5.1/src/antennaknobs.egg-info/requires.txt +18 -0
- antennaknobs-0.5.1/src/antennaknobs.egg-info/top_level.txt +1 -0
- antennaknobs-0.5.1/tests/test_cebik_designs.py +1322 -0
- antennaknobs-0.5.1/tests/test_cli.py +102 -0
- antennaknobs-0.5.1/tests/test_cli_user_designs.py +115 -0
- antennaknobs-0.5.1/tests/test_design_schemas.py +505 -0
- antennaknobs-0.5.1/tests/test_draw.py +33 -0
- antennaknobs-0.5.1/tests/test_drone.py +256 -0
- antennaknobs-0.5.1/tests/test_engine_spec.py +161 -0
- antennaknobs-0.5.1/tests/test_fandipole_schema.py +105 -0
- antennaknobs-0.5.1/tests/test_hexbeam_5band.py +154 -0
- antennaknobs-0.5.1/tests/test_momwire_engine.py +1511 -0
- antennaknobs-0.5.1/tests/test_nec_export.py +126 -0
- antennaknobs-0.5.1/tests/test_opt_nested_params.py +69 -0
- antennaknobs-0.5.1/tests/test_optimize.py +126 -0
- antennaknobs-0.5.1/tests/test_resolve_range.py +14 -0
- antennaknobs-0.5.1/tests/test_segment_convergence.py +102 -0
- antennaknobs-0.5.1/tests/test_sici.py +115 -0
- antennaknobs-0.5.1/tests/test_sterba_center_driven.py +41 -0
- antennaknobs-0.5.1/tests/test_sterba_driven.py +89 -0
- antennaknobs-0.5.1/tests/test_sweep_freq.py +9 -0
- antennaknobs-0.5.1/tests/test_transform.py +45 -0
- antennaknobs-0.5.1/tests/test_unit.py +163 -0
- antennaknobs-0.5.1/tests/test_unit_core.py +29 -0
- antennaknobs-0.5.1/tests/test_unit_sim.py +44 -0
- antennaknobs-0.5.1/tests/test_user_designs.py +150 -0
- antennaknobs-0.5.1/tests/test_variants.py +72 -0
- antennaknobs-0.5.1/tests/test_web_feedline.py +72 -0
- antennaknobs-0.5.1/tests/test_web_pynec_backend.py +84 -0
- antennaknobs-0.5.1/tests/test_web_server.py +1150 -0
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
BSD 3-Clause License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024, Steve Burns
|
|
4
|
+
|
|
5
|
+
Redistribution and use in source and binary forms, with or without
|
|
6
|
+
modification, are permitted provided that the following conditions are met:
|
|
7
|
+
|
|
8
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
|
9
|
+
list of conditions and the following disclaimer.
|
|
10
|
+
|
|
11
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
12
|
+
this list of conditions and the following disclaimer in the documentation
|
|
13
|
+
and/or other materials provided with the distribution.
|
|
14
|
+
|
|
15
|
+
3. Neither the name of the copyright holder nor the names of its
|
|
16
|
+
contributors may be used to endorse or promote products derived from
|
|
17
|
+
this software without specific prior written permission.
|
|
18
|
+
|
|
19
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
20
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
21
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
22
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
23
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
24
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
25
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
26
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
27
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
28
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@@ -0,0 +1,515 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: antennaknobs
|
|
3
|
+
Version: 0.5.1
|
|
4
|
+
Summary: Scriptable antenna design with live, knob-driven tuning
|
|
5
|
+
Author-email: "Steven M. Burns" <smburns47@gmail.com>
|
|
6
|
+
Project-URL: Homepage, https://github.com/stevenmburns/antennaknobs
|
|
7
|
+
Project-URL: Issues, https://github.com/stevenmburns/antennaknobs/issues
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Requires-Python: >=3.8
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
License-File: LICENSE
|
|
14
|
+
Requires-Dist: momwire>=0.1.0
|
|
15
|
+
Requires-Dist: numpy>=1.21
|
|
16
|
+
Requires-Dist: scipy>=1.7
|
|
17
|
+
Requires-Dist: matplotlib>=3.5
|
|
18
|
+
Requires-Dist: icecream>=2.1
|
|
19
|
+
Requires-Dist: scikit-rf>=0.24
|
|
20
|
+
Provides-Extra: web
|
|
21
|
+
Requires-Dist: fastapi; extra == "web"
|
|
22
|
+
Requires-Dist: uvicorn[standard]; extra == "web"
|
|
23
|
+
Requires-Dist: websockets; extra == "web"
|
|
24
|
+
Requires-Dist: psutil; extra == "web"
|
|
25
|
+
Provides-Extra: test
|
|
26
|
+
Requires-Dist: antennaknobs[web]; extra == "test"
|
|
27
|
+
Requires-Dist: pytest; extra == "test"
|
|
28
|
+
Requires-Dist: coverage; extra == "test"
|
|
29
|
+
Requires-Dist: httpx2; extra == "test"
|
|
30
|
+
Dynamic: license-file
|
|
31
|
+
|
|
32
|
+
# AntennaKNoBs · *by KK7KNB*
|
|
33
|
+
|
|
34
|
+
### Script your antenna. Tune it in real time by turning knobs.
|
|
35
|
+
|
|
36
|
+
AntennaKNoBs is a Python package for **parametric, programmatic antenna design**.
|
|
37
|
+
You describe an antenna once as a small Python *builder* — its geometry expressed
|
|
38
|
+
in terms of named parameters — and then explore the design space two ways:
|
|
39
|
+
|
|
40
|
+
- **In code**, from the command line or a Python script: draw geometry, sweep a
|
|
41
|
+
parameter, compare radiation patterns, optimize for match or gain, export a
|
|
42
|
+
NEC deck.
|
|
43
|
+
- **In the browser**, from a live workbench: drag a knob and watch the 3D wire
|
|
44
|
+
model, far-field patterns, and Smith chart redraw in real time.
|
|
45
|
+
|
|
46
|
+
Its built-in engine is **momwire**, a new in-house set of method-of-moments
|
|
47
|
+
engines. You can *optionally* add **PyNEC** (the battle-tested NEC2 engine) as a
|
|
48
|
+
second backend and solve the same design both ways to trust the answer.
|
|
49
|
+
|
|
50
|
+
[](https://github.com/stevenmburns/antennaknobs/actions/workflows/test.yml)
|
|
51
|
+
[](https://github.com/stevenmburns/antennaknobs/actions/workflows/ruff.yml)
|
|
52
|
+
[](https://github.com/stevenmburns/antennaknobs/actions/workflows/test.yml)
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
## The live web workbench
|
|
57
|
+
|
|
58
|
+
The workbench is the fastest way to feel a design. Pick an antenna, and its
|
|
59
|
+
parameters appear as a panel of sliders — the *knobs*. Drag one and every view
|
|
60
|
+
updates live over a WebSocket: the solver re-runs and the browser redraws.
|
|
61
|
+
|
|
62
|
+
<!-- TODO: add a screenshot/gif of the web workbench here (web-workbench.png) -->
|
|
63
|
+
|
|
64
|
+
What you get:
|
|
65
|
+
|
|
66
|
+
- **A panel of knobs.** Every builder parameter becomes a slider (or dropdown,
|
|
67
|
+
or checkbox) with sensible min/max/step. Drag and the design re-solves.
|
|
68
|
+
- **3D wire geometry** with current visualization, viewable from three
|
|
69
|
+
orthogonal projections (top / front / side).
|
|
70
|
+
- **Azimuth and elevation** far-field pattern slices.
|
|
71
|
+
- **A Smith chart** of input impedance, with optional frequency-sweep and
|
|
72
|
+
convergence overlays.
|
|
73
|
+
- **Three solver slots (A / B / C)** you can point at different backends and
|
|
74
|
+
compare side by side — e.g. momwire triangular vs. B-spline vs. PyNEC on the
|
|
75
|
+
same antenna, at once.
|
|
76
|
+
|
|
77
|
+
Live tuning stays responsive because rapid slider drags are coalesced into one
|
|
78
|
+
solve per round-trip, so the solver is never buried under stale requests.
|
|
79
|
+
|
|
80
|
+
### Running it
|
|
81
|
+
|
|
82
|
+
The workbench is a FastAPI backend plus a React (Vite) frontend.
|
|
83
|
+
|
|
84
|
+
**Installed (no Node needed).** A wheel install bundles the pre-built frontend,
|
|
85
|
+
so one process serves the whole app:
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
pip install "antennaknobs[web]"
|
|
89
|
+
uvicorn antennaknobs.web.server:app # open http://127.0.0.1:8000
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
The backend serves the UI at `/` and the JSON/`/ws` API on the same origin;
|
|
93
|
+
`/docs` is the interactive API explorer.
|
|
94
|
+
|
|
95
|
+
**Development (two terminals, hot-reload).** When editing the frontend, run the
|
|
96
|
+
Vite dev server alongside the backend so you get HMR:
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
# Terminal 1 — backend (from the repo root, in your .venv)
|
|
100
|
+
pip install -e ".[web]"
|
|
101
|
+
uvicorn antennaknobs.web.server:app --reload # API on http://127.0.0.1:8000
|
|
102
|
+
|
|
103
|
+
# Terminal 2 — frontend dev server
|
|
104
|
+
cd src/antennaknobs/web/frontend
|
|
105
|
+
npm install
|
|
106
|
+
npm run dev # open http://localhost:5173
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
The Vite dev server proxies the API and the `/ws` live-solve channel to the
|
|
110
|
+
backend on port 8000, so you only ever open `http://localhost:5173`. (A source
|
|
111
|
+
checkout has no pre-built bundle, so the backend alone runs API-only until you
|
|
112
|
+
`npm run build` — which writes `src/antennaknobs/web/static/`, the same bundle the wheel ships.)
|
|
113
|
+
|
|
114
|
+
> The `[web]` extra pulls in `uvicorn[standard]`, which includes the WebSocket
|
|
115
|
+
> support the live-solve channel needs — plain `uvicorn` fails the `/ws`
|
|
116
|
+
> handshake.
|
|
117
|
+
|
|
118
|
+
---
|
|
119
|
+
|
|
120
|
+
## Two simulation backends
|
|
121
|
+
|
|
122
|
+
AntennaKNoBs can solve any design with either backend, selected per-run with
|
|
123
|
+
`--engine` (CLI) or per-slot (web). Solving the same antenna two ways is the
|
|
124
|
+
point — agreement between independent engines is your confidence check.
|
|
125
|
+
|
|
126
|
+
| | **PyNEC** | **momwire** |
|
|
127
|
+
|---|---|---|
|
|
128
|
+
| What | Python binding to the compiled C++ **NEC2** engine | In-house **method-of-moments** engines, pure-Python core with optional C++ accelerators |
|
|
129
|
+
| Basis | NEC2 thin-wire (pulse/sinusoidal) | Multiple families: triangular (tent), sinusoidal, B-spline, H-matrix, array-block |
|
|
130
|
+
| Speed | Very fast single-frequency solves | Fast; C++ accelerators (pybind11) for assembly/quadrature, pure-Python fallback |
|
|
131
|
+
| Ground | Sommerfeld–Norton finite ground (default) | PEC image method; free space by default |
|
|
132
|
+
| Install | Prebuilt wheel from the `python-necpp` fork release (OpenBLAS vendored) | C++ accelerator built from the `momwire` submodule |
|
|
133
|
+
| Use it for | The established reference; finite-ground patterns | Basis-flexible cross-validation; geometries where NEC2 reactance fails to converge |
|
|
134
|
+
|
|
135
|
+
**Selecting an engine** (CLI):
|
|
136
|
+
|
|
137
|
+
```bash
|
|
138
|
+
--engine pynec # NEC2 via PyNEC
|
|
139
|
+
--engine momwire # momwire, default triangular basis
|
|
140
|
+
--engine momwire:triangular # piecewise-linear (tent) basis — the momwire default
|
|
141
|
+
--engine momwire:sinusoidal # NEC2-style three-term basis (cross-validator)
|
|
142
|
+
--engine momwire:bspline # degree-1/2 B-spline Galerkin basis
|
|
143
|
+
--engine momwire:hmatrix # B-spline + hierarchical-matrix (ACA) acceleration
|
|
144
|
+
--engine momwire:arrayblock # element-aware block solver for arrays
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
In Python, instantiate an engine directly:
|
|
148
|
+
|
|
149
|
+
```python
|
|
150
|
+
from antennaknobs.engines import PyNECEngine, MomwireEngine
|
|
151
|
+
from momwire import BSplineSolver
|
|
152
|
+
|
|
153
|
+
engine = PyNECEngine(builder)
|
|
154
|
+
engine = MomwireEngine(builder, solver=BSplineSolver, solver_kwargs={"degree": 2})
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
**momwire** lives in its own repository and is vendored here as a git submodule;
|
|
158
|
+
its primary `TriangularSolver` engine converges to NEC accuracy in ~80 segments
|
|
159
|
+
and is validated against the independent B-spline basis. The H-matrix and
|
|
160
|
+
array-block engines are newer and aimed at large arrays. **PyNEC** is an
|
|
161
|
+
*optional* second backend — the `python-necpp` fork, distributed as a
|
|
162
|
+
self-contained wheel (OpenBLAS vendored, so no SWIG/BLAS/autotools toolchain is
|
|
163
|
+
required at install time). It is licensed **GPL-2.0** and installed separately
|
|
164
|
+
from its own release; antennaknobs (MIT) neither bundles nor depends on it,
|
|
165
|
+
and loads it only if present.
|
|
166
|
+
|
|
167
|
+
---
|
|
168
|
+
|
|
169
|
+
## Designing antennas in code
|
|
170
|
+
|
|
171
|
+
An antenna is a subclass of `AntennaBuilder` that declares named parameters and
|
|
172
|
+
builds its wires from them. Because the geometry is *computed* from parameters
|
|
173
|
+
in ordinary Python, you specify physical coordinates a minimal number of times —
|
|
174
|
+
the rest follow by reflection and relative position. (Most antenna tools make
|
|
175
|
+
you type six absolute coordinates per wire.)
|
|
176
|
+
|
|
177
|
+
Here is the built-in Moxon beam (`beams.moxon`), abbreviated. Four parameters
|
|
178
|
+
describe the rectangle; helper functions negate coordinates (`rx`, `ry`) and
|
|
179
|
+
chain nodes into wires (`build_path`):
|
|
180
|
+
|
|
181
|
+
```python
|
|
182
|
+
from ... import AntennaBuilder
|
|
183
|
+
from types import MappingProxyType
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
class Builder(AntennaBuilder):
|
|
187
|
+
default_params = MappingProxyType(
|
|
188
|
+
{
|
|
189
|
+
"freq": 28.57,
|
|
190
|
+
"base": 7.0,
|
|
191
|
+
"halfdriver": 2.4597430629596713, # length of one radiating side
|
|
192
|
+
"aspect_ratio": 0.3646010186757216, # short side / long side
|
|
193
|
+
"tipspacer_factor": 0.07729647745945359,
|
|
194
|
+
"t0_factor": 0.4078045966770739,
|
|
195
|
+
}
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
def build_wires(self):
|
|
199
|
+
eps = 0.05
|
|
200
|
+
base = self.base
|
|
201
|
+
|
|
202
|
+
long = 2 * self.halfdriver / (1 + 2 * self.aspect_ratio * self.t0_factor)
|
|
203
|
+
short = self.aspect_ratio * long
|
|
204
|
+
tipspacer = short * self.tipspacer_factor
|
|
205
|
+
t0 = short * self.t0_factor
|
|
206
|
+
|
|
207
|
+
def build_path(lst, ns, ex):
|
|
208
|
+
return ((a, b, ns, ex) for a, b in zip(lst[:-1], lst[1:]))
|
|
209
|
+
def rx(p): return -p[0], p[1], p[2] # mirror across x
|
|
210
|
+
def ry(p): return p[0], -p[1], p[2] # mirror across y
|
|
211
|
+
|
|
212
|
+
S = (short / 2, eps, base)
|
|
213
|
+
A = (S[0], long / 2, base)
|
|
214
|
+
B = (A[0] - t0, A[1], base)
|
|
215
|
+
C = (B[0] - tipspacer, B[1], base)
|
|
216
|
+
D = rx(A)
|
|
217
|
+
E, F, G, H, T = ry(D), ry(C), ry(B), ry(A), ry(S)
|
|
218
|
+
|
|
219
|
+
n_seg0, n_seg1 = 21, 1
|
|
220
|
+
tups = []
|
|
221
|
+
tups.extend(build_path([S, A, B], n_seg0, None))
|
|
222
|
+
tups.extend(build_path([C, D, E, F], n_seg0, None))
|
|
223
|
+
tups.extend(build_path([G, H, T], n_seg0, None))
|
|
224
|
+
tups.append((T, S, n_seg1, 1 + 0j)) # the driven segment
|
|
225
|
+
return tups
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
The top-level package re-exports the workhorse functions, so a full
|
|
229
|
+
design-explore-compare loop is a short script. This optimizes an inverted-V
|
|
230
|
+
dipole at several heights and overlays the resulting patterns:
|
|
231
|
+
|
|
232
|
+
```python
|
|
233
|
+
import antennaknobs as ant
|
|
234
|
+
from antennaknobs.designs.dipoles.invvee import Builder
|
|
235
|
+
|
|
236
|
+
p = dict(Builder.default_params)
|
|
237
|
+
bounds = ((p['length_factor'] * .8, p['length_factor'] * 1.25), (0, 60))
|
|
238
|
+
|
|
239
|
+
builders = (
|
|
240
|
+
ant.optimize(
|
|
241
|
+
Builder(dict(p, base=base)),
|
|
242
|
+
['length_factor', 'angle_deg'], z0=50, bounds=bounds,
|
|
243
|
+
)
|
|
244
|
+
for base in [5, 6, 7, 8]
|
|
245
|
+
)
|
|
246
|
+
|
|
247
|
+
ant.compare_patterns(builders)
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
---
|
|
251
|
+
|
|
252
|
+
## Command-line usage
|
|
253
|
+
|
|
254
|
+
Everything is under `python -m antennaknobs <subcommand>`. Designs are named
|
|
255
|
+
`family.name` (with an optional `:variant`) — run `list` to see them all.
|
|
256
|
+
|
|
257
|
+
```bash
|
|
258
|
+
# Draw a Moxon's wire geometry to a file
|
|
259
|
+
python -m antennaknobs draw --builder beams.moxon --fn moxon.png
|
|
260
|
+
|
|
261
|
+
# Sweep frequency and plot impedance on a Smith chart
|
|
262
|
+
python -m antennaknobs sweep --builder beams.moxon --param freq \
|
|
263
|
+
--use_smithchart --npoints 21 --fn moxon_smith.png
|
|
264
|
+
|
|
265
|
+
# Far-field pattern of a Yagi, solved with momwire
|
|
266
|
+
python -m antennaknobs pattern --builder beams.yagi --engine momwire:triangular
|
|
267
|
+
|
|
268
|
+
# Overlay patterns of three beams
|
|
269
|
+
python -m antennaknobs compare_patterns \
|
|
270
|
+
--builders beams.moxon beams.hexbeam beams.yagi --fn beams.png
|
|
271
|
+
|
|
272
|
+
# Cross-check one design across two backends
|
|
273
|
+
python -m antennaknobs compare_patterns \
|
|
274
|
+
--builders beams.moxon beams.moxon --engines pynec momwire:bspline --fn check.png
|
|
275
|
+
|
|
276
|
+
# Optimize length and arm angle of an inverted-V dipole for a 50 Ω match
|
|
277
|
+
python -m antennaknobs optimize --builder dipoles.invvee \
|
|
278
|
+
--params length_factor angle_deg
|
|
279
|
+
|
|
280
|
+
# Export a NEC2 card deck for use in external tools
|
|
281
|
+
python -m antennaknobs export --builder beams.hexbeam --out hexbeam.nec
|
|
282
|
+
|
|
283
|
+
# List the available designs (optionally filter)
|
|
284
|
+
python -m antennaknobs list
|
|
285
|
+
python -m antennaknobs list dipole
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
Shared flags: `--engine` (backend, see above), `--ground`
|
|
289
|
+
(`free` | `pec` | `finite` | `finite:<eps_r>,<sigma>`), `--builder`/`--builders`,
|
|
290
|
+
and `--fn` (save to file instead of showing on screen).
|
|
291
|
+
|
|
292
|
+
Below is a typical far-field plot produced by the `pattern`/`compare_patterns`
|
|
293
|
+
commands:
|
|
294
|
+
|
|
295
|
+

|
|
296
|
+
|
|
297
|
+
### Available designs
|
|
298
|
+
|
|
299
|
+
Roughly 70 built-in designs across nine families — run
|
|
300
|
+
`python -m antennaknobs list` for the authoritative list:
|
|
301
|
+
|
|
302
|
+
| Family | Examples |
|
|
303
|
+
|---|---|
|
|
304
|
+
| `dipoles` | invvee, folded_invvee, ocf_dipole, koch_dipole, dipole_turnstile |
|
|
305
|
+
| `beams` | moxon, hexbeam, yagi, hb9cv |
|
|
306
|
+
| `loops` | quad, delta_loop, diamond_loop, horizontal_loop, bisquare |
|
|
307
|
+
| `verticals` | vertical, jpole, inverted_l, bobtail, four_square, bruce |
|
|
308
|
+
| `arrays` | yagiarray, moxonarray, invveearray, bowtiearray, delta_looparray |
|
|
309
|
+
| `multiband` | fandipole, trap_dipole, hexbeam_5band, twoband_fan_dipole |
|
|
310
|
+
| `broadband` | discone, g5rv, lpda, t2fd |
|
|
311
|
+
| `wire` | sterba, rhombic, vbeam, w8jk, zepp, lazy_h, longwire |
|
|
312
|
+
| `specialty` | hentenna, bowtie, helix, hourglass |
|
|
313
|
+
|
|
314
|
+
User-authored designs (in the `user.*` namespace) appear here too; filter with
|
|
315
|
+
`list --builtin-only` / `list --user-only`.
|
|
316
|
+
|
|
317
|
+
---
|
|
318
|
+
|
|
319
|
+
## Install
|
|
320
|
+
|
|
321
|
+
### From PyPI (prebuilt wheels — no toolchain)
|
|
322
|
+
|
|
323
|
+
`antennaknobs` and its MIT C++ engine `momwire` are published to **PyPI** with
|
|
324
|
+
prebuilt wheels, so a plain install needs no compiler:
|
|
325
|
+
|
|
326
|
+
```bash
|
|
327
|
+
python3 -m venv .venv && source .venv/bin/activate
|
|
328
|
+
pip install --upgrade pip
|
|
329
|
+
|
|
330
|
+
# antennaknobs + the web workbench; momwire (the engine) comes along as a dep
|
|
331
|
+
pip install "antennaknobs[web]"
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
The optional NEC2 cross-validation backend **`pynec-accel`** is **GPL-2.0** — it
|
|
335
|
+
is *not* a dependency of antennaknobs and is installed as a **separate** step,
|
|
336
|
+
never in the same `pip install` command as the MIT packages. antennaknobs is
|
|
337
|
+
fully functional on momwire alone; install pynec-accel only to cross-check
|
|
338
|
+
against NEC2:
|
|
339
|
+
|
|
340
|
+
```bash
|
|
341
|
+
# optional, GPL-2.0 (Linux / Windows / macOS-arm64 wheels)
|
|
342
|
+
pip install pynec-accel
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
Then launch the workbench with `uvicorn antennaknobs.web.server:app` (see
|
|
346
|
+
[Running it](#running-it)). On **macOS**, `brew install libomp` is required —
|
|
347
|
+
the `momwire` and `pynec-accel` wheels link Homebrew's OpenMP runtime (and share
|
|
348
|
+
it, so cross-engine use is fully multithreaded; details under [macOS](#macos)).
|
|
349
|
+
|
|
350
|
+
The sections below build from source instead (a development checkout, or a
|
|
351
|
+
platform without prebuilt wheels).
|
|
352
|
+
|
|
353
|
+
### Ubuntu (22.04 / 24.04)
|
|
354
|
+
|
|
355
|
+
PyNEC installs as a prebuilt wheel, so no
|
|
356
|
+
SWIG/BLAS/autotools toolchain is needed; only the momwire C++ accelerator compiles
|
|
357
|
+
from source (hence `g++`).
|
|
358
|
+
|
|
359
|
+
**1. System dependencies**
|
|
360
|
+
|
|
361
|
+
```bash
|
|
362
|
+
sudo apt-get update
|
|
363
|
+
sudo apt-get install \
|
|
364
|
+
python3 python3-pip python3-venv python3-dev \
|
|
365
|
+
g++ build-essential git
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
**2. Clone and create a virtual environment**
|
|
369
|
+
|
|
370
|
+
```bash
|
|
371
|
+
git clone https://github.com/stevenmburns/antennaknobs
|
|
372
|
+
cd antennaknobs
|
|
373
|
+
python3 -m venv .venv
|
|
374
|
+
source .venv/bin/activate
|
|
375
|
+
pip install --upgrade pip
|
|
376
|
+
pip install setuptools numpy scipy pytest matplotlib icecream scikit-rf
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
**3. Install momwire (the engine)**
|
|
380
|
+
|
|
381
|
+
```bash
|
|
382
|
+
# momwire: a git submodule; its C++ accelerator builds from source.
|
|
383
|
+
pip install pybind11
|
|
384
|
+
git submodule update --init momwire
|
|
385
|
+
pip install --no-build-isolation -e ./momwire
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
**3b. (Optional) Install PyNEC for cross-validation**
|
|
389
|
+
|
|
390
|
+
PyNEC is an optional second backend — **GPL-2.0**, installed separately from its
|
|
391
|
+
own release, and never bundled with or required by antennaknobs. Skip it and
|
|
392
|
+
momwire is still fully functional; install it only if you want to cross-check
|
|
393
|
+
against NEC2.
|
|
394
|
+
|
|
395
|
+
```bash
|
|
396
|
+
# The fork is published to PyPI as `pynec-accel` (a distinct name from upstream
|
|
397
|
+
# PyNEC/pynec, whose builds are broken on current Python; the import name stays
|
|
398
|
+
# `import PyNEC`). Its wheels vendor OpenBLAS + libgfortran.
|
|
399
|
+
#
|
|
400
|
+
# Use >= 1.7.4.post2: earlier builds vendored their own libgomp, which clashes
|
|
401
|
+
# with momwire's system libgomp via a static-TLS limit and silently knocks
|
|
402
|
+
# momwire's C++ accelerator onto its slow pure-Python path whenever both backends
|
|
403
|
+
# load in one process. post2 binds the system libgomp instead (universal on glibc
|
|
404
|
+
# Linux — the GCC OpenMP runtime).
|
|
405
|
+
pip install "pynec-accel>=1.7.4.post2"
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
**4. Install AntennaKNoBs**
|
|
409
|
+
|
|
410
|
+
```bash
|
|
411
|
+
pip install -e ".[test]" # core + test deps (pytest, the web test client)
|
|
412
|
+
# or pip install -e ".[web]" # just the web workbench, no test extras
|
|
413
|
+
# or pip install -e . # library only
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
**5. Run the tests**
|
|
417
|
+
|
|
418
|
+
```bash
|
|
419
|
+
pytest -vv --durations=0 -- tests/
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
(The `[test]` extra above is what makes this step work from a clean clone — it
|
|
423
|
+
pulls in `pytest`, the `[web]` server deps, and `httpx2` for the web-server
|
|
424
|
+
tests' TestClient.)
|
|
425
|
+
|
|
426
|
+
> The authoritative, always-tested version of this whole sequence is the CI
|
|
427
|
+
> workflow at [`.github/workflows/test.yml`](.github/workflows/test.yml) — it
|
|
428
|
+
> installs both engines and runs the suite on every push. If anything here
|
|
429
|
+
> drifts, that file is the source of truth.
|
|
430
|
+
|
|
431
|
+
### macOS
|
|
432
|
+
|
|
433
|
+
Tested on Apple Silicon (arm64), macOS 14+. The momwire C++ accelerator compiles
|
|
434
|
+
from source against Homebrew's OpenMP runtime (`libomp`); PyNEC installs as a
|
|
435
|
+
prebuilt wheel. CI only runs Ubuntu, so the Ubuntu sequence above is the
|
|
436
|
+
source of truth — the steps below are the same with macOS system packages.
|
|
437
|
+
|
|
438
|
+
**1. System dependencies**
|
|
439
|
+
|
|
440
|
+
```bash
|
|
441
|
+
xcode-select --install # clang/clang++ + git (skip if already installed)
|
|
442
|
+
brew install python git libomp # libomp = the OpenMP runtime the momwire accelerator links
|
|
443
|
+
```
|
|
444
|
+
|
|
445
|
+
**2. Clone and create a virtual environment.** Use a venv — on macOS it is
|
|
446
|
+
effectively required, not just good hygiene: Homebrew's Python is marked
|
|
447
|
+
externally managed (PEP 668), so `pip install` into it fails with an
|
|
448
|
+
`error: externally-managed-environment`. A venv sidesteps that and keeps the
|
|
449
|
+
project's dependencies off your system Python.
|
|
450
|
+
|
|
451
|
+
```bash
|
|
452
|
+
git clone https://github.com/stevenmburns/antennaknobs
|
|
453
|
+
cd antennaknobs
|
|
454
|
+
python3 -m venv .venv
|
|
455
|
+
source .venv/bin/activate # re-run this in each new shell before using the project
|
|
456
|
+
pip install --upgrade pip setuptools wheel
|
|
457
|
+
pip install numpy scipy pytest matplotlib icecream scikit-rf
|
|
458
|
+
```
|
|
459
|
+
|
|
460
|
+
**3. Install momwire (the engine)** — same as Ubuntu:
|
|
461
|
+
|
|
462
|
+
```bash
|
|
463
|
+
pip install pybind11
|
|
464
|
+
git submodule update --init momwire
|
|
465
|
+
pip install --no-build-isolation -e ./momwire
|
|
466
|
+
```
|
|
467
|
+
|
|
468
|
+
The build finds Homebrew's `libomp` at `/opt/homebrew/opt/libomp`, the Apple
|
|
469
|
+
Silicon default. On an Intel Mac, Homebrew lives under `/usr/local`, so point the
|
|
470
|
+
build there with `LIBOMP_PREFIX`:
|
|
471
|
+
|
|
472
|
+
```bash
|
|
473
|
+
LIBOMP_PREFIX=/usr/local/opt/libomp pip install --no-build-isolation -e ./momwire
|
|
474
|
+
```
|
|
475
|
+
|
|
476
|
+
If the accelerator fails to build for any reason, momwire still installs and runs
|
|
477
|
+
in its slower pure-Python mode.
|
|
478
|
+
|
|
479
|
+
**3b. (Optional) Install PyNEC for cross-validation**
|
|
480
|
+
|
|
481
|
+
The same optional **GPL-2.0** second backend as on Linux. The fork ships prebuilt
|
|
482
|
+
macOS wheels for **Apple Silicon (arm64), macOS 14+, Python 3.10–3.14** only —
|
|
483
|
+
there are no Intel-Mac wheels, so on an Intel Mac skip PyNEC and use momwire alone.
|
|
484
|
+
|
|
485
|
+
```bash
|
|
486
|
+
pip install "pynec-accel>=1.7.4.post2"
|
|
487
|
+
```
|
|
488
|
+
|
|
489
|
+
With **pynec-accel ≥ 1.7.4.post2** and **momwire ≥ 0.2.1**, neither wheel vendors
|
|
490
|
+
its own `libomp` — both link Homebrew's by absolute path, so a process that loads
|
|
491
|
+
both (any cross-engine run, including the tests) shares a *single* OpenMP runtime
|
|
492
|
+
and stays fully multithreaded, with no env vars. That shared runtime is why
|
|
493
|
+
`brew install libomp` is required.
|
|
494
|
+
|
|
495
|
+
> Older macOS wheels each bundled a private `libomp`; two copies in one process
|
|
496
|
+
> abort with `OMP: Error #15` (or, with `KMP_DUPLICATE_LIB_OK=TRUE`, deadlock). If
|
|
497
|
+
> you're pinned to a pre-`1.7.4.post2` pynec-accel or pre-`0.2.1` momwire, the
|
|
498
|
+
> stopgap is `export KMP_DUPLICATE_LIB_OK=TRUE` and `export OMP_NUM_THREADS=1`
|
|
499
|
+
> before any cross-engine run — at the cost of a single-threaded accelerator.
|
|
500
|
+
|
|
501
|
+
**4. Install AntennaKNoBs** and **5. Run the tests** — identical to the Ubuntu
|
|
502
|
+
steps:
|
|
503
|
+
|
|
504
|
+
```bash
|
|
505
|
+
pip install -e ".[test]"
|
|
506
|
+
pytest -vv --durations=0 -- tests/
|
|
507
|
+
```
|
|
508
|
+
|
|
509
|
+
(For the web workbench's frontend dev server, also `brew install node`.)
|
|
510
|
+
|
|
511
|
+
---
|
|
512
|
+
|
|
513
|
+
## License
|
|
514
|
+
|
|
515
|
+
MIT — see [LICENSE](LICENSE).
|