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/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/LICENSE.txt b/PyNEC/LICENCE.txt similarity index 100% rename from PyNEC/LICENSE.txt rename to PyNEC/LICENCE.txt diff --git a/PyNEC/MANIFEST.in b/PyNEC/MANIFEST.in index 5b3d3ed..181a486 100644 --- a/PyNEC/MANIFEST.in +++ b/PyNEC/MANIFEST.in @@ -1,8 +1,8 @@ include LICENCE.txt -include ../necpp_src/src/*.h +include necpp_src/src/*.h -include ../necpp_src/config.h +include necpp_src/config.h 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 56bc5fa..1829c1d 100644 --- a/PyNEC/README.md +++ b/PyNEC/README.md @@ -1,7 +1,7 @@ # 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 @@ -52,21 +52,44 @@ 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 -## Testing +* [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.* - python example/test_rp.py +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 + +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. 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/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/context_clean.py b/PyNEC/example/context_clean.py index 0242073..4f4a3d3 100644 --- a/PyNEC/example/context_clean.py +++ b/PyNEC/example/context_clean.py @@ -19,7 +19,7 @@ def __init__(self, start, stop, count=None, delta=None): def debug(card, *args): if do_debug: stringified = " , ".join([str(a) for a in args]) - print "%s %s" % (card, stringified) + print("%s %s" % (card, stringified)) class context_clean(object): def __init__(self, context): @@ -101,10 +101,10 @@ def get_geometry(self): def set_extended_thin_wire_kernel(self, enable): if enable: debug ("EK", 0) - self.context.set_extended_thin_wire_kernel(1) + self.context.set_extended_thin_wire_kernel(True) else: debug ("EK", -1) - self.context.set_extended_thin_wire_kernel(0) + self.context.set_extended_thin_wire_kernel(False) def geometry_complete(self, ground_plane, current_expansion=True): no_ground_plane = 0 diff --git a/PyNEC/example/dipole.py b/PyNEC/example/dipole.py index abe1c26..780c713 100644 --- a/PyNEC/example/dipole.py +++ b/PyNEC/example/dipole.py @@ -89,13 +89,13 @@ def matched_range_around(nec, count, center_freq, system_impedance): initial_length = wavelength / 2 # TODO - print "Wavelength is %0.4fm, initial length is %0.4fm" % (wavelength, initial_length) + 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) + 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])) @@ -103,8 +103,8 @@ def matched_range_around(nec, count, center_freq, system_impedance): 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) + 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) @@ -117,10 +117,10 @@ def matched_range_around(nec, count, center_freq, system_impedance): 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 + 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]) + 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 = [] @@ -140,5 +140,5 @@ def matched_range_around(nec, count, center_freq, system_impedance): plt.ylabel("VSWR") plt.grid(True) filename = "vswr_%i_MHz.pdf" % system_impedance - print "Saving plot to file: %s" % filename + print("Saving plot to file: %s" % filename) plt.savefig(filename) diff --git a/PyNEC/example/logperiodic_opt.py b/PyNEC/example/logperiodic_opt.py index 5c54d67..0a4ecd5 100644 --- a/PyNEC/example/logperiodic_opt.py +++ b/PyNEC/example/logperiodic_opt.py @@ -15,7 +15,7 @@ brass_conductivity = 15600000 # mhos -tl_impedance = 75 +tl_impedance = 75.0 def geometry_logperiodic(l_1, x_1, tau): """ @@ -45,7 +45,7 @@ def geometry_logperiodic(l_1, x_1, tau): 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 + 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]) @@ -64,7 +64,7 @@ def geometry_logperiodic(l_1, x_1, tau): # The 6th tag is the smallest tag is the source element for dipole in range(0, dipoles_count - 1): - src_tag = 1 + dipole # NEC indexing + src_tag = int(1 + dipole) # NEC indexing src_seg = dipole_center_segs[src_tag] dst_tag = src_tag + 1 @@ -129,10 +129,10 @@ def target(args): result = vswr_score - gains_score except: - print "Caught exception" + print("Caught exception") return float('inf') - print result + print(result) return result return target @@ -169,8 +169,8 @@ def show_report(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) + 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) @@ -219,12 +219,12 @@ def show_report(l1, x1, tau): initial_x1 = wavelength / 2 initial_tau = 0.8 - print "Wavelength is %0.4fm, initial length is %0.4fm" % (wavelength, initial_l1) + print("Wavelength is %0.4fm, initial length is %0.4fm" % (wavelength, initial_l1)) - print "Unoptimized antenna..." + print("Unoptimized antenna...") show_report(initial_l1, initial_x1, initial_tau) - print "Optimizing antenna..." + print("Optimizing antenna...") target = create_optimization_target() # Optimize local minimum only with gradient desce @@ -238,7 +238,7 @@ def show_report(l1, x1, tau): # 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..." + 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 index 9cf166d..cc0eb4b 100644 --- a/PyNEC/example/monopole.py +++ b/PyNEC/example/monopole.py @@ -1,6 +1,6 @@ # # Simple vertical monopole antenna simulation using python-necpp -# pip install necpp +# pip install PyNEC # from PyNEC import * @@ -9,40 +9,40 @@ import math def geometry(freq, base, length): - conductivity = 1.45e6 # Stainless steel - ground_conductivity = 0.002 - ground_dielectric = 10 + conductivity = 1.45e6 # Stainless steel + ground_conductivity = 0.002 + ground_dielectric = 10 - wavelength = 3e8/(1e6*freq) - n_seg = int(math.ceil(50*length/wavelength)) + wavelength = 3e8/(1e6*freq) + n_seg = int(math.ceil(50*length/wavelength)) - nec = context_clean(nec_context()) + 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) + 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_all_wires_conductivity(conductivity) - nec.set_finite_ground(ground_dielectric, ground_conductivity) - nec.set_frequency(freq) + 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) + # 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 + return nec def impedance(freq, base, length): - nec = geometry(freq, base, length) - nec.xq_card(0) # Execute simulation + nec = geometry(freq, base, length) + nec.xq_card(0) # Execute simulation - index = 0 + index = 0 - ipt = nec.get_input_parameters(index) - z = ipt.get_impedance() + ipt = nec.get_input_parameters(index) + z = ipt.get_impedance() - return z + 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) + 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 index 2d41099..8c18713 100644 --- a/PyNEC/example/monopole_realistic_ground_plane.py +++ b/PyNEC/example/monopole_realistic_ground_plane.py @@ -92,7 +92,7 @@ def target(args): nec.xq_card(0) # Execute simulation except: - print "Caught exception" + print("Caught exception") return float('inf') for idx in range(0, count): @@ -122,7 +122,7 @@ def target(args): sampled_ground_wire_lenths.append(ground_wire_length) sampled_results.append(result) - print result + print(result) return result return target @@ -133,7 +133,7 @@ def target(args): initial_length = wavelength / 4 # quarter-wavelength monopole - print "Wavelength is %0.4fm, initial length is %0.4fm" % (wavelength, initial_length) + 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 @@ -141,8 +141,8 @@ def target(args): 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) + 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') @@ -152,9 +152,9 @@ def target(args): 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) + 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) @@ -172,7 +172,7 @@ def target(args): 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) + print("Maximal gain is %2.2f dBi, at an angle of %2.2f" % (max_gain, max_theta * 180.0 / np.pi)) # Plot stuff diff --git a/PyNEC/example/optimized.py b/PyNEC/example/optimized.py index 96317eb..3d8d33f 100644 --- a/PyNEC/example/optimized.py +++ b/PyNEC/example/optimized.py @@ -1,9 +1,11 @@ # # Automatically tune antenna # -import monopole +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 @@ -11,21 +13,49 @@ # numbers using exp. because otherwise the antenna will lie # below ground and cause an error in simulation. def target(x): - global freq - base_height = np.exp(x[0]) # Make it positive - length = np.exp(x[1]) # Make it positive - z = monopole.impedance(freq, base_height, length) - return reflection_coefficient(z, z0=50.0) - + 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))) -# Starting value -freq = 134.5 -x0 = [-2.0, 0.0] -# Carry out the minimization -log_base, log_length = scipy.optimize.fmin(target, x0) +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() -base_height = np.exp(log_base) -length = np.exp(log_length) + # Starting value + freq = 134.5 + x0 = [-2.0, 1.0] + target_impedance = args.target_impedance + + # Carry out the minimization -print "Optimium base_height=%fm, h=%fm, impedance=%s Ohms" % \ - (base_height, length, monopole.impedance(freq, base_height, length)) + 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 index 7f0842c..39a3084 100644 --- a/PyNEC/example/radiation_pattern.py +++ b/PyNEC/example/radiation_pattern.py @@ -36,18 +36,18 @@ def geometry(freq, base, length): ipt = nec.get_input_parameters(0) z = ipt.get_impedance() -print("Impedance is {}".format(z)) +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) +print((complex_e_field.size)) for t in range(30): for p in range(30): pass - print e[t, p] + print(e[t, p]) -print dir(rpt) +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 dd8d0bd..bc33761 100644 --- a/PyNEC/setup.py +++ b/PyNEC/setup.py @@ -6,67 +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")) - -with open('README.txt') as f: - readme = f.read() +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.3.1', - 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", - long_description=readme, - data_files=[('examples', ['example/test_rp.py'])], - ext_modules = [necpp_module], - requires = ['numpy'], - py_modules = ["PyNEC"], - license='GPLv2', +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)", - "Intended Audience :: Science/Research"] + "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 3b597f1..9e20312 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # python-necpp: Antenna simulation in python -This repository contains two wrappers for the nec2++ antenna simulation package: +This repository contains two wrappers for the [http://github.com/tmolteno/necpp nec2++] antenna simulation package: * 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. @@ -8,4 +8,5 @@ This repository contains two wrappers for the nec2++ antenna simulation package: Both are based on Tim Molteno (tim@physics.otago.ac.nz)'s code with major cleanup by Bart Coppens. ## TODOs + 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/necpp/LICENSE.txt b/necpp/LICENCE.txt similarity index 100% rename from necpp/LICENSE.txt rename to necpp/LICENCE.txt diff --git a/necpp/MANIFEST.in b/necpp/MANIFEST.in index 0c11346..bb7bd4c 100644 --- a/necpp/MANIFEST.in +++ b/necpp/MANIFEST.in @@ -1,7 +1,7 @@ include LICENCE.txt -include ../necpp_src/src/*.h +include necpp_src/src/*.h +include necpp_src/config.h -include ../necpp_src/config.h - -include ../necpp_src/example/test.py +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/build.sh b/necpp/build.sh index 41b1359..a99d63b 100755 --- a/necpp/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 -I../necpp_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/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/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/necpp/setup.py b/necpp/setup.py index 8040245..896ed98 100644 --- a/necpp/setup.py +++ b/necpp/setup.py @@ -1,61 +1,72 @@ #!/usr/bin/env python """ -setup.py file for necpp Python module. +setup.py file for necpp Python module. """ -from distutils.core import setup, Extension -from glob import glob 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( + [ + 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")) +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', +necpp_module = Extension( + "_necpp", sources=nec_sources, - include_dirs=['../necpp_src/src/', '../necpp_src/'], + include_dirs=["necpp_src/src/", "necpp_src/"], depends=nec_headers, - define_macros=[('BUILD_PYTHON', '1')] - ) + define_macros=[("BUILD_PYTHON", "1")], +) -with open('README.txt') as f: +with open("README.md") as f: readme = f.read() -setup (name = 'necpp', - version = '1.7.3.2', - 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", +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, - data_files=[('examples', ['../necpp_src/example/test.py'])], - ext_modules = [necpp_module], - py_modules = ["necpp"], - license='GPLv2', + 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"] + "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 a033992..049c556 160000 --- a/necpp_src +++ b/necpp_src @@ -1 +1 @@ -Subproject commit a0339925a6fafa29c5b6d9a04bcfd3d406ad7ea3 +Subproject commit 049c556cb10769410b760dcea7ef341a3e08f78b