diff --git a/.github/workflows/build-necpp-wheels.yml b/.github/workflows/build-necpp-wheels.yml
new file mode 100644
index 0000000..b1255a2
--- /dev/null
+++ b/.github/workflows/build-necpp-wheels.yml
@@ -0,0 +1,126 @@
+name: Build and upload necpp to PyPI
+
+on:
+ workflow_dispatch:
+ pull_request:
+ push:
+ branches:
+ - main
+ release:
+ types:
+ - published
+
+jobs:
+ build_wheels:
+ name: Build necpp wheels on ${{ matrix.os }}
+ runs-on: ${{ matrix.runs-on }}
+ permissions:
+ contents: read
+ strategy:
+ matrix:
+ include:
+ - os: linux
+ runs-on: ubuntu-latest
+ - os: macos-intel
+ runs-on: macos-15
+ - os: macos-arm
+ runs-on: macos-latest
+
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ submodules: recursive
+
+ - name: Install build dependencies (Linux)
+ if: runner.os == 'Linux'
+ run: |
+ sudo apt-get update
+ sudo apt-get install -y swig autoconf automake libtool
+
+ - name: Install build dependencies (macOS)
+ if: runner.os == 'macOS'
+ run: |
+ brew install swig autoconf automake libtool
+
+ - name: Configure necpp_src
+ shell: bash
+ run: |
+ cd necpp_src
+ make -f Makefile.git
+ ./configure
+
+ - name: Setup package directory and generate SWIG wrapper
+ shell: bash
+ run: |
+ cd necpp
+ ln -s ../necpp_src .
+ swig -I../necpp_src/src/ -python necpp.i
+
+ - name: Build wheels
+ uses: pypa/cibuildwheel@v2.16.5
+ env:
+ CIBW_BUILD: "cp39-* cp310-* cp311-* cp312-* cp313-*"
+ CIBW_SKIP: "*-musllinux_*"
+ CIBW_BUILD_VERBOSITY: 1
+ with:
+ package-dir: necpp
+ output-dir: wheelhouse
+
+ - uses: actions/upload-artifact@v4
+ with:
+ name: cibw-wheels-necpp-${{ matrix.os }}
+ path: ./wheelhouse/*.whl
+
+ build_sdist:
+ name: Build necpp source distribution
+ runs-on: ubuntu-latest
+ permissions:
+ contents: read
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ submodules: recursive
+
+ - name: Install build dependencies
+ run: |
+ sudo apt-get update
+ sudo apt-get install -y swig autoconf automake libtool
+
+ - name: Configure necpp_src
+ run: |
+ cd necpp_src
+ make -f Makefile.git
+ ./configure
+
+ - name: Setup package directory and generate SWIG wrapper
+ run: |
+ cd necpp
+ ln -s ../necpp_src .
+ swig -I../necpp_src/src/ -python necpp.i
+
+ - name: Build sdist
+ run: |
+ cd necpp
+ pipx run build --sdist
+
+ - uses: actions/upload-artifact@v4
+ with:
+ name: cibw-sdist-necpp
+ path: necpp/dist/*.tar.gz
+
+ upload_pypi:
+ needs: [build_wheels, build_sdist]
+ runs-on: ubuntu-latest
+ environment: pypi
+ permissions:
+ id-token: write
+ if: github.event_name == 'release' && github.event.action == 'published'
+ steps:
+ - uses: actions/download-artifact@v4
+ with:
+ # unpacks all CIBW artifacts into dist/
+ pattern: cibw-*
+ path: dist
+ merge-multiple: true
+
+ - uses: pypa/gh-action-pypi-publish@release/v1
diff --git a/.github/workflows/build-pynec-wheels.yml b/.github/workflows/build-pynec-wheels.yml
new file mode 100644
index 0000000..bb89d90
--- /dev/null
+++ b/.github/workflows/build-pynec-wheels.yml
@@ -0,0 +1,126 @@
+name: Build and upload PyNEC to PyPI
+
+on:
+ workflow_dispatch:
+ pull_request:
+ push:
+ branches:
+ - main
+ release:
+ types:
+ - published
+
+jobs:
+ build_wheels:
+ name: Build PyNEC wheels on ${{ matrix.os }}
+ runs-on: ${{ matrix.runs-on }}
+ permissions:
+ contents: read
+ strategy:
+ matrix:
+ include:
+ - os: linux
+ runs-on: ubuntu-latest
+ - os: macos-intel
+ runs-on: macos-15
+ - os: macos-arm
+ runs-on: macos-latest
+
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ submodules: recursive
+
+ - name: Install build dependencies (Linux)
+ if: runner.os == 'Linux'
+ run: |
+ sudo apt-get update
+ sudo apt-get install -y swig autoconf automake libtool
+
+ - name: Install build dependencies (macOS)
+ if: runner.os == 'macOS'
+ run: |
+ brew install swig autoconf automake libtool
+
+ - name: Configure necpp_src
+ shell: bash
+ run: |
+ cd necpp_src
+ make -f Makefile.git
+ ./configure
+
+ - name: Setup package directory and generate SWIG wrapper
+ shell: bash
+ run: |
+ cd PyNEC
+ ln -s ../necpp_src .
+ swig -Wall -c++ -python PyNEC.i
+
+ - name: Build wheels
+ uses: pypa/cibuildwheel@v2.16.5
+ env:
+ CIBW_BUILD: "cp39-* cp310-* cp311-* cp312-* cp313-*"
+ CIBW_SKIP: "*-musllinux_*"
+ CIBW_BUILD_VERBOSITY: 1
+ with:
+ package-dir: PyNEC
+ output-dir: wheelhouse
+
+ - uses: actions/upload-artifact@v4
+ with:
+ name: cibw-wheels-pynec-${{ matrix.os }}
+ path: ./wheelhouse/*.whl
+
+ build_sdist:
+ name: Build PyNEC source distribution
+ runs-on: ubuntu-latest
+ permissions:
+ contents: read
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ submodules: recursive
+
+ - name: Install build dependencies
+ run: |
+ sudo apt-get update
+ sudo apt-get install -y swig autoconf automake libtool
+
+ - name: Configure necpp_src
+ run: |
+ cd necpp_src
+ make -f Makefile.git
+ ./configure
+
+ - name: Setup package directory and generate SWIG wrapper
+ run: |
+ cd PyNEC
+ ln -s ../necpp_src .
+ swig -Wall -c++ -python PyNEC.i
+
+ - name: Build sdist
+ run: |
+ cd PyNEC
+ pipx run build --sdist
+
+ - uses: actions/upload-artifact@v4
+ with:
+ name: cibw-sdist-pynec
+ path: PyNEC/dist/*.tar.gz
+
+ upload_pypi:
+ needs: [build_wheels, build_sdist]
+ runs-on: ubuntu-latest
+ environment: pypi
+ permissions:
+ id-token: write
+ if: github.event_name == 'release' && github.event.action == 'published'
+ steps:
+ - uses: actions/download-artifact@v4
+ with:
+ # unpacks all CIBW artifacts into dist/
+ pattern: cibw-*
+ path: dist
+ merge-multiple: true
+
+ - uses: pypa/gh-action-pypi-publish@release/v1
diff --git a/.gitmodules b/.gitmodules
index 08e0e53..affaabc 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -1,3 +1,3 @@
[submodule "necpp_src"]
path = necpp_src
- url = git@github.com:tmolteno/necpp
+ url = https://github.com/tmolteno/necpp.git
\ No newline at end of file
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..dd1eb58
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,9 @@
+PLAT=manylinux2_x86_28
+DOCKER_IMAGE=quay.io/pypa/manylinux2014_x86_64
+PRE_CMD=
+docker-wheels:
+ docker run --rm -e PLAT=${PLAT} -v `pwd`:/io ${DOCKER_IMAGE} ${PRE_CMD} /io/build_wheels.sh
+ auditwheel repair /output/mylibrary*whl -w /output
+
+install:
+ docker pull ${DOCKER_IMAGE}
diff --git a/PyNEC/.gitignore b/PyNEC/.gitignore
index a713962..fbe747b 100644
--- a/PyNEC/.gitignore
+++ b/PyNEC/.gitignore
@@ -1,2 +1,6 @@
build/**
*.pyc
+PyNEC.py
+*.cxx
+*.png
+necpp_src
diff --git a/PyNEC/CHANGES.md b/PyNEC/CHANGES.md
new file mode 100644
index 0000000..b22db73
--- /dev/null
+++ b/PyNEC/CHANGES.md
@@ -0,0 +1,4 @@
+### Version 1.7.3.6:
+
+* Update with a requested fix by user slawkory in context_clean.py
+* Also fix an intger division bug introduced with the shift to python3 in logperiodic_opt.py
diff --git a/PyNEC/INSTALL.md b/PyNEC/INSTALL.md
new file mode 100644
index 0000000..06059a3
--- /dev/null
+++ b/PyNEC/INSTALL.md
@@ -0,0 +1,17 @@
+## Building from Source
+
+ aptitude install swig3.0
+ git submodule init
+ git submodule update --remote
+ cd PyNEC
+ ./build.sh
+ sudo python setup.py install
+
+
+## Uploading the package to pypi
+
+Source & Binary Distribution
+ python3 setup.py sdist
+ python3 setup.py bdist_wheel
+
+ python3 setup.py upload
diff --git a/PyNEC/LICENCE.txt b/PyNEC/LICENCE.txt
new file mode 100644
index 0000000..d6a9326
--- /dev/null
+++ b/PyNEC/LICENCE.txt
@@ -0,0 +1,340 @@
+GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users. This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it. (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.) You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+ To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have. You must make sure that they, too, receive or can get the
+source code. And you must show them these terms so they know their
+rights.
+
+ We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+ Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+ Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary. To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ GNU GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License. The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language. (Hereinafter, translation is included without limitation in
+the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+ 1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+ 2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) You must cause the modified files to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ b) You must cause any work that you distribute or publish, that in
+ whole or in part contains or is derived from the Program or any
+ part thereof, to be licensed as a whole at no charge to all third
+ parties under the terms of this License.
+
+ c) If the modified program normally reads commands interactively
+ when run, you must cause it, when started running for such
+ interactive use in the most ordinary way, to print or display an
+ announcement including an appropriate copyright notice and a
+ notice that there is no warranty (or else, saying that you provide
+ a warranty) and that users may redistribute the program under
+ these conditions, and telling the user how to view a copy of this
+ License. (Exception: if the Program itself is interactive but
+ does not normally print such an announcement, your work based on
+ the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+ a) Accompany it with the complete corresponding machine-readable
+ source code, which must be distributed under the terms of Sections
+ 1 and 2 above on a medium customarily used for software interchange; or,
+
+ b) Accompany it with a written offer, valid for at least three
+ years, to give any third party, for a charge no more than your
+ cost of physically performing source distribution, a complete
+ machine-readable copy of the corresponding source code, to be
+ distributed under the terms of Sections 1 and 2 above on a medium
+ customarily used for software interchange; or,
+
+ c) Accompany it with the information you received as to the offer
+ to distribute corresponding source code. (This alternative is
+ allowed only for noncommercial distribution and only if you
+ received the program in object code or executable form with such
+ an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it. For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable. However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License. Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+ 5. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Program or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+ 6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+ 7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all. For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded. In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+ 9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation. If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+ 10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission. For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this. Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+ NO WARRANTY
+
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ {description}
+ Copyright (C) {year} {fullname}
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+ Gnomovision version 69, Copyright (C) year name of author
+ Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+ `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+ {signature of Ty Coon}, 1 April 1989
+ Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs. If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.
+
diff --git a/PyNEC/MANIFEST.in b/PyNEC/MANIFEST.in
index 4092210..181a486 100644
--- a/PyNEC/MANIFEST.in
+++ b/PyNEC/MANIFEST.in
@@ -4,4 +4,5 @@ include necpp_src/src/*.h
include necpp_src/config.h
-include necpp_src/example/test.py
\ No newline at end of file
+include example/*.py
+
diff --git a/PyNEC/Makefile b/PyNEC/Makefile
new file mode 100644
index 0000000..8b45947
--- /dev/null
+++ b/PyNEC/Makefile
@@ -0,0 +1,19 @@
+build:
+ python3 setup.py sdist
+ #python3 setup.py bdist_wheel
+
+clean:
+ rm -rf build
+ rm -rf dist
+
+test-upload:
+ python3 -m pip install --user --upgrade twine
+
+ python3 -m twine upload --repository-url https://test.pypi.org/legacy/ dist/*
+
+upload:
+ python3 -m twine upload dist/*
+
+test-install:
+ python3 -m pip install --index-url https://test.pypi.org/simple/ --no-deps PyNEC --upgrade
+
diff --git a/PyNEC/README.md b/PyNEC/README.md
index 4449822..1829c1d 100644
--- a/PyNEC/README.md
+++ b/PyNEC/README.md
@@ -1,7 +1,6 @@
# Python NEC2++ Module
-This module wraps the C++ API for antenna simulation of nec2++. It is easier to work with, and more powerful
-than the C-style API wrapper.
+This module wraps the C++ API for antenna simulation of nec2++. It is easier to work with, and more powerful than the C-style API wrapper. Works with Python 2.7 and 3+.
## Usage
@@ -53,17 +52,48 @@ Here is an example that plots a radiation pattern.
plt.savefig('RadiationPattern.png')
plt.show()
-## Install
+## Manual Build & install
- git clone https://github.com/tmolteno/python-necpp.git
- cd python-necpp
- git submodule init
- git submodule update --remote
- cd PyNEC
- ./build.sh
- sudo python setup.py install
+Requirements
+
+* [Pandoc](https://pandoc.org/installing.html)
+* [Swig](http://www.swig.org/download.html)
+* For Windows: [C/C++ compilers](https://wiki.python.org/moin/WindowsCompilers).
+* Git bash (for running build.sh script)
+* Latest python packages: pip, setuptools, numpy, wheel, numpy. Run:
+`$ pip install --upgrade pip setuptools wheel numpy`
+
+*Note: Download and extract swigwin.zip and add the path to swig.exe to environment.*
+
+Then do following:
+
+ $ git clone --recursive https://github.com/tmolteno/python-necpp.git
+ $ cd python-necpp
+ $ cd PyNEC
+ $ ./build.sh
+ $ python setup.py bdist_wheel (For generating wheel, requires wheel package)
+ $ sudo python setup.py install
+
+*Note: 'sudo' is not required in windows.*
+
+## Install from PyPI
+
+ $ sudo pip install pynec
+
+*Note: 'sudo' is not required in windows.*
## Testing
- python example/test_rp.py
+Requirements
+
+* python package: matplotlib
+
+ $ python example/test_rp.py
+
+The example directory contains the following additional examples (that are inspired by excercises from a course on antennas):
+
+* logperiodic_opt.py is an example on how to combine PyNECPP with scipy.optimize to use a genetic algorithm to **optimize an antenna for multiple frequency bands** at the same time (which I thin is not possible in 4nec2). The resulting gains and VSWR are plotted over the frequency range of interest. This requires scipy >= 0.15.0 due to the usage of scipy.optimize.differential_evolution.
+* monopole_realistic_ground_plane.py plots the vertical gain pattern of a monopole antenna. Its dimensions are optimized with a local search, and the path through the search space is visualized with a heat map.
+* dipole.py does a very simple optimization of a dipole, and plots the VSWR over a given frequency range for different system impedances to file.
+
diff --git a/PyNEC/build.sh b/PyNEC/build.sh
index 9712711..4785138 100755
--- a/PyNEC/build.sh
+++ b/PyNEC/build.sh
@@ -4,16 +4,15 @@
#
# Author. Tim Molteno.
#
+# FIrst have to do git submodule init
git submodule update --remote
-#
-pushd ../necpp_src
+ln -s ../necpp_src .
+DIR=`pwd`
+cd ../necpp_src
make -f Makefile.git
./configure --without-lapack
-popd
-
-# Generate a README.txt from README.md
-pandoc -o README.txt README.md
+cd ${DIR}
# Build PyNEC
swig -Wall -v -c++ -python PyNEC.i
-python setup.py build
\ No newline at end of file
+python3 setup.py build
diff --git a/example/.gitignore b/PyNEC/example/.gitignore
similarity index 100%
rename from example/.gitignore
rename to PyNEC/example/.gitignore
diff --git a/PyNEC/example/README.md b/PyNEC/example/README.md
new file mode 100644
index 0000000..0b242c8
--- /dev/null
+++ b/PyNEC/example/README.md
@@ -0,0 +1,15 @@
+# PyNEC examples
+
+This folder contains some examples showing the use of PyNEC antenna simulation module
+
+## Optimizing a Monopole
+
+The file monopole.py simulates a monopole antenna, printing out the impedance. To optimize this
+
+ python3 monopole.py
+
+To optimize the monopole design to achieve a particular impedance, try
+
+ python3 optimized.py --basinhopping --target-impedance=110
+
+This code uses standard scipy optimizers to find the best solution.
diff --git a/PyNEC/example/antenna_util.py b/PyNEC/example/antenna_util.py
new file mode 100644
index 0000000..5f5567f
--- /dev/null
+++ b/PyNEC/example/antenna_util.py
@@ -0,0 +1,31 @@
+#
+# Some antenna utility functions
+#
+import numpy as np
+
+def reflection_coefficient(z, z0):
+ return np.abs((z - z0) / (z + z0))
+
+def vswr(z, z0):
+ Gamma = reflection_coefficient(z, z0)
+ return float((1 + Gamma) / (1 - Gamma))
+
+def mismatch(z, z0):
+ Gamma = reflection_coefficient(z, z0)
+ return 1 - Gamma**2
+
+# Source: https://gist.github.com/temporaer/6755266
+# 'matplotlib log-polar plots seem to be quite buggy at the time of writing.' Yes, indeed, sadly...
+def plot_logpolar(ax, theta, r_, bullseye=None, **kwargs):
+ min10 = np.log10(np.min(r_))
+ max10 = np.log10(np.max(r_))
+ if bullseye is None:
+ bullseye = min10 - np.log10(0.5 * np.min(r_))
+ r = np.log10(r_) - min10 + bullseye
+ ax.plot(theta, r, **kwargs)
+ l = np.arange(np.floor(min10), max10)
+ ax.set_rticks(l - min10 + bullseye)
+ # ax.set_yticklabels(["1e%d" % x for x in l])
+ ax.set_yticklabels(["%d" % x for x in l])
+ ax.set_rlim(0, max10 - min10 + bullseye)
+ return ax
diff --git a/PyNEC/example/context_clean.py b/PyNEC/example/context_clean.py
new file mode 100644
index 0000000..4f4a3d3
--- /dev/null
+++ b/PyNEC/example/context_clean.py
@@ -0,0 +1,184 @@
+# Note: explicit zeroes are blanks. All other values should be specified symbolically.
+
+# Currently these contain only the subset of cards that I needed
+
+class Range(object):
+ def __init__(self, start, stop, count=None, delta=None):
+ self.start = start
+ self.stop = stop
+ if count is not None:
+ self.count = count
+ self.delta = (stop - start) / count
+ else:
+ self.count = (stop_ - start) / delta
+ self.delta = delta
+
+# Setting do_debug to True will dump all the cards generated with context_clean, so you can verify the output more easily in a text editor (and debug that file manually)
+do_debug = False
+
+def debug(card, *args):
+ if do_debug:
+ stringified = " , ".join([str(a) for a in args])
+ print("%s %s" % (card, stringified))
+
+class context_clean(object):
+ def __init__(self, context):
+ self.context = context
+
+ def remove_all_loads(self):
+ ld_short_all_loads = -1
+ self.context.ld_card(ld_short_all_loads, 0, 0, 0, 0, 0, 0)
+
+ def set_wire_conductivity(self, conductivity, wire_tag=None):
+ """ The conductivity is specified in mhos/meter. Currently all segments of a wire are set. If wire_tag is None, all wire_tags are set (i.e., a tag of 0 is used). """
+ if wire_tag is None:
+ wire_tag = 0
+
+ debug("LD", 5, wire_tag, 0, 0, conductivity, 0, 0)
+ self.context.ld_card(5, wire_tag, 0, 0, conductivity, 0, 0)
+
+ def set_all_wires_conductivity(self, conductivity):
+ self.set_wire_conductivity(conductivity)
+
+ # TODO: multiplicative
+ def set_frequencies_linear(self, start_frequency, stop_frequency, count=None, step_size=None):
+ """ If start_frequency does not equal stop_frequency, either count or step should be specified. The other parameter will be automatically deduced """
+
+ if start_frequency == stop_frequency:
+ step_size = 0
+ count = 1
+ else:
+ # TODO: add some asserts
+ if count is not None:
+ step_size = (stop_frequency - start_frequency) / count
+ else:
+ count = (stop_frequency - start_frequency) / step_size
+
+ # TODO, what if we don't have nice divisibility here
+ count = int(count)
+
+ ifrq_linear_step = 0
+ debug("FR", ifrq_linear_step, count, start_frequency, step_size, 0, 0, 0)
+ self.context.fr_card(ifrq_linear_step, count, start_frequency, step_size)
+
+ def set_frequency(self, frequency):
+ self.set_frequencies_linear(frequency, frequency)
+
+ def clear_ground(self):
+ gn_nullify_ground = -1
+ self.context.gn_card(gn_nullify_ground, 0, 0, 0, 0, 0, 0, 0)
+
+ # TODO: I could probably make a ground class, would probably be cleaner to group some of the options and different functions there (like combining ground screen etc)
+
+ # TODO: gn card is iffy, check!
+ def set_finite_ground(self, ground_dielectric, ground_conductivity):
+ gn_finite_ground = 0
+ no_ground_screen = 0
+
+ self.context.gn_card(gn_finite_ground, no_ground_screen, ground_dielectric, ground_conductivity, 0, 0, 0, 0)
+
+ def set_perfect_ground(self):
+ gn_perfectly_conducting = 1
+ no_ground_screen = 0
+
+ debug("GN", gn_perfectly_conducting, no_ground_screen, 0, 0, 0, 0, 0, 0)
+ self.context.gn_card(gn_perfectly_conducting, no_ground_screen, 0, 0, 0, 0, 0, 0)
+
+
+ # TODO: i1 = 5 is also a voltage excitation
+ def voltage_excitation(self, wire_tag, segment_nr, voltage):
+ ex_voltage_excitation = 0
+ no_action = 0 # TODO configurable
+ option_i3i4 = 10*no_action + no_action
+
+ debug("EX", ex_voltage_excitation, wire_tag, segment_nr, option_i3i4, voltage.real, voltage.imag, 0, 0, 0, 0)
+ self.context.ex_card(ex_voltage_excitation, wire_tag, segment_nr, option_i3i4, voltage.real, voltage.imag, 0, 0, 0, 0)
+
+ def get_geometry(self):
+ #return geometry_clean(self.context.get_geometry()) # TODO
+ return self.context.get_geometry()
+
+ def set_extended_thin_wire_kernel(self, enable):
+ if enable:
+ debug ("EK", 0)
+ self.context.set_extended_thin_wire_kernel(True)
+ else:
+ debug ("EK", -1)
+ self.context.set_extended_thin_wire_kernel(False)
+
+ def geometry_complete(self, ground_plane, current_expansion=True):
+ no_ground_plane = 0
+ ground_plane_current_expansion = 1
+ ground_plane_no_current_expansion = -1
+ if not ground_plane:
+ debug("GE", no_ground_plane)
+ self.context.geometry_complete(no_ground_plane)
+ else:
+ if current_expansion:
+ debug("GE", ground_plane_current_expansion)
+ self.context.geometry_complete(ground_plane_current_expansion)
+ else:
+ debug("GE", ground_plane_no_current_expansion)
+ self.context.geometry_complete(ground_plane_no_current_expansion)
+
+ output_major_minor = 0
+ output_vertical_horizontal = 1
+
+ normalization_none = 0
+ normalization_major = 1
+ normalization_minor = 2
+ normalization_vertical = 3
+ normalization_horizontal = 4
+ normalization_totalgain = 5
+
+ power_gain = 0
+ directive_gain = 1
+
+ average_none = 0
+ average_gain = 1
+ average_todo = 2
+
+ # TODO: this should be different for surface_wave_mode (1), because then thetas = z
+ def radiation_pattern(self, thetas, phis, output_mode=output_vertical_horizontal, normalization=normalization_none, gain=power_gain, average=average_todo):
+ """ thetas and phis should be Range(-like) objects """
+ normal_mode = 0 # TODO other modes
+
+ # the rp_card already has XNDA as separate arguments
+ radial_distance = 0 # TODO
+ gnornamize_maximum = 0 # TODO
+
+ xnda = average + 10*gain+100*normalization+1000*output_mode
+
+ debug("RP", normal_mode, thetas.count, phis.count, xnda, thetas.start, phis.start, thetas.delta, phis.delta, radial_distance, gnornamize_maximum)
+ self.context.rp_card(normal_mode, thetas.count, phis.count, output_mode, normalization, gain, average, thetas.start, phis.start, thetas.delta, phis.delta, radial_distance, gnornamize_maximum)
+
+ # TODO: shunt admittances, length of transmission line if not straight-line distance
+ def transmission_line(self, src, dst, impedance, crossed_line=False, length=None, shunt_admittance_src=0, shunt_admittance_dst=0):
+ """ src and dst are (tag_nr, segment_nr) pairs """
+ if crossed_line:
+ impedance *= -1
+ if length is None:
+ length = 0
+ shunt_admittance_src = complex(shunt_admittance_src)
+ shunt_admittance_dst = complex(shunt_admittance_dst)
+
+ debug("TL", src[0], src[1], dst[0], dst[1], impedance, length, shunt_admittance_src.real, shunt_admittance_src.imag, shunt_admittance_dst.real, shunt_admittance_dst.imag)
+ self.context.tl_card(src[0], src[1], dst[0], dst[1], impedance, length, shunt_admittance_src.real, shunt_admittance_src.imag, shunt_admittance_dst.real, shunt_admittance_dst.imag)
+
+ # Some simple wrappers for context...
+ # TODO: this should be simpler, can't this be auto-generated, or implicitly defined? The best solution is of course to do this in the C++ code,
+ # and then the wrappers are immediately correct and nice
+ def xq_card(self, *args):
+ return self.context.xq_card(*args)
+ def get_input_parameters(self, *args):
+ return self.context.get_input_parameters(*args)
+
+class geometry_clean(object):
+ def __init__(self, geometry):
+ self.geometry = geometry
+
+ def wire(self, tag_id, nr_segments, src, dst, radius, length_ratio=1.0, radius_ratio=1.0):
+ """ radius is in meter. length_ratio can be set to have non-uniform segment lengths, radius_ratio can be used for tapered wires """
+ debug("GW", tag_id, nr_segments, src[0], src[1], src[2], dst[0], dst[1], dst[2], radius) # TODO
+
+ self.geometry.wire(tag_id, nr_segments, src[0], src[1], src[2], dst[0], dst[1], dst[2], radius, length_ratio, radius_ratio)
diff --git a/PyNEC/example/dipole.py b/PyNEC/example/dipole.py
new file mode 100644
index 0000000..780c713
--- /dev/null
+++ b/PyNEC/example/dipole.py
@@ -0,0 +1,144 @@
+import numpy as np
+import scipy.optimize
+import pylab as plt
+
+
+from PyNEC import *
+from antenna_util import *
+
+from context_clean import *
+
+import math
+
+def geometry(freq_mhz, length, nr_segments):
+ wire_radius = 0.01e-3 # 0.01 mm
+
+ nec = context_clean(nec_context())
+ nec.set_extended_thin_wire_kernel(False)
+
+ geo = geometry_clean(nec.get_geometry())
+
+ center = np.array([0,0,0])
+ half = np.array([length/2, 0, 0])
+
+ pt1 = center - half
+ pt2 = center + half
+
+ wire_tag = 1
+ geo.wire(tag_id=wire_tag, nr_segments=nr_segments, src=pt1, dst=pt2, radius=wire_radius)
+
+ nec.geometry_complete(ground_plane=False)
+
+ nec.set_frequency(freq_mhz)
+
+ # Voltage excitation in the center of the antenna
+ nec.voltage_excitation(wire_tag=wire_tag, segment_nr=int(nr_segments/2), voltage=1.0)
+
+ return nec
+
+def impedance(freq_mhz, length, nr_segments):
+ nec = geometry(freq_mhz, length, nr_segments)
+ nec.xq_card(0)
+
+ index = 0
+ return nec.get_input_parameters(index).get_impedance()
+
+def create_optimization_target(freq_mhz, nr_segments):
+ def target(length):
+ return abs(impedance(freq_mhz, length[0], nr_segments).imag)
+ return target
+
+# It's probably possible that the antenna matches in multiple regions, pick the one around the center frequency...
+def matched_range_around(nec, count, center_freq, system_impedance):
+ # TODO: this is not ideal
+ min_dist = float('inf')
+ min_idx = None
+ target_hz = center_freq * 1000000
+
+ for idx in range(0, count):
+ dist = abs(nec.get_input_parameters(idx).get_frequency() - target_hz)
+ if dist < min_dist:
+ min_dist = dist
+ min_idx = idx
+
+ idx = min_idx
+ matched_min_freq = None
+ while idx >= 0:
+ ipt = nec.get_input_parameters(idx)
+ z = ipt.get_impedance()
+ if vswr(z, system_impedance) > 2:
+ break
+ matched_min_freq = ipt.get_frequency() / 1000000
+ idx -= 1
+
+ idx = min_idx
+ matched_max_freq = None
+ while idx < count:
+ ipt = nec.get_input_parameters(idx)
+ z = ipt.get_impedance()
+ if vswr(z, system_impedance) > 2:
+ break
+ matched_max_freq = ipt.get_frequency() / 1000000
+ idx += 1
+
+ return (matched_min_freq, matched_max_freq)
+
+if (__name__ == '__main__'):
+ design_freq_mhz = 2450
+ wavelength = 299792e3/(design_freq_mhz*1000000)
+
+ initial_length = wavelength / 2 # TODO
+
+ print("Wavelength is %0.4fm, initial length is %0.4fm" % (wavelength, initial_length))
+
+ nr_segments = 101 # int(math.ceil(50*initial_length/wavelength))
+
+ z = impedance(design_freq_mhz, initial_length, nr_segments)
+
+ print("Initial impedance: (%6.1f,%+6.1fI) Ohms" % (z.real, z.imag))
+
+ target = create_optimization_target(design_freq_mhz, nr_segments)
+ optimized_result = scipy.optimize.minimize(target, np.array([initial_length]))
+ optimized_length = optimized_result.x[0]
+
+ z = impedance(design_freq_mhz, optimized_length, nr_segments)
+
+ print("Optimized length %6.6f m, which gives an impedance of: (%6.4f,%+6.4fI) Ohms" % (optimized_length, z.real, z.imag))
+ print("VSWR @ 75 Ohm is %6.6f" % vswr(z, 75))
+
+ for system_impedance in [75, 50, 300]:
+ nec = geometry(design_freq_mhz, optimized_length, nr_segments)
+
+ count = 300
+ nec.set_frequencies_linear(2300, 2600, count=count)
+ nec.xq_card(0) # Execute simulation
+
+ # TODO: add get_n_items to nec_antenna_input and co, so we can automatically deduce count etc. Much cleaner
+
+ rng = matched_range_around(nec, count, design_freq_mhz, system_impedance)
+ if rng[0] is None or rng[1] is None:
+ print("VSWR is nowhere <= 2 @ %i Ohm!" % system_impedance)
+ else:
+ bandwidth = 100.0 * (rng[1] - rng[0]) / design_freq_mhz
+ print("The fractional bandwidth @ %i Ohm is %2.2f%% - %i MHz (%i Mhz to %i MHz)" % (system_impedance, bandwidth, (rng[1] - rng[0]), rng[0], rng[1]))
+
+ freqs = []
+ vswrs = []
+ for idx in range(0, count):
+ ipt = nec.get_input_parameters(idx)
+ z = ipt.get_impedance()
+
+ freqs.append(ipt.get_frequency() / 1000000)
+ vswrs.append(vswr(z, system_impedance))
+
+ # print "%i MHz, Z = %6.6f @ %i" % (ipt.get_frequency(), vswr(z, system_impedance), system_impedance)
+
+ plt.figure()
+ plt.plot(freqs, vswrs)
+ plt.title("VSWR of a %.2f mm long dipole for a %i Ohm system" % (optimized_length * 1000.0, system_impedance))
+ plt.xlabel("Frequency (MHz)")
+ plt.ylabel("VSWR")
+ plt.grid(True)
+ filename = "vswr_%i_MHz.pdf" % system_impedance
+ print("Saving plot to file: %s" % filename)
+ plt.savefig(filename)
diff --git a/example/impedance_plot.py b/PyNEC/example/impedance_plot.py
similarity index 100%
rename from example/impedance_plot.py
rename to PyNEC/example/impedance_plot.py
diff --git a/PyNEC/example/logperiodic_opt.py b/PyNEC/example/logperiodic_opt.py
new file mode 100644
index 0000000..0a4ecd5
--- /dev/null
+++ b/PyNEC/example/logperiodic_opt.py
@@ -0,0 +1,244 @@
+import numpy as np
+import scipy.optimize
+import matplotlib.pyplot as plt
+import matplotlib as mpl
+
+from PyNEC import *
+from antenna_util import *
+
+from context_clean import *
+
+import math
+
+""" Optimize and plot the gains/VSWR of a logperiodic antenna (6 brass elements, 75 Ohm transmission lines) for both the 2.4GHz as the 5.8GHz ISM bands.
+ Inspired by an excercise for a course, hence the weird constraints. """
+
+brass_conductivity = 15600000 # mhos
+
+tl_impedance = 75.0
+
+def geometry_logperiodic(l_1, x_1, tau):
+ """
+ x_1 is the distance from the origin to the largest (farthest away) dipole, which has a length of l_1.
+ The spacing is as follows: l_{i+1}/l_i = tau = x_{i+1}/x_i
+ """
+ wire_radius = 0.00025 # 0.25 mm
+
+ # alpha = np.arctan( (l_1/2.0) / x_1 )
+
+ nec = context_clean(nec_context())
+ nec.set_extended_thin_wire_kernel(True)
+
+ geo = geometry_clean(nec.get_geometry())
+
+ # Dipoles should be oriented in the Z direction; they should be placed on the (positive) X axis
+
+ x_i = x_1
+ l_i = x_1
+
+ # As ususal, note that nec tags start at 1, and we typically index from 0!
+ dipole_center_segs = {} # Maps from NEC wire id!
+
+ dipoles_count = 5
+
+ for dipole_tag in range(1, dipoles_count + 1):
+ nr_segments = int(math.ceil(50*l_i/wavelength)) # TODO this might vary when sweeping even!
+ #print nr_segments
+
+ dipole_center_segs[dipole_tag] = nr_segments // 2 + 1
+
+ center = np.array([x_i, 0, 0])
+ half_height = np.array([0 , 0, l_i/2.0])
+ top = center + half_height
+ bottom = center - half_height
+
+ geo.wire(tag_id=dipole_tag, nr_segments=nr_segments, src=bottom, dst=top, radius=wire_radius)
+
+ x_i = tau * x_i
+ l_i = tau * l_i
+
+ # Everything is in brass
+ nec.set_wire_conductivity(brass_conductivity)
+
+ nec.geometry_complete(ground_plane=False)
+
+ # The 6th tag is the smallest tag is the source element
+ for dipole in range(0, dipoles_count - 1):
+ src_tag = int(1 + dipole) # NEC indexing
+ src_seg = dipole_center_segs[src_tag]
+
+ dst_tag = src_tag + 1
+ dst_seg = dipole_center_segs[dst_tag]
+
+ nec.transmission_line((src_tag, src_seg), (dst_tag, dst_seg), tl_impedance, crossed_line=True)
+
+ smallest_dipole_tag = dipoles_count # Again, start at 1
+
+ nec.voltage_excitation(wire_tag=smallest_dipole_tag, segment_nr=dipole_center_segs[smallest_dipole_tag], voltage=1.0)
+
+ return nec
+
+start = 2300
+stop = 5900
+count = stop - start
+
+
+def get_gain_swr_range(l_1, x_1, tau, start=start, stop=stop, step=10):
+ gains_db = []
+ frequencies = []
+ vswrs = []
+ for freq in range(start, stop + 1, step):
+ nec = geometry_logperiodic(l_1, x_1, tau)
+ nec.set_frequency(freq) # TODO: ensure that we don't need to re-generate this!
+ nec.radiation_pattern(thetas=Range(90, 90, count=1), phis=Range(180,180,count=1))
+
+ rp = nec.context.get_radiation_pattern(0)
+ ipt = nec.get_input_parameters(0)
+ z = ipt.get_impedance()
+
+ # Gains are in decibels
+ gains_db.append(rp.get_gain()[0])
+ vswrs.append(vswr(z, system_impedance))
+ frequencies.append(ipt.get_frequency())
+
+ return frequencies, gains_db, vswrs
+
+def create_optimization_target():
+ def target(args):
+ l_1, x_1, tau = args
+ if l_1 <= 0 or x_1 <= 0 or tau <= 0:
+ return float('inf')
+
+ try:
+ result = 0
+
+ vswr_score = 0
+ gains_score = 0
+
+ for range_low, range_high in [ (2400, 2500), (5725, 5875) ]:
+ freqs, gains, vswrs = get_gain_swr_range(l_1, x_1, tau, start=range_low, stop=range_high)
+
+ for gain in gains:
+ gains_score += gain
+ for vswr in vswrs:
+ if vswr >= 1.8:
+ vswr = np.exp(vswr) # a penalty :)
+ vswr_score += vswr
+
+ # VSWR should minimal in both bands, gains maximal:
+ result = vswr_score - gains_score
+
+ except:
+ print("Caught exception")
+ return float('inf')
+
+ print(result)
+
+ return result
+ return target
+
+
+def simulate_and_get_impedance(nec):
+ nec.set_frequency(design_freq_mhz)
+
+ nec.xq_card(0)
+
+ index = 0
+ return nec.get_input_parameters(index).get_impedance()
+
+system_impedance = 50 # This makes it a bit harder to optimize, given the 75 Ohm TLs, which is good for this excercise of course...
+
+# (2.4 GHz to 2.5 GHz) and the 5.8 GHz ISM band (5.725 GHz to 5.875 GHz)
+
+design_freq_mhz = 2450 # The center of the first range
+wavelength = 299792e3/(design_freq_mhz*1000000)
+
+majorLocator = mpl.ticker.MultipleLocator(10)
+majorFormatter = mpl.ticker.FormatStrFormatter('%d')
+minorLocator = mpl.ticker.MultipleLocator(1)
+minorFormatter = mpl.ticker.FormatStrFormatter('%d')
+
+def draw_frequencie_ranges(ax):
+ ax.axvline(x=2400, color='red', linewidth=1)
+ ax.axvline(x=2500, color='red', linewidth=1)
+ ax.axvline(x=5725, color='red', linewidth=1)
+ ax.axvline(x=5875, color='red', linewidth=1)
+
+def show_report(l1, x1, tau):
+ nec = geometry_logperiodic(l1, x1, tau)
+
+ z = simulate_and_get_impedance(nec)
+
+ print("Initial impedance: (%6.1f,%+6.1fI) Ohms" % (z.real, z.imag))
+ print("VSWR @ 50 Ohm is %6.6f" % vswr(z, 50))
+
+ nec = geometry_logperiodic(l1, x1, tau)
+
+ freqs, gains, vswrs = get_gain_swr_range(l1, x1, tau, step=5)
+
+ freqs = np.array(freqs) / 1000000 # In MHz
+
+ ax = plt.subplot(111)
+ ax.plot(freqs, gains)
+ draw_frequencie_ranges(ax)
+
+ ax.set_title("Gains of a 5-element log-periodic antenna")
+ ax.set_xlabel("Frequency (MHz)")
+ ax.set_ylabel("Gain")
+
+ ax.yaxis.set_major_locator(majorLocator)
+ ax.yaxis.set_major_formatter(majorFormatter)
+
+ ax.yaxis.set_minor_locator(minorLocator)
+ ax.yaxis.set_minor_formatter(minorFormatter)
+
+ ax.yaxis.grid(b=True, which='minor', color='0.75', linestyle='-')
+
+ plt.show()
+
+ ax = plt.subplot(111)
+ ax.plot(freqs, vswrs)
+ draw_frequencie_ranges(ax)
+
+ ax.set_yscale("log")
+ ax.set_title("VSWR of a 6-element log-periodic antenna @ 50 Ohm impedance")
+ ax.set_xlabel("Frequency (MHz)")
+ ax.set_ylabel("VSWR")
+
+ ax.yaxis.set_major_locator(majorLocator)
+ ax.yaxis.set_major_formatter(majorFormatter)
+ ax.yaxis.set_minor_locator(minorLocator)
+ ax.yaxis.set_minor_formatter(minorFormatter)
+
+ ax.yaxis.grid(b=True, which='minor', color='0.75', linestyle='-')
+ plt.show()
+
+
+if (__name__ == '__main__'):
+ initial_l1 = wavelength / 2
+ initial_x1 = wavelength / 2
+ initial_tau = 0.8
+
+ print("Wavelength is %0.4fm, initial length is %0.4fm" % (wavelength, initial_l1))
+
+ print("Unoptimized antenna...")
+ show_report(initial_l1, initial_x1, initial_tau)
+
+ print("Optimizing antenna...")
+ target = create_optimization_target()
+
+ # Optimize local minimum only with gradient desce
+ #optimized_result = scipy.optimize.minimize(target, np.array([initial_l1, initial_x1, initial_tau]), method='Nelder-Mead')
+
+ # Use differential evolution:
+ minimizer_kwargs = dict(method='Nelder-Mead')
+ bounds = [ (0.01, 0.2), (0.01, 0.2), (0.7, 0.9) ]
+ optimized_result = scipy.optimize.differential_evolution(target, bounds, seed=42, disp=True, popsize=20)
+
+ # Basin hopping isn't so good, but could also have been an option:
+ #optimized_result = scipy.optimize.basinhopping(target, np.array([initial_l1, initial_x1, initial_tau]), minimizer_kwargs=minimizer_kwargs, niter=5, stepsize=0.015, T=2.0, disp=True)
+
+ print("Optimized antenna...")
+ optimized_l1, optimized_x1, optimized_tau = optimized_result.x[0], optimized_result.x[1], optimized_result.x[2]
+ show_report(optimized_l1, optimized_x1, optimized_tau)
+
diff --git a/PyNEC/example/monopole.py b/PyNEC/example/monopole.py
new file mode 100644
index 0000000..cc0eb4b
--- /dev/null
+++ b/PyNEC/example/monopole.py
@@ -0,0 +1,48 @@
+#
+# Simple vertical monopole antenna simulation using python-necpp
+# pip install PyNEC
+#
+from PyNEC import *
+
+from context_clean import *
+
+import math
+
+def geometry(freq, base, length):
+ conductivity = 1.45e6 # Stainless steel
+ ground_conductivity = 0.002
+ ground_dielectric = 10
+
+ wavelength = 3e8/(1e6*freq)
+ n_seg = int(math.ceil(50*length/wavelength))
+
+ nec = context_clean(nec_context())
+
+ geo = nec.get_geometry()
+ geo.wire(1, n_seg, 0, 0, base, 0, 0, base+length, 0.002, 1.0, 1.0)
+ nec.geometry_complete(1)
+
+ nec.set_all_wires_conductivity(conductivity)
+
+ nec.set_finite_ground(ground_dielectric, ground_conductivity)
+ nec.set_frequency(freq)
+
+ # Voltage excitation one third of the way along the wire
+ nec.voltage_excitation(wire_tag=1, segment_nr=int(n_seg/3), voltage=1.0)
+
+ return nec
+
+def impedance(freq, base, length):
+ nec = geometry(freq, base, length)
+ nec.xq_card(0) # Execute simulation
+
+ index = 0
+
+ ipt = nec.get_input_parameters(index)
+ z = ipt.get_impedance()
+
+ return z
+
+if (__name__ == '__main__'):
+ z = impedance(freq = 134.5, base = 0.5, length = 4.0)
+ print("Impedance at base=%0.2f, length=%0.2f : (%6.1f,%+6.1fI) Ohms" % (0.5, 4.0, z.real, z.imag))
diff --git a/PyNEC/example/monopole_realistic_ground_plane.py b/PyNEC/example/monopole_realistic_ground_plane.py
new file mode 100644
index 0000000..8c18713
--- /dev/null
+++ b/PyNEC/example/monopole_realistic_ground_plane.py
@@ -0,0 +1,227 @@
+import numpy as np
+import scipy.optimize
+import matplotlib.pyplot as plt
+import matplotlib as mpl
+
+
+from PyNEC import *
+from antenna_util import *
+
+from context_clean import *
+
+import math
+
+brass_conductivity = 15600000 # mhos
+
+""" Optimize and plot the gains/VSWR of a simple monopole antenna, that has some brass wires added that act as ground. Visualises the path in the search space explored
+ by the minimization algorithm.
+ Inspired by an excercise for a course on antennas. The constraints are thus not really ideal I think... """
+
+# TODO: this probably could also be done by the GN/GD cards, but am I allowed?
+def add_ground_screen(geo, start_tag, length):
+ # The ground is modeled 6 radial wires (brass, radius 1 mm), equally spaced by 2pi/6, and their initial length is d = 2 cm
+
+ src = np.array([0, 0, 0])
+ radius = 0.001
+
+ for i in range(0, 6):
+ angle = i * (2*np.pi/6.0)
+ dst = np.array([length * np.cos(angle), length * np.sin(angle), 0])
+ # TODO: nr_segments (actually, more TODO: if nr_segments>=9, the geometry is invalid with this wire radius!)
+ geo.wire(tag_id=start_tag+i, nr_segments=5, src=src, dst=dst, radius=radius) # TODO: nr_segments
+
+def geometry_monopole_ground(freq_mhz, monopole_length, ground_wire_length, nr_segments):
+ wire_radius = 0.0005 # 0.5 mm
+
+ nec = context_clean(nec_context())
+ nec.set_extended_thin_wire_kernel(True)
+
+ geo = geometry_clean(nec.get_geometry())
+
+ bottom = np.array([0, 0, 0])
+ top = np.array([0, 0, monopole_length])
+
+ wire_tag = 1
+ geo.wire(tag_id=wire_tag, nr_segments=nr_segments, src=bottom, dst=top, radius=wire_radius)
+
+ add_ground_screen(geo, start_tag=2, length=ground_wire_length)
+
+ # Everything is in brass
+ nec.set_wire_conductivity(brass_conductivity)
+
+ nec.geometry_complete(ground_plane=False) # We added our own 'ground plane'
+
+ nec.voltage_excitation(wire_tag=wire_tag, segment_nr=1, voltage=1.0)
+
+ return nec
+
+def simulate_and_get_impedance(nec):
+ nec.set_frequency(design_freq_mhz)
+
+ nec.xq_card(0)
+
+ index = 0
+ return nec.get_input_parameters(index).get_impedance()
+
+# TODO: perhaps the length <= 0 can be added through additional constraints to minimize?
+
+design_freq_mhz = 2595
+lte_high_band = [2500, 2690]
+system_impedance = 50
+
+sampled_monopole_lenths = []
+sampled_ground_wire_lenths = []
+sampled_results = []
+
+def create_optimization_target(freq_mhz, nr_segments):
+ def target(args):
+ monopole_length, ground_wire_length = args[0], args[1]
+ if monopole_length <= 0 or ground_wire_length <= 0:
+ return float('inf')
+
+ result = 0
+ # VSWR should be < 2 in the lte_high_band
+ # So let's just sum (for now, TODO) the surplus for all the VSWRs that exceed it:
+ low = lte_high_band[0]
+ high = lte_high_band[1]
+ count = high-low
+
+ try:
+ nec = geometry_monopole_ground(design_freq_mhz, monopole_length, ground_wire_length, nr_segments)
+ nec.set_frequencies_linear(low, high, count=count)
+
+ nec.xq_card(0) # Execute simulation
+ except:
+ print("Caught exception")
+ return float('inf')
+
+ for idx in range(0, count):
+ ipt = nec.get_input_parameters(idx)
+ z = ipt.get_impedance()
+ s = vswr(z, system_impedance)
+ if s > 2:
+ result += s - 2
+
+ # And then maximize the gain in the horizontal plane (theta = pi/2 (90))
+ # TODO: might this be possible in one step?
+ gains_db = []
+ for freq in range(low, high+1):
+ nec = geometry_monopole_ground(design_freq_mhz, monopole_length, ground_wire_length, nr_segments)
+
+ nec.set_frequency(freq)
+ nec.radiation_pattern(thetas=Range(-90, -90, count=1), phis=Range(90,90,count=1))
+ rp = nec.context.get_radiation_pattern(0)
+
+ gains_db.append(rp.get_gain()[0][0])
+
+ #print gains_db
+ result -= np.exp(max(gains_db)) # maximize gain, hence the minus
+
+ global sampled_monopole_lenths, sampled_ground_wire_lenths, sampled_results
+ sampled_monopole_lenths.append(monopole_length)
+ sampled_ground_wire_lenths.append(ground_wire_length)
+ sampled_results.append(result)
+
+ print(result)
+
+ return result
+ return target
+
+if (__name__ == '__main__'):
+
+ wavelength = 299792e3/(design_freq_mhz*1000000)
+
+ initial_length = wavelength / 4 # quarter-wavelength monopole
+
+ print("Wavelength is %0.4fm, initial length is %0.4fm" % (wavelength, initial_length))
+
+ nr_segments = 15 # int(math.ceil(50*initial_length/wavelength))
+ #print nr_segments
+
+ ground_wire_length = 0.02
+ z = simulate_and_get_impedance(geometry_monopole_ground(design_freq_mhz, initial_length, ground_wire_length, nr_segments))
+
+ print("Initial impedance: (%6.1f,%+6.1fI) Ohms" % (z.real, z.imag))
+ print("VSWR @ 50 Ohm is %6.6f" % vswr(z, 50))
+
+ target = create_optimization_target(design_freq_mhz, nr_segments)
+ optimized_result = scipy.optimize.minimize(target, np.array([initial_length, ground_wire_length]), method='Nelder-Mead')
+
+ optimized_length, optimized_ground_wire_length = optimized_result.x[0], optimized_result.x[1]
+
+ geo_opt = geometry_monopole_ground(design_freq_mhz, optimized_length, optimized_ground_wire_length, nr_segments)
+ z = simulate_and_get_impedance(geo_opt)
+
+ print("Optimized length %6.6f m and ground screen radials of length %6.6f m, which gives an impedance of: (%6.4f,%+6.4fI) Ohms" % (optimized_length, optimized_ground_wire_length, z.real, z.imag))
+ print("Mismatch @ 50 Ohm is %6.6f" % mismatch(z, 50))
+ print("VSWR @ 50 Ohm is %6.6f" % vswr(z, 50))
+
+ geo_opt = geometry_monopole_ground(design_freq_mhz, optimized_length, optimized_ground_wire_length, nr_segments)
+ geo_opt.set_frequency(design_freq_mhz)
+ geo_opt.radiation_pattern(thetas=Range(-90, 90, count=180), phis=Range(0,0,count=1))
+
+ #get the radiation_pattern
+ rp = geo_opt.context.get_radiation_pattern(0)
+
+ # Gains are in decibels
+ gains_db = rp.get_gain()[:,0] # Is an array of theta,phi -> gain. In this case we only have one phi
+ thetas = rp.get_theta_angles() * 3.1415 / 180.0
+ phis = rp.get_phi_angles() * 3.1415 / 180.0
+
+ max_idx = gains_db.argmax()
+ max_gain = gains_db[max_idx]
+ max_theta = thetas[max_idx]
+ #print gains_db
+ print("Maximal gain is %2.2f dBi, at an angle of %2.2f" % (max_gain, max_theta * 180.0 / np.pi))
+
+ # Plot stuff
+
+ ax = plt.subplot(111, polar=True)
+
+ ax.plot(thetas, gains_db, color='r', linewidth=3)
+ ax.set_xticks(np.pi/180. * np.linspace(180, -180, 8, endpoint=False))
+ ax.set_theta_zero_location("N")
+ ax.set_rlim((-24.0, 9.0)) # TODO: automate. TODO: 4nec2 cheats and makes the lowest points (-999) the same as the lowest non-999 point :)
+ ax.set_rticks(np.linspace(-24, 9, 10, endpoint=False))
+ ax.grid(True)
+
+ ax.set_title("Gain pattern in the vertical plane", va='bottom')
+ plt.show()
+
+
+ low = lte_high_band[0]
+ high = lte_high_band[1]
+ count = high-low
+
+ # Reset the geometry, so that there is no spurious FR card left. (TODO this should really be not necessary)
+ geo_opt = geometry_monopole_ground(design_freq_mhz, optimized_length, optimized_ground_wire_length, nr_segments)
+ geo_opt.set_frequencies_linear(low, high, count=count)
+ geo_opt.xq_card(0) # Execute simulation
+
+ freqs = []
+ vswrs = []
+ for idx in range(0, count):
+ ipt = geo_opt.get_input_parameters(idx)
+ z = ipt.get_impedance()
+
+ freqs.append(ipt.get_frequency() / 1000000)
+ vswrs.append(vswr(z, system_impedance))
+
+ ax = plt.subplot(111)
+ ax.plot(freqs, vswrs)
+ ax.set_xlabel("Frequency (MHz)")
+ ax.set_ylabel("VSWR")
+ ax.grid(True)
+ plt.show()
+
+ ax = plt.subplot(111)
+ #print sampled_monopole_lenths
+ #print sampled_ground_wire_lenths
+ sampled_results = np.log(4 + np.array(sampled_results))
+ #print sampled_results
+ norm = mpl.colors.Normalize(vmin=min(sampled_results), vmax=max(sampled_results))
+ plt.scatter(sampled_monopole_lenths, sampled_ground_wire_lenths, c=sampled_results, cmap=mpl.cm.cool, norm=norm, edgecolors='None', alpha=0.75)
+ ax.set_title("Optimization path")
+ plt.colorbar()
+ plt.show()
+
diff --git a/PyNEC/example/optimized.py b/PyNEC/example/optimized.py
new file mode 100644
index 0000000..3d8d33f
--- /dev/null
+++ b/PyNEC/example/optimized.py
@@ -0,0 +1,61 @@
+#
+# Automatically tune antenna
+#
+import argparse
+import scipy.optimize
+import numpy as np
+
+import monopole
+from antenna_util import reflection_coefficient
+
+# A function that will be minimized when the impedance is 50 Ohms
+# We convert the height and antenna length to positive
+# numbers using exp. because otherwise the antenna will lie
+# below ground and cause an error in simulation.
+def target(x):
+ global freq, target_impedance
+ base_height = np.exp(x[0]) # Make it positive
+ length = np.exp(x[1]) # Make it positive
+ if (length > 10.0):
+ return 100
+ try:
+ z = monopole.impedance(freq, base_height, length)
+ return reflection_coefficient(z, z0=target_impedance)
+ except RuntimeError as re:
+ return 100
+
+def print_result(x, f, accepted):
+ log_base, log_length = x
+ base_height = np.exp(log_base)
+ length = np.exp(log_length)
+
+ if accepted:
+ print("Optimium base_height=%fm, h=%fm, impedance=%s Ohms" % \
+ (base_height, length, monopole.impedance(freq, base_height, length)))
+ else:
+ print("Local_minimum=%fm, h=%fm, impedance=%s Ohms" % \
+ (base_height, length, monopole.impedance(freq, base_height, length)))
+
+if __name__=="__main__":
+ parser = argparse.ArgumentParser(description='Optimize a monopole antenna.')
+ parser.add_argument('--target-impedance', type=float, default=50.0, help='Target for the optimized impedance')
+ parser.add_argument('--basinhopping', action="store_true", help='Use basinhopping')
+ args = parser.parse_args()
+
+ # Starting value
+ freq = 134.5
+ x0 = [-2.0, 1.0]
+ target_impedance = args.target_impedance
+
+ # Carry out the minimization
+
+ if args.basinhopping:
+ result = scipy.optimize.basinhopping(target, x0, disp=True, T=1.0, niter_success=10)
+ else:
+ result = scipy.optimize.minimize(target, x0, method='Nelder-Mead')
+
+ print("")
+ print("***********************************************************************")
+ print("* OPTIMIZATION COMPLETED *")
+ print("***********************************************************************")
+ print_result(result.x, None, True)
diff --git a/PyNEC/example/radiation_pattern.py b/PyNEC/example/radiation_pattern.py
new file mode 100644
index 0000000..39a3084
--- /dev/null
+++ b/PyNEC/example/radiation_pattern.py
@@ -0,0 +1,53 @@
+#
+# Simple vertical monopole antenna simulation using python-necpp
+# pip install necpp
+#
+from PyNEC import *
+
+import math
+
+def geometry(freq, base, length):
+ conductivity = 1.45e6 # Stainless steel
+ ground_conductivity = 0.002
+ ground_dielectric = 10
+
+ wavelength = 3e8/(1e6*freq)
+ n_seg = int(math.ceil(50*length/wavelength))
+
+ #nec = context_clean(nec_context())
+ nec = nec_context()
+
+ geo = nec.get_geometry()
+ geo.wire(1, n_seg, 0, 0, base, 0, 0, base+length, 0.002, 1.0, 1.0)
+ nec.geometry_complete(1)
+
+ nec.ld_card(5, 0, 0, 0, conductivity, 0.0, 0.0)
+ nec.gn_card(0, 0, ground_dielectric, ground_conductivity, 0, 0, 0, 0)
+ nec.fr_card(0, 1, freq, 0)
+
+ # Voltage excitation one third of the way along the wire
+ nec.ex_card(0, 0, int(n_seg/3), 0, 1.0, 0, 0, 0, 0, 0)
+
+ return nec
+
+nec = geometry(freq=123.4, base=0.5, length=4.0)
+nec.rp_card(calc_mode=0, n_theta=30, n_phi=30, output_format=0, normalization=0, D=0, A=0, theta0=0, delta_theta=10, phi0=0, delta_phi=5, radial_distance=0, gain_norm=0)
+nec.xq_card(0) # Execute simulation
+
+ipt = nec.get_input_parameters(0)
+z = ipt.get_impedance()
+print(("Impedance is {}".format(z)))
+
+rpt = nec.get_radiation_pattern(0)
+
+complex_e_field = rpt.get_e_theta()
+e = complex_e_field.reshape((30,30))
+
+print((complex_e_field.size))
+
+for t in range(30):
+ for p in range(30):
+ pass
+ print(e[t, p])
+
+print(dir(rpt))
diff --git a/PyNEC/example/test_ne_nh.py b/PyNEC/example/test_ne_nh.py
index 4a13d21..2b08584 100644
--- a/PyNEC/example/test_ne_nh.py
+++ b/PyNEC/example/test_ne_nh.py
@@ -53,4 +53,4 @@
ne = context.get_near_field_pattern(0)
nh = context.get_near_field_pattern(1)
-print ne
\ No newline at end of file
+print(ne)
\ No newline at end of file
diff --git a/PyNEC/interface_files/c_geometry.i b/PyNEC/interface_files/c_geometry.i
index 2008f7b..8c2a0b2 100644
--- a/PyNEC/interface_files/c_geometry.i
+++ b/PyNEC/interface_files/c_geometry.i
@@ -255,18 +255,13 @@ public:
\param ax3 The x_coordinate of corner 3.
\param ay3 The y_coordinate of corner 3.
\param az3 The z_coordinate of corner 3.
-
- \param ax4 The x_coordinate of corner 4.
- \param ay4 The x_coordinate of corner 4.
- \param az4 The x_coordinate of corner 4.
*/
void multiple_patch( int nx, int ny,
nec_float ax1, nec_float ay1, nec_float az1,
nec_float ax2, nec_float ay2, nec_float az2,
- nec_float ax3, nec_float ay3, nec_float az3,
- nec_float ax4, nec_float ay4, nec_float az4 )
+ nec_float ax3, nec_float ay3, nec_float az3 )
{
- return self->patch( nx, ny, ax1, ay1, az1, ax2, ay2, az2, ax3, ay3, az3, ax4, ay4, az4 );
+ return self->patch( nx, ny, ax1, ay1, az1, ax2, ay2, az2, ax3, ay3, az3, 0, 0, 0 );
}
}
diff --git a/PyNEC/interface_files/nec_context.i b/PyNEC/interface_files/nec_context.i
index bd1fb23..a010769 100644
--- a/PyNEC/interface_files/nec_context.i
+++ b/PyNEC/interface_files/nec_context.i
@@ -12,7 +12,39 @@ public:
c_geometry* get_geometry();
+ /*! \brief Get the maximum gain in dB.
+ This function requires a previous rp_card() method to have been called (with gain normalization requested)
+
+ \return The maximum gain in dB or -999.0 if no radiation pattern had been previously requested.
+ */
+ double get_gain(int freq_index, int theta_index, int phi_index);
+
+ double get_gain_max(int freq_index);
+ double get_gain_min(int freq_index);
+ double get_gain_mean(int freq_index);
+ double get_gain_sd(int freq_index);
+
+ /********************** RHCP ********************************/
+ double get_gain_rhcp_max(int freq_index);
+ double get_gain_rhcp_min(int freq_index);
+ double get_gain_rhcp_mean(int freq_index);
+ double get_gain_rhcp_sd(int freq_index);
+
+ /********************** LHCP ********************************/
+ double get_gain_lhcp_max(int freq_index);
+ double get_gain_lhcp_min(int freq_index);
+ double get_gain_lhcp_mean(int freq_index);
+ double get_gain_lhcp_sd(int freq_index);
+
+ /****************** IMPEDANCE CHARACTERISTICS *********************/
+
+ /*! \brief Impedance: Real Part */
+ double get_impedance_real(int freq_index);
+ /*! \brief Impedance: Imaginary Part */
+ double get_impedance_imag(int freq_index);
+
+
/*! Get the result antenna_input_parameters specified by index
\param index The index of the requested result.
diff --git a/PyNEC/pyproject.toml b/PyNEC/pyproject.toml
new file mode 100644
index 0000000..4973c84
--- /dev/null
+++ b/PyNEC/pyproject.toml
@@ -0,0 +1,3 @@
+[build-system]
+requires = ["setuptools", "wheel", "numpy"]
+build-backend = "setuptools.build_meta"
diff --git a/PyNEC/setup.py b/PyNEC/setup.py
index f11ea4d..bc33761 100644
--- a/PyNEC/setup.py
+++ b/PyNEC/setup.py
@@ -6,63 +6,79 @@
Author Tim Molteno. tim@molteno.net
"""
-from distutils.core import setup, Extension
import distutils.sysconfig
-from glob import glob
import os
-import numpy as np
+from glob import glob
+import numpy as np
+import setuptools
# Remove silly flags from the compilation to avoid warnings.
-cfg_vars = distutils.sysconfig.get_config_vars()
-for key, value in cfg_vars.items():
- if type(value) == str:
- cfg_vars[key] = value.replace("-Wstrict-prototypes", "")
+# cfg_vars = distutils.sysconfig.get_config_vars()
+# for key, value in cfg_vars.items():
+# if type(value) == str:
+# cfg_vars[key] = value.replace("-Wstrict-prototypes", "")
-# Generate a list of the sources.
+# Generate a list of the sources.
nec_sources = []
-nec_sources.extend([fn for fn in glob('../necpp_src/src/*.cpp')
- if not os.path.basename(fn).endswith('_tb.cpp')
- if not os.path.basename(fn).startswith('net_solve.cpp')
- if not os.path.basename(fn).startswith('nec2cpp.cpp')
- if not os.path.basename(fn).startswith('necDiff.cpp')])
+nec_sources.extend(
+ [
+ fn
+ for fn in glob("necpp_src/src/*.cpp")
+ if not os.path.basename(fn).endswith("_tb.cpp")
+ if not os.path.basename(fn).startswith("net_solve.cpp")
+ if not os.path.basename(fn).startswith("nec2cpp.cpp")
+ if not os.path.basename(fn).startswith("necDiff.cpp")
+ ]
+)
nec_sources.extend(glob("PyNEC_wrap.cxx"))
nec_headers = []
-nec_headers.extend(glob("../necpp_src/src/*.h"))
-nec_headers.extend(glob("../necpp_src/config.h"))
+nec_headers.extend(glob("necpp_src/src/*.h"))
+nec_headers.extend(glob("necpp_src/config.h"))
# At the moment, the config.h file is needed, and this should be generated from the ./configure
# command in the parent directory. Use ./configure --without-lapack to avoid dependance on LAPACK
#
-necpp_module = Extension('_PyNEC',
+necpp_module = setuptools.Extension(
+ "_PyNEC",
sources=nec_sources,
- include_dirs=[np.get_include(), '../necpp_src/src', '../necpp_src/'],
- extra_compile_args = ['-fPIC'],
- extra_link_args = ['-lstdc++'],
+ include_dirs=[np.get_include(), "necpp_src/src", "necpp_src/", "necpp_src/win32/"],
+ extra_compile_args=["-fPIC"],
+ extra_link_args=["-lstdc++"],
depends=nec_headers,
- define_macros=[('BUILD_PYTHON', '1'), ('NPY_NO_DEPRECATED_API','NPY_1_7_API_VERSION')]
- )
-
+ define_macros=[
+ ("BUILD_PYTHON", "1"),
+ ("NPY_NO_DEPRECATED_API", "NPY_1_7_API_VERSION"),
+ ],
+)
+with open("README.md", "r") as fh:
+ long_description = fh.read()
-setup (name = 'PyNEC',
- version = '1.7.0.3',
- author = "Tim Molteno",
- author_email = "tim@physics.otago.ac.nz",
- url = "http://github.com/tmolteno/necpp",
- keywords = "nec2 nec2++ antenna electromagnetism radio",
- description = "Python Antenna Simulation Module (nec2++) object-oriented interface",
- data_files=[('examples', ['example/test_rp.py'])],
- ext_modules = [necpp_module],
- requires = ['numpy'],
- py_modules = ["PyNEC"],
- license='GPLv2',
- classifiers=[
- "Development Status :: 5 - Production/Stable",
- "Topic :: Scientific/Engineering",
- "Topic :: Communications :: Ham Radio",
- "License :: OSI Approved :: GNU General Public License v2 (GPLv2)",
- "Intended Audience :: Science/Research"]
- )
+setuptools.setup(
+ name="PyNEC",
+ version="1.7.4",
+ author="Tim Molteno",
+ author_email="tim@physics.otago.ac.nz",
+ url="http://github.com/tmolteno/python-necpp",
+ keywords="nec2 nec2++ antenna electromagnetism radio",
+ description="Python Antenna Simulation Module (nec2++) object-oriented interface",
+ long_description=long_description,
+ long_description_content_type="text/markdown",
+ include_package_data=True,
+ data_files=[("examples", ["example/test_rp.py"])],
+ ext_modules=[necpp_module],
+ requires=["numpy"],
+ py_modules=["PyNEC"],
+ license="GPLv2",
+ classifiers=[
+ "Development Status :: 5 - Production/Stable",
+ "Topic :: Scientific/Engineering",
+ "Topic :: Communications :: Ham Radio",
+ "License :: OSI Approved :: GNU General Public License v2 (GPLv2)",
+ "Programming Language :: Python :: 3",
+ "Intended Audience :: Science/Research",
+ ],
+)
diff --git a/PyNEC/tests/Makefile b/PyNEC/tests/Makefile
index b67626b..a3792fa 100644
--- a/PyNEC/tests/Makefile
+++ b/PyNEC/tests/Makefile
@@ -1,2 +1,2 @@
test:
- python -m unittest discover
+ python3 -m unittest discover
diff --git a/PyNEC/tests/test_get_gain.py b/PyNEC/tests/test_get_gain.py
index dd1a4a1..054e940 100644
--- a/PyNEC/tests/test_get_gain.py
+++ b/PyNEC/tests/test_get_gain.py
@@ -1,4 +1,4 @@
-from PyNEC import *
+import PyNEC
import unittest
@@ -22,15 +22,18 @@ def test_example4(self):
RP 0 10 4 1001 0. 0. 10. 30.
EN
'''
- nec = nec_create()
- nec.sp_card(0, 0.1, 0.05, 0.05, 0.0, 0.0, 0.01)
- nec.sp_card(0, .05, .1, .05, 0.0, 90.0, 0.01)
- nec.gx_card(0, 110)
- nec.sp_card(0, 0.0, 0.0, 0.1, 90.0, 0.0, 0.04)
+ nec= PyNEC.nec_context()
+
+ geo = nec.get_geometry()
+
+ geo.sp_card(0, 0.1, 0.05, 0.05, 0.0, 0.0, 0.01)
+ geo.sp_card(0, .05, .1, .05, 0.0, 90.0, 0.01)
+ geo.gx_card(0, 110)
+ geo.sp_card(0, 0.0, 0.0, 0.1, 90.0, 0.0, 0.04)
- nec.wire(1, 4, 0., 0.0, 0.1, 0.0, 0.0, 0.3, .001, 1.0, 1.0)
- nec.wire(2, 2, 0., 0.0, 0.3, 0.15, 0.0, 0.3, .001, 1.0, 1.0)
- nec.wire(3, 2, 0., 0.0, 0.3, -.15, 0.0, 0.3, .001, 1.0, 1.0)
+ geo.wire(1, 4, 0., 0.0, 0.1, 0.0, 0.0, 0.3, .001, 1.0, 1.0)
+ geo.wire(2, 2, 0., 0.0, 0.3, 0.15, 0.0, 0.3, .001, 1.0, 1.0)
+ geo.wire(3, 2, 0., 0.0, 0.3, -.15, 0.0, 0.3, .001, 1.0, 1.0)
nec.geometry_complete(1)
nec.gn_card(1, 0, 0, 0, 0, 0, 0, 0)
@@ -38,18 +41,16 @@ def test_example4(self):
nec.ex_card(0, 1, 1, 0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0)
nec.rp_card(0,10,4,1,0,0,1,0.0,0.0,10.0,30.0, 0, 0)
- self.assertAlmostEqual(nec_gain_max(nec,0),5.076,3)
+ self.assertAlmostEqual(nec.get_gain_max(0),5.076,3)
gmax = -999.0
for theta_index in range(0,10):
for phi_index in range(0,4):
- g = nec_gain(nec,0,theta_index, phi_index)
+ g = nec.get_gain(0,theta_index, phi_index)
gmax = max(g, gmax)
- self.assertAlmostEqual(gmax, nec_gain_max(nec,0), 5 )
-
- nec_delete(nec)
+ self.assertAlmostEqual(gmax, nec.get_gain_max(0), 5 )
if __name__ == '__main__':
diff --git a/README.md b/README.md
index 1c997e4..9e20312 100644
--- a/README.md
+++ b/README.md
@@ -1,63 +1,12 @@
# python-necpp: Antenna simulation in python
-This module allows you to do antenna simulations in Python using the nec2++ antenna
-simulation package. This is a wrapper using SWIG of the C interface, so the syntax
-is quite simple. Have a look at the file necpp_src/example/test.py, for an example of how this
-library can be used.
+This repository contains two wrappers for the [http://github.com/tmolteno/necpp nec2++] antenna simulation package:
-Tim Molteno. tim@physics.otago.ac.nz
+* necpp/ contains a wrapper using SWIG of the C interface (Python module name: necpp).
+* PyNEC/ contains a wrapper of the C++ interfaces (Python module name: PyNEC). The example/ directory furthermore contains some nicer, more readable Python wrappers that make toying around with NEC a less painful experience.
-## NEWS
+Both are based on Tim Molteno (tim@physics.otago.ac.nz)'s code with major cleanup by Bart Coppens.
-* Version 1.7.0 includes support for getting elements of radiation patterns. At the moment
- this is just through the function nec_get_gain().
-* Version 1.7.0.3 includes nec_medium_parameters(). You could simulate an antenna in seawater!
+## TODOs
-
-## Install
-
-As of version 1.6.1.2 swig is no longer required for installation. Simply use PIP as
-follows:
-
- pip install necpp
-
-## Documentation
-
-Try help(necpp) to list the available functions. The functions available are documented in the C-style API of nec2++.
-This is [available here](http://tmolteno.github.io/necpp/libnecpp_8h.html)
-
-## Using
-
-The following code calculates the impedance of a simple vertical monopole antenna
-over a perfect ground.
-
- import necpp
-
- def handle_nec(result):
- if (result != 0):
- print necpp.nec_error_message()
-
- def impedance(frequency, z0, height):
-
- nec = necpp.nec_create()
- handle_nec(necpp.nec_wire(nec, 1, 17, 0, 0, z0, 0, 0, z0+height, 0.1, 1, 1))
- handle_nec(necpp.nec_geometry_complete(nec, 1, 0))
- handle_nec(necpp.nec_gn_card(nec, 1, 0, 0, 0, 0, 0, 0, 0))
- handle_nec(necpp.nec_fr_card(nec, 0, 1, frequency, 0))
- handle_nec(necpp.nec_ex_card(nec, 0, 0, 1, 0, 1.0, 0, 0, 0, 0, 0))
- handle_nec(necpp.nec_rp_card(nec, 0, 90, 1, 0,5,0,0, 0, 90, 1, 0, 0, 0))
- result_index = 0
-
- z = complex(necpp.nec_impedance_real(nec,result_index),
- necpp.nec_impedance_imag(nec,result_index))
-
- necpp.nec_delete(nec)
- return z
-
- if (__name__ == 'main'):
- z = impedance(frequency = 34.5, z0 = 0.5, height = 4.0)
- print "Impedance \t(%6.1f,%+6.1fI) Ohms" % (z.real, z.imag)
-
-## More Information
-
-Have a look at [http://github.com/tmolteno/necpp] for more information on using nec2++.
+The cleaner API should really be **ported to C++**, so the clean wrappers get automatically generated, and C++ can use the same cleaner interface. But for now, I'm happy with the Python wrapper :)
diff --git a/build_wheels.sh b/build_wheels.sh
new file mode 100755
index 0000000..d19092b
--- /dev/null
+++ b/build_wheels.sh
@@ -0,0 +1,22 @@
+#!/bin/bash
+set -e -x
+
+# Install a system package required by our library
+# yum install -y atlas-devel
+
+# Compile wheels
+for PYBIN in /opt/python/*/bin; do
+ "${PYBIN}/pip" install -r /io/dev-requirements.txt
+ "${PYBIN}/pip" wheel -e /io/PyNEC/ -w wheelhouse/
+done
+
+# Bundle external shared libraries into the wheels
+for whl in wheelhouse/*.whl; do
+ auditwheel repair "$whl" --plat $PLAT -w /io/wheelhouse/
+done
+
+# Install packages and test
+for PYBIN in /opt/python/*/bin/; do
+ "${PYBIN}/pip" install PyNEC --no-index -f /io/wheelhouse
+ (cd "$HOME"; "${PYBIN}/nosetests" pymanylinuxdemo)
+done
diff --git a/dev-requirements.txt b/dev-requirements.txt
new file mode 100644
index 0000000..24ce15a
--- /dev/null
+++ b/dev-requirements.txt
@@ -0,0 +1 @@
+numpy
diff --git a/necpp/.gitignore b/necpp/.gitignore
new file mode 100644
index 0000000..d66c9ee
--- /dev/null
+++ b/necpp/.gitignore
@@ -0,0 +1,3 @@
+necpp_wrap.c
+necpp_src
+necpp.py
diff --git a/INSTALL.md b/necpp/INSTALL.md
similarity index 86%
rename from INSTALL.md
rename to necpp/INSTALL.md
index 18e1ba9..499cc1d 100644
--- a/INSTALL.md
+++ b/necpp/INSTALL.md
@@ -4,7 +4,7 @@ PyPI module for nec2++
This module allows you to do antenna simulations in Python using the nec2++ antenna
simulation package. This is a wrapper using SWIG of the C interface, so the syntax
is quite simple. Have a look at the file test.py, for an example of how this
-library can be used.
+library can be used. Other examples are in the 'examples' directory.
### Author
@@ -30,6 +30,8 @@ To update the submodule to the latest necpp
### Converting from MarkDown
sudo aptitude install pandoc swig
+
+ pandoc --to=rst README.md > README.txt
### Testing
@@ -43,4 +45,4 @@ This will run SWIG a source distribution tarball
http://peterdowns.com/posts/first-time-with-pypi.html
- python setup.py sdist upload -r pypitest
\ No newline at end of file
+ python setup.py sdist upload -r pypitest
diff --git a/necpp/LICENCE.txt b/necpp/LICENCE.txt
new file mode 100644
index 0000000..d6a9326
--- /dev/null
+++ b/necpp/LICENCE.txt
@@ -0,0 +1,340 @@
+GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users. This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it. (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.) You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+ To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have. You must make sure that they, too, receive or can get the
+source code. And you must show them these terms so they know their
+rights.
+
+ We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+ Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+ Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary. To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ GNU GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License. The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language. (Hereinafter, translation is included without limitation in
+the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+ 1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+ 2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) You must cause the modified files to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ b) You must cause any work that you distribute or publish, that in
+ whole or in part contains or is derived from the Program or any
+ part thereof, to be licensed as a whole at no charge to all third
+ parties under the terms of this License.
+
+ c) If the modified program normally reads commands interactively
+ when run, you must cause it, when started running for such
+ interactive use in the most ordinary way, to print or display an
+ announcement including an appropriate copyright notice and a
+ notice that there is no warranty (or else, saying that you provide
+ a warranty) and that users may redistribute the program under
+ these conditions, and telling the user how to view a copy of this
+ License. (Exception: if the Program itself is interactive but
+ does not normally print such an announcement, your work based on
+ the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+ a) Accompany it with the complete corresponding machine-readable
+ source code, which must be distributed under the terms of Sections
+ 1 and 2 above on a medium customarily used for software interchange; or,
+
+ b) Accompany it with a written offer, valid for at least three
+ years, to give any third party, for a charge no more than your
+ cost of physically performing source distribution, a complete
+ machine-readable copy of the corresponding source code, to be
+ distributed under the terms of Sections 1 and 2 above on a medium
+ customarily used for software interchange; or,
+
+ c) Accompany it with the information you received as to the offer
+ to distribute corresponding source code. (This alternative is
+ allowed only for noncommercial distribution and only if you
+ received the program in object code or executable form with such
+ an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it. For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable. However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License. Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+ 5. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Program or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+ 6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+ 7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all. For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded. In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+ 9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation. If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+ 10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission. For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this. Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+ NO WARRANTY
+
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ {description}
+ Copyright (C) {year} {fullname}
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+ Gnomovision version 69, Copyright (C) year name of author
+ Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+ `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+ {signature of Ty Coon}, 1 April 1989
+ Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs. If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.
+
diff --git a/MANIFEST.in b/necpp/MANIFEST.in
similarity index 57%
rename from MANIFEST.in
rename to necpp/MANIFEST.in
index 4092210..bb7bd4c 100644
--- a/MANIFEST.in
+++ b/necpp/MANIFEST.in
@@ -1,7 +1,7 @@
include LICENCE.txt
include necpp_src/src/*.h
-
include necpp_src/config.h
-include necpp_src/example/test.py
\ No newline at end of file
+include necpp_src/example/test.py
+include example/*.py
diff --git a/necpp/Makefile b/necpp/Makefile
new file mode 100644
index 0000000..9f1f3e5
--- /dev/null
+++ b/necpp/Makefile
@@ -0,0 +1,20 @@
+build:
+ sh build.sh
+ python3 setup.py sdist
+ #python3 setup.py bdist_wheel
+
+clean:
+ rm -rf necpp_src
+ rm -rf build
+ rm -rf dist
+
+test-upload:
+ python3 -m pip install --user --upgrade twine
+
+ python3 -m twine upload --repository-url https://test.pypi.org/legacy/ dist/*
+
+upload:
+ python3 -m twine upload dist/*
+
+test-install:
+ python3 -m pip install --index-url https://test.pypi.org/simple/ --no-deps necpp --upgrade
diff --git a/necpp/README.md b/necpp/README.md
new file mode 100644
index 0000000..28ad3c9
--- /dev/null
+++ b/necpp/README.md
@@ -0,0 +1,67 @@
+# python-necpp: Antenna simulation in python
+
+This module allows you to do antenna simulations in Python using the nec2++ antenna
+simulation package.
+
+This is a wrapper using SWIG of the C interface, so the syntax
+is quite simple. Have a look at the file necpp_src/example/test.py, for an example of how this
+library can be used.
+
+Tim Molteno. tim@physics.otago.ac.nz
+
+## NEWS
+
+* Version 1.7.3 Includes Python3 support. Also some bug fixes and updating nec++ to the
+ latest version.
+* Version 1.7.0.3 includes nec_medium_parameters(). You could simulate an antenna in seawater!
+* Version 1.7.0 includes support for getting elements of radiation patterns. At the moment
+ this is just through the function nec_get_gain().
+
+
+## Install
+
+As of version 1.6.1.2 swig is no longer required for installation. Simply use PIP as
+follows:
+
+ pip install necpp
+
+## Documentation
+
+Try help(necpp) to list the available functions. The functions available are documented in the C-style API of nec2++.
+This is [available here](http://tmolteno.github.io/necpp/libnecpp_8h.html)
+
+## Using
+
+The following code calculates the impedance of a simple vertical monopole antenna
+over a perfect ground.
+
+ import necpp
+
+ def handle_nec(result):
+ if (result != 0):
+ print necpp.nec_error_message()
+
+ def impedance(frequency, z0, height):
+
+ nec = necpp.nec_create()
+ handle_nec(necpp.nec_wire(nec, 1, 17, 0, 0, z0, 0, 0, z0+height, 0.1, 1, 1))
+ handle_nec(necpp.nec_geometry_complete(nec, 1, 0))
+ handle_nec(necpp.nec_gn_card(nec, 1, 0, 0, 0, 0, 0, 0, 0))
+ handle_nec(necpp.nec_fr_card(nec, 0, 1, frequency, 0))
+ handle_nec(necpp.nec_ex_card(nec, 0, 0, 1, 0, 1.0, 0, 0, 0, 0, 0))
+ handle_nec(necpp.nec_rp_card(nec, 0, 90, 1, 0,5,0,0, 0, 90, 1, 0, 0, 0))
+ result_index = 0
+
+ z = complex(necpp.nec_impedance_real(nec,result_index),
+ necpp.nec_impedance_imag(nec,result_index))
+
+ necpp.nec_delete(nec)
+ return z
+
+ if (__name__ == 'main'):
+ z = impedance(frequency = 34.5, z0 = 0.5, height = 4.0)
+ print "Impedance \t(%6.1f,%+6.1fI) Ohms" % (z.real, z.imag)
+
+## More Information
+
+Have a look at [http://github.com/tmolteno/necpp] for more information on using nec2++.
diff --git a/build.sh b/necpp/build.sh
similarity index 54%
rename from build.sh
rename to necpp/build.sh
index 7052360..a99d63b 100755
--- a/build.sh
+++ b/necpp/build.sh
@@ -1,12 +1,13 @@
#!/bin/bash
# Script to build the nec2++ python module.
git submodule update --remote
-pushd necpp_src
+ln -s ../necpp_src .
+DIR=`pwd`
+cd necpp_src
make -f Makefile.git
./configure --without-lapack
-popd
-pandoc -o README.txt README.md
-PYTHON=python
-swig -v -Inecpp_src/src/ -python necpp.i
-python setup.py build
+cd ${DIR}
+PYTHON=python3
+swig3.0 -v -Inecpp_src/src/ -python necpp.i
+python3 setup.py build
#sudo python setup.py install
diff --git a/necpp/example/.gitignore b/necpp/example/.gitignore
new file mode 100644
index 0000000..0d20b64
--- /dev/null
+++ b/necpp/example/.gitignore
@@ -0,0 +1 @@
+*.pyc
diff --git a/example/Makefile b/necpp/example/Makefile
similarity index 100%
rename from example/Makefile
rename to necpp/example/Makefile
diff --git a/example/README.md b/necpp/example/README.md
similarity index 100%
rename from example/README.md
rename to necpp/example/README.md
diff --git a/example/antenna_util.py b/necpp/example/antenna_util.py
similarity index 100%
rename from example/antenna_util.py
rename to necpp/example/antenna_util.py
diff --git a/necpp/example/different_material.py b/necpp/example/different_material.py
new file mode 100644
index 0000000..92b87c1
--- /dev/null
+++ b/necpp/example/different_material.py
@@ -0,0 +1,50 @@
+from necpp import *
+import math
+
+def handle_nec(result):
+ if (result != 0):
+ print(nec_error_message())
+
+def geometry(freq, base, length):
+
+ conductivity = 1.45e6 # Stainless steel
+ ground_conductivity = 0.002
+ ground_dielectric = 10
+
+ wavelength = 3e8/(1e6*freq)
+ n_seg = int(math.ceil(50*length/wavelength))
+ nec = nec_create()
+
+ '''
+ \brief Set the prameters of the medium (permittivity and permeability)
+
+ \param permittivity The electric permittivity of the medium (in farads per meter)
+ \param permeability The magnetic permeability of the medium (in henries per meter)
+
+ \remark From these parameters a speed of light is chosen.
+ '''
+ permittivity = 8.8e-12 # Farads per meter
+ permeability = 4*math.pi*1e-7
+ handle_nec(nec_medium_parameters(nec, 2.0*permittivity, permeability))
+
+ handle_nec(nec_wire(nec, 1, n_seg, 0, 0, base, 0, 0, base+length, 0.002, 1.0, 1.0))
+ handle_nec(nec_geometry_complete(nec, 1))
+ handle_nec(nec_ld_card(nec, 5, 0, 0, 0, conductivity, 0.0, 0.0))
+ handle_nec(nec_gn_card(nec, 0, 0, ground_dielectric, ground_conductivity, 0, 0, 0, 0))
+ handle_nec(nec_fr_card(nec, 0, 1, freq, 0))
+ # Voltage excitation one third of the way along the wire
+ handle_nec(nec_ex_card(nec, 0, 0, int(n_seg/3), 0, 1.0, 0, 0, 0, 0, 0))
+
+ return nec
+
+def impedance(freq, base, length):
+ nec = geometry(freq, base, length)
+ handle_nec(nec_xq_card(nec, 0)) # Execute simulation
+ index = 0
+ z = complex(nec_impedance_real(nec,index), nec_impedance_imag(nec,index))
+ nec_delete(nec)
+ return z
+
+if (__name__ == '__main__'):
+ z = impedance(freq = 134.5, base = 0.5, length = 4.0)
+ print("Impedance at base=%0.2f, length=%0.2f : (%6.1f,%+6.1fI) Ohms" % (0.5, 4.0, z.real, z.imag))
diff --git a/necpp/example/impedance_plot.py b/necpp/example/impedance_plot.py
new file mode 100644
index 0000000..a882ce4
--- /dev/null
+++ b/necpp/example/impedance_plot.py
@@ -0,0 +1,25 @@
+#
+# Plot of reflection coefficient vs antenna length for a fixed base height.
+#
+import monopole
+import numpy as np
+import pylab as plt
+from antenna_util import reflection_coefficient
+
+lengths = np.linspace(0.2, 5.0, 270)
+reflections = []
+z0 = 50
+
+for l in lengths:
+ freq = 134.5
+ z = monopole.impedance(freq, base=0.5, length=l)
+ reflections.append(reflection_coefficient(z, z0))
+
+plt.plot(lengths, reflections)
+plt.xlabel("Antenna length (m)")
+plt.ylabel("Reflection coefficient")
+plt.title("Reflection coefficient vs length (base_height=0.5m)")
+plt.grid(True)
+plt.show()
+plt.savefig("reflection_coefficient.png")
+
diff --git a/example/monopole.py b/necpp/example/monopole.py
similarity index 84%
rename from example/monopole.py
rename to necpp/example/monopole.py
index 725cb4d..4ccf0a8 100644
--- a/example/monopole.py
+++ b/necpp/example/monopole.py
@@ -7,7 +7,7 @@
def handle_nec(result):
if (result != 0):
- print nec_error_message()
+ print(nec_error_message())
def geometry(freq, base, length):
@@ -24,7 +24,7 @@ def geometry(freq, base, length):
handle_nec(nec_gn_card(nec, 0, 0, ground_dielectric, ground_conductivity, 0, 0, 0, 0))
handle_nec(nec_fr_card(nec, 0, 1, freq, 0))
# Voltage excitation one third of the way along the wire
- handle_nec(nec_ex_card(nec, 0, 0, n_seg/3, 0, 1.0, 0, 0, 0, 0, 0))
+ handle_nec(nec_ex_card(nec, 0, 0, int(n_seg/3), 0, 1.0, 0, 0, 0, 0, 0))
return nec
@@ -38,4 +38,4 @@ def impedance(freq, base, length):
if (__name__ == '__main__'):
z = impedance(freq = 134.5, base = 0.5, length = 4.0)
- print "Impedance at base=%0.2f, length=%0.2f : (%6.1f,%+6.1fI) Ohms" % (0.5, 4.0, z.real, z.imag)
+ print("Impedance at base=%0.2f, length=%0.2f : (%6.1f,%+6.1fI) Ohms" % (0.5, 4.0, z.real, z.imag))
diff --git a/example/optimized.py b/necpp/example/optimized.py
similarity index 85%
rename from example/optimized.py
rename to necpp/example/optimized.py
index 96317eb..2d1778d 100644
--- a/example/optimized.py
+++ b/necpp/example/optimized.py
@@ -27,5 +27,4 @@ def target(x):
base_height = np.exp(log_base)
length = np.exp(log_length)
-print "Optimium base_height=%fm, h=%fm, impedance=%s Ohms" % \
- (base_height, length, monopole.impedance(freq, base_height, length))
+print("Optimium base_height={}m, h={}m, impedance={} Ohms".format(base_height, length, monopole.impedance(freq, base_height, length)))
diff --git a/necpp.i b/necpp/necpp.i
similarity index 100%
rename from necpp.i
rename to necpp/necpp.i
diff --git a/necpp/pyproject.toml b/necpp/pyproject.toml
new file mode 100644
index 0000000..9787c3b
--- /dev/null
+++ b/necpp/pyproject.toml
@@ -0,0 +1,3 @@
+[build-system]
+requires = ["setuptools", "wheel"]
+build-backend = "setuptools.build_meta"
diff --git a/setup.cfg b/necpp/setup.cfg
similarity index 100%
rename from setup.cfg
rename to necpp/setup.cfg
diff --git a/necpp/setup.py b/necpp/setup.py
new file mode 100644
index 0000000..896ed98
--- /dev/null
+++ b/necpp/setup.py
@@ -0,0 +1,72 @@
+#!/usr/bin/env python
+
+"""
+setup.py file for necpp Python module.
+"""
+
+import os
+from glob import glob
+
+from setuptools import Extension, setup
+
+nec_sources = []
+nec_sources.extend(
+ [
+ fn
+ for fn in glob("necpp_src/src/*.cpp")
+ if not os.path.basename(fn).endswith("_tb.cpp")
+ if not os.path.basename(fn).startswith("net_solve.cpp")
+ if not os.path.basename(fn).startswith("nec2cpp.cpp")
+ if not os.path.basename(fn).startswith("necDiff.cpp")
+ ]
+)
+nec_sources.extend(glob("necpp_wrap.c"))
+
+nec_headers = []
+nec_headers.extend(glob("necpp_src/src/*.h"))
+nec_headers.extend(glob("necpp_src/config.h"))
+
+
+# At the moment, the config.h file is needed, and this should be generated from the ./configure
+# command in the parent directory. Use ./configure --without-lapack to avoid dependance on LAPACK
+#
+necpp_module = Extension(
+ "_necpp",
+ sources=nec_sources,
+ include_dirs=["necpp_src/src/", "necpp_src/"],
+ depends=nec_headers,
+ define_macros=[("BUILD_PYTHON", "1")],
+)
+
+with open("README.md") as f:
+ readme = f.read()
+
+setup(
+ name="necpp",
+ version="1.7.4",
+ author="Tim Molteno",
+ author_email="tim@physics.otago.ac.nz",
+ url="http://github.com/tmolteno/necpp",
+ keywords="nec2 nec2++ antenna electromagnetism radio",
+ description="Python Antenna Simulation Module (nec2++) C-style interface",
+ long_description=readme,
+ long_description_content_type="text/markdown",
+ include_package_data=True,
+ data_files=[("examples", ["necpp_src/example/test.py"])],
+ ext_modules=[necpp_module],
+ py_modules=["necpp"],
+ license="GPLv2",
+ classifiers=[
+ "Development Status :: 5 - Production/Stable",
+ "Topic :: Scientific/Engineering",
+ "Topic :: Communications :: Ham Radio",
+ "License :: OSI Approved :: GNU General Public License v2 (GPLv2)",
+ "Programming Language :: Python :: 2",
+ "Programming Language :: Python :: 2.7",
+ "Programming Language :: Python :: 3",
+ "Programming Language :: Python :: 3.3",
+ "Programming Language :: Python :: 3.4",
+ "Programming Language :: Python :: 3.5",
+ "Intended Audience :: Science/Research",
+ ],
+)
diff --git a/necpp_src b/necpp_src
index 56f10d2..049c556 160000
--- a/necpp_src
+++ b/necpp_src
@@ -1 +1 @@
-Subproject commit 56f10d2bfe56e1ef7bbfda81de8fe421a3be4b83
+Subproject commit 049c556cb10769410b760dcea7ef341a3e08f78b
diff --git a/setup.py b/setup.py
deleted file mode 100644
index b307b47..0000000
--- a/setup.py
+++ /dev/null
@@ -1,52 +0,0 @@
-#!/usr/bin/env python
-
-"""
-setup.py file for necpp Python module
-"""
-
-from distutils.core import setup, Extension
-from glob import glob
-import os
-
-nec_sources = []
-nec_sources.extend([fn for fn in glob('necpp_src/src/*.cpp')
- if not os.path.basename(fn).endswith('_tb.cpp')
- if not os.path.basename(fn).startswith('net_solve.cpp')
- if not os.path.basename(fn).startswith('nec2cpp.cpp')
- if not os.path.basename(fn).startswith('necDiff.cpp')])
-nec_sources.extend(glob("necpp_wrap.c"))
-
-nec_headers = []
-nec_headers.extend(glob("necpp_src/src/*.h"))
-nec_headers.extend(glob("necpp_src/config.h"))
-
-
-# At the moment, the config.h file is needed, and this should be generated from the ./configure
-# command in the parent directory. Use ./configure --without-lapack to avoid dependance on LAPACK
-#
-necpp_module = Extension('_necpp',
- sources=nec_sources,
- include_dirs=['necpp_src/src/', 'necpp_src/'],
- depends=nec_headers,
- define_macros=[('BUILD_PYTHON', '1')]
- )
-
-
-setup (name = 'necpp',
- version = '1.7.0.3',
- author = "Tim Molteno",
- author_email = "tim@physics.otago.ac.nz",
- url = "http://github.com/tmolteno/necpp",
- keywords = "nec2 nec2++ antenna electromagnetism radio",
- description = "Python Antenna Simulation Module (nec2++) C-style interface",
- data_files=[('examples', ['necpp_src/example/test.py'])],
- ext_modules = [necpp_module],
- py_modules = ["necpp"],
- license='GPLv2',
- classifiers=[
- "Development Status :: 5 - Production/Stable",
- "Topic :: Scientific/Engineering",
- "Topic :: Communications :: Ham Radio",
- "License :: OSI Approved :: GNU General Public License v2 (GPLv2)",
- "Intended Audience :: Science/Research"]
- )