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.
Files changed (156) hide show
  1. antennaknobs-0.5.1/LICENSE +28 -0
  2. antennaknobs-0.5.1/PKG-INFO +515 -0
  3. antennaknobs-0.5.1/README.md +484 -0
  4. antennaknobs-0.5.1/pyproject.toml +99 -0
  5. antennaknobs-0.5.1/setup.cfg +4 -0
  6. antennaknobs-0.5.1/setup.py +26 -0
  7. antennaknobs-0.5.1/src/antennaknobs/__init__.py +53 -0
  8. antennaknobs-0.5.1/src/antennaknobs/__main__.py +3 -0
  9. antennaknobs-0.5.1/src/antennaknobs/builder.py +457 -0
  10. antennaknobs-0.5.1/src/antennaknobs/cli.py +666 -0
  11. antennaknobs-0.5.1/src/antennaknobs/core.py +8 -0
  12. antennaknobs-0.5.1/src/antennaknobs/designs/__init__.py +8 -0
  13. antennaknobs-0.5.1/src/antennaknobs/designs/arrays/__init__.py +1 -0
  14. antennaknobs-0.5.1/src/antennaknobs/designs/arrays/bowtiearray.py +41 -0
  15. antennaknobs-0.5.1/src/antennaknobs/designs/arrays/bowtiearray1x2.py +20 -0
  16. antennaknobs-0.5.1/src/antennaknobs/designs/arrays/bowtiearray2x4.py +56 -0
  17. antennaknobs-0.5.1/src/antennaknobs/designs/arrays/delta_looparray.py +61 -0
  18. antennaknobs-0.5.1/src/antennaknobs/designs/arrays/delta_looparray_1x4.py +40 -0
  19. antennaknobs-0.5.1/src/antennaknobs/designs/arrays/delta_looparray_1x4_grouped.py +42 -0
  20. antennaknobs-0.5.1/src/antennaknobs/designs/arrays/delta_looparray_2x2.py +41 -0
  21. antennaknobs-0.5.1/src/antennaknobs/designs/arrays/delta_looparray_network.py +97 -0
  22. antennaknobs-0.5.1/src/antennaknobs/designs/arrays/delta_looparray_with_tls.py +106 -0
  23. antennaknobs-0.5.1/src/antennaknobs/designs/arrays/folded_invveearray.py +40 -0
  24. antennaknobs-0.5.1/src/antennaknobs/designs/arrays/hentenna_array.py +22 -0
  25. antennaknobs-0.5.1/src/antennaknobs/designs/arrays/hourglass_array.py +22 -0
  26. antennaknobs-0.5.1/src/antennaknobs/designs/arrays/invveearray.py +66 -0
  27. antennaknobs-0.5.1/src/antennaknobs/designs/arrays/moxonarray.py +57 -0
  28. antennaknobs-0.5.1/src/antennaknobs/designs/arrays/yagiarray.py +48 -0
  29. antennaknobs-0.5.1/src/antennaknobs/designs/beams/__init__.py +1 -0
  30. antennaknobs-0.5.1/src/antennaknobs/designs/beams/hb9cv.py +121 -0
  31. antennaknobs-0.5.1/src/antennaknobs/designs/beams/hexbeam.py +90 -0
  32. antennaknobs-0.5.1/src/antennaknobs/designs/beams/moxon.py +100 -0
  33. antennaknobs-0.5.1/src/antennaknobs/designs/beams/yagi.py +88 -0
  34. antennaknobs-0.5.1/src/antennaknobs/designs/broadband/__init__.py +1 -0
  35. antennaknobs-0.5.1/src/antennaknobs/designs/broadband/discone.py +122 -0
  36. antennaknobs-0.5.1/src/antennaknobs/designs/broadband/g5rv.py +124 -0
  37. antennaknobs-0.5.1/src/antennaknobs/designs/broadband/lpda.py +161 -0
  38. antennaknobs-0.5.1/src/antennaknobs/designs/broadband/t2fd.py +121 -0
  39. antennaknobs-0.5.1/src/antennaknobs/designs/dipoles/__init__.py +1 -0
  40. antennaknobs-0.5.1/src/antennaknobs/designs/dipoles/dipole_turnstile.py +46 -0
  41. antennaknobs-0.5.1/src/antennaknobs/designs/dipoles/folded_invvee.py +82 -0
  42. antennaknobs-0.5.1/src/antennaknobs/designs/dipoles/invvee.py +137 -0
  43. antennaknobs-0.5.1/src/antennaknobs/designs/dipoles/koch_dipole.py +130 -0
  44. antennaknobs-0.5.1/src/antennaknobs/designs/dipoles/ocf_dipole.py +93 -0
  45. antennaknobs-0.5.1/src/antennaknobs/designs/dipoles/short_dipole_loaded.py +55 -0
  46. antennaknobs-0.5.1/src/antennaknobs/designs/loops/__init__.py +1 -0
  47. antennaknobs-0.5.1/src/antennaknobs/designs/loops/bisquare.py +98 -0
  48. antennaknobs-0.5.1/src/antennaknobs/designs/loops/delta_loop.py +83 -0
  49. antennaknobs-0.5.1/src/antennaknobs/designs/loops/delta_loop_drone.py +72 -0
  50. antennaknobs-0.5.1/src/antennaknobs/designs/loops/delta_loop_marked.py +93 -0
  51. antennaknobs-0.5.1/src/antennaknobs/designs/loops/delta_loop_reflected.py +79 -0
  52. antennaknobs-0.5.1/src/antennaknobs/designs/loops/delta_loop_slanted.py +100 -0
  53. antennaknobs-0.5.1/src/antennaknobs/designs/loops/delta_loop_solved.py +82 -0
  54. antennaknobs-0.5.1/src/antennaknobs/designs/loops/diamond_loop.py +95 -0
  55. antennaknobs-0.5.1/src/antennaknobs/designs/loops/diamond_loop_turnstile.py +85 -0
  56. antennaknobs-0.5.1/src/antennaknobs/designs/loops/horizontal_loop.py +111 -0
  57. antennaknobs-0.5.1/src/antennaknobs/designs/loops/horizontal_loop_drone.py +81 -0
  58. antennaknobs-0.5.1/src/antennaknobs/designs/loops/inv_delta_loop.py +84 -0
  59. antennaknobs-0.5.1/src/antennaknobs/designs/loops/quad.py +118 -0
  60. antennaknobs-0.5.1/src/antennaknobs/designs/multiband/__init__.py +1 -0
  61. antennaknobs-0.5.1/src/antennaknobs/designs/multiband/fandipole.py +207 -0
  62. antennaknobs-0.5.1/src/antennaknobs/designs/multiband/hexbeam_5band.py +388 -0
  63. antennaknobs-0.5.1/src/antennaknobs/designs/multiband/trap_dipole.py +139 -0
  64. antennaknobs-0.5.1/src/antennaknobs/designs/multiband/trap_fan_dipole.py +391 -0
  65. antennaknobs-0.5.1/src/antennaknobs/designs/multiband/twoband_fan_dipole.py +231 -0
  66. antennaknobs-0.5.1/src/antennaknobs/designs/specialty/__init__.py +1 -0
  67. antennaknobs-0.5.1/src/antennaknobs/designs/specialty/bowtie.py +48 -0
  68. antennaknobs-0.5.1/src/antennaknobs/designs/specialty/helix.py +131 -0
  69. antennaknobs-0.5.1/src/antennaknobs/designs/specialty/hentenna.py +91 -0
  70. antennaknobs-0.5.1/src/antennaknobs/designs/specialty/hentenna_slant.py +176 -0
  71. antennaknobs-0.5.1/src/antennaknobs/designs/specialty/hourglass.py +73 -0
  72. antennaknobs-0.5.1/src/antennaknobs/designs/specialty/hourglass_slant.py +94 -0
  73. antennaknobs-0.5.1/src/antennaknobs/designs/verticals/__init__.py +1 -0
  74. antennaknobs-0.5.1/src/antennaknobs/designs/verticals/bobtail.py +160 -0
  75. antennaknobs-0.5.1/src/antennaknobs/designs/verticals/bruce.py +157 -0
  76. antennaknobs-0.5.1/src/antennaknobs/designs/verticals/four_square.py +138 -0
  77. antennaknobs-0.5.1/src/antennaknobs/designs/verticals/half_square.py +105 -0
  78. antennaknobs-0.5.1/src/antennaknobs/designs/verticals/inverted_l.py +108 -0
  79. antennaknobs-0.5.1/src/antennaknobs/designs/verticals/jpole.py +125 -0
  80. antennaknobs-0.5.1/src/antennaknobs/designs/verticals/phased_verticals.py +105 -0
  81. antennaknobs-0.5.1/src/antennaknobs/designs/verticals/raised_vertical.py +49 -0
  82. antennaknobs-0.5.1/src/antennaknobs/designs/verticals/vertical.py +50 -0
  83. antennaknobs-0.5.1/src/antennaknobs/designs/wire/__init__.py +1 -0
  84. antennaknobs-0.5.1/src/antennaknobs/designs/wire/lazy_h.py +92 -0
  85. antennaknobs-0.5.1/src/antennaknobs/designs/wire/longwire.py +102 -0
  86. antennaknobs-0.5.1/src/antennaknobs/designs/wire/rhombic.py +115 -0
  87. antennaknobs-0.5.1/src/antennaknobs/designs/wire/sterba.py +170 -0
  88. antennaknobs-0.5.1/src/antennaknobs/designs/wire/sterba_center_driven.py +114 -0
  89. antennaknobs-0.5.1/src/antennaknobs/designs/wire/sterba_difftl.py +199 -0
  90. antennaknobs-0.5.1/src/antennaknobs/designs/wire/sterba_driven.py +148 -0
  91. antennaknobs-0.5.1/src/antennaknobs/designs/wire/sterba_tl.py +156 -0
  92. antennaknobs-0.5.1/src/antennaknobs/designs/wire/vbeam.py +96 -0
  93. antennaknobs-0.5.1/src/antennaknobs/designs/wire/w8jk.py +97 -0
  94. antennaknobs-0.5.1/src/antennaknobs/designs/wire/zepp.py +124 -0
  95. antennaknobs-0.5.1/src/antennaknobs/drone.py +205 -0
  96. antennaknobs-0.5.1/src/antennaknobs/engine.py +100 -0
  97. antennaknobs-0.5.1/src/antennaknobs/engines/__init__.py +8 -0
  98. antennaknobs-0.5.1/src/antennaknobs/engines/momwire.py +534 -0
  99. antennaknobs-0.5.1/src/antennaknobs/engines/pynec.py +499 -0
  100. antennaknobs-0.5.1/src/antennaknobs/far_field.py +225 -0
  101. antennaknobs-0.5.1/src/antennaknobs/geometry.py +290 -0
  102. antennaknobs-0.5.1/src/antennaknobs/nec_export.py +131 -0
  103. antennaknobs-0.5.1/src/antennaknobs/network.py +283 -0
  104. antennaknobs-0.5.1/src/antennaknobs/network_reduce.py +380 -0
  105. antennaknobs-0.5.1/src/antennaknobs/opt.py +166 -0
  106. antennaknobs-0.5.1/src/antennaknobs/sim.py +6 -0
  107. antennaknobs-0.5.1/src/antennaknobs/sweep.py +266 -0
  108. antennaknobs-0.5.1/src/antennaknobs/transform.py +65 -0
  109. antennaknobs-0.5.1/src/antennaknobs/user_designs.py +106 -0
  110. antennaknobs-0.5.1/src/antennaknobs/web/__init__.py +11 -0
  111. antennaknobs-0.5.1/src/antennaknobs/web/adapter.py +1323 -0
  112. antennaknobs-0.5.1/src/antennaknobs/web/examples/__init__.py +38 -0
  113. antennaknobs-0.5.1/src/antennaknobs/web/examples/_base.py +382 -0
  114. antennaknobs-0.5.1/src/antennaknobs/web/examples/_feedline.py +85 -0
  115. antennaknobs-0.5.1/src/antennaknobs/web/optimize.py +145 -0
  116. antennaknobs-0.5.1/src/antennaknobs/web/pynec_backend.py +204 -0
  117. antennaknobs-0.5.1/src/antennaknobs/web/server.py +920 -0
  118. antennaknobs-0.5.1/src/antennaknobs/web/static/assets/index-Cqvgb7YD.css +1 -0
  119. antennaknobs-0.5.1/src/antennaknobs/web/static/assets/index-RQE_Q_i5.js +10 -0
  120. antennaknobs-0.5.1/src/antennaknobs/web/static/index.html +34 -0
  121. antennaknobs-0.5.1/src/antennaknobs/web/user_design_assets/CLAUDE.md +124 -0
  122. antennaknobs-0.5.1/src/antennaknobs/web/user_design_assets/TEMPLATE.py +92 -0
  123. antennaknobs-0.5.1/src/antennaknobs/web/user_designs.py +131 -0
  124. antennaknobs-0.5.1/src/antennaknobs.egg-info/PKG-INFO +515 -0
  125. antennaknobs-0.5.1/src/antennaknobs.egg-info/SOURCES.txt +154 -0
  126. antennaknobs-0.5.1/src/antennaknobs.egg-info/dependency_links.txt +1 -0
  127. antennaknobs-0.5.1/src/antennaknobs.egg-info/requires.txt +18 -0
  128. antennaknobs-0.5.1/src/antennaknobs.egg-info/top_level.txt +1 -0
  129. antennaknobs-0.5.1/tests/test_cebik_designs.py +1322 -0
  130. antennaknobs-0.5.1/tests/test_cli.py +102 -0
  131. antennaknobs-0.5.1/tests/test_cli_user_designs.py +115 -0
  132. antennaknobs-0.5.1/tests/test_design_schemas.py +505 -0
  133. antennaknobs-0.5.1/tests/test_draw.py +33 -0
  134. antennaknobs-0.5.1/tests/test_drone.py +256 -0
  135. antennaknobs-0.5.1/tests/test_engine_spec.py +161 -0
  136. antennaknobs-0.5.1/tests/test_fandipole_schema.py +105 -0
  137. antennaknobs-0.5.1/tests/test_hexbeam_5band.py +154 -0
  138. antennaknobs-0.5.1/tests/test_momwire_engine.py +1511 -0
  139. antennaknobs-0.5.1/tests/test_nec_export.py +126 -0
  140. antennaknobs-0.5.1/tests/test_opt_nested_params.py +69 -0
  141. antennaknobs-0.5.1/tests/test_optimize.py +126 -0
  142. antennaknobs-0.5.1/tests/test_resolve_range.py +14 -0
  143. antennaknobs-0.5.1/tests/test_segment_convergence.py +102 -0
  144. antennaknobs-0.5.1/tests/test_sici.py +115 -0
  145. antennaknobs-0.5.1/tests/test_sterba_center_driven.py +41 -0
  146. antennaknobs-0.5.1/tests/test_sterba_driven.py +89 -0
  147. antennaknobs-0.5.1/tests/test_sweep_freq.py +9 -0
  148. antennaknobs-0.5.1/tests/test_transform.py +45 -0
  149. antennaknobs-0.5.1/tests/test_unit.py +163 -0
  150. antennaknobs-0.5.1/tests/test_unit_core.py +29 -0
  151. antennaknobs-0.5.1/tests/test_unit_sim.py +44 -0
  152. antennaknobs-0.5.1/tests/test_user_designs.py +150 -0
  153. antennaknobs-0.5.1/tests/test_variants.py +72 -0
  154. antennaknobs-0.5.1/tests/test_web_feedline.py +72 -0
  155. antennaknobs-0.5.1/tests/test_web_pynec_backend.py +84 -0
  156. 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 &nbsp;·&nbsp; *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
+ [![Test Python package](https://github.com/stevenmburns/antennaknobs/actions/workflows/test.yml/badge.svg)](https://github.com/stevenmburns/antennaknobs/actions/workflows/test.yml)
51
+ [![Ruff](https://github.com/stevenmburns/antennaknobs/actions/workflows/ruff.yml/badge.svg)](https://github.com/stevenmburns/antennaknobs/actions/workflows/ruff.yml)
52
+ [![Coverage](https://raw.githubusercontent.com/stevenmburns/antennaknobs/python-coverage-comment-action-data/badge.svg)](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
+ ![Radiation pattern](RadiationPattern.png)
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).