├── .f2py_f2cmap ├── .gitignore ├── Dockerfile ├── LICENSE.txt ├── MANIFEST.in ├── README.md ├── build.bat ├── build.sh ├── doc ├── Makefile ├── _static │ └── montage.png ├── _templates │ └── sidebar_links.html ├── _themes │ └── custom-agogo │ │ ├── layout.html │ │ ├── static │ │ ├── agogo.css_t │ │ ├── bgfooter.png │ │ └── bgtop.png │ │ └── theme.conf ├── conf.py ├── examples.rst ├── features.rst ├── gettingstarted.rst ├── index.rst ├── install.rst ├── make.bat ├── maths.rst ├── thanks.rst ├── using.rst └── whatsnew.rst ├── generate_wrappers.bat ├── openmodes ├── __init__.py ├── array.py ├── basis.py ├── constants.py ├── eig.py ├── external │ ├── __init__.py │ ├── ordered_set.py │ ├── point_in_polygon.py │ └── three.js │ │ ├── CanvasRenderer.js │ │ ├── Detector.js │ │ ├── LICENSE │ │ ├── Lut.js │ │ ├── OrbitControls.js │ │ ├── Projector.js │ │ ├── README.md │ │ └── three.min.js ├── geometry │ ├── SRR.geo │ ├── asymmetric_ring.geo │ ├── box.geo │ ├── canonical_spiral.geo │ ├── circle.geo │ ├── circled_cross.geo │ ├── closed_ring.geo │ ├── cross.geo │ ├── cylinder_hole.geo │ ├── cylinder_hollow.geo │ ├── cylinder_rounded.geo │ ├── ellipsoid.geo │ ├── elliptical_cylinder.geo │ ├── horseshoe_rect.geo │ ├── isosceles.geo │ ├── rectangle.geo │ ├── single-bent.geo │ ├── single.geo │ ├── solid_L.geo │ ├── sphere.geo │ ├── torus.geo │ └── v_antenna.geo ├── helpers.py ├── impedance.py ├── integration.py ├── ipython.py ├── material.py ├── mesh │ ├── __init__.py │ ├── freecad.py │ ├── gmsh.py │ └── mesh.py ├── model.py ├── modes.py ├── multipole.py ├── operator │ ├── __init__.py │ ├── operator.py │ ├── pec.py │ ├── penetrable.py │ ├── rwg.py │ └── singularities.py ├── parts.py ├── simulation.py ├── sources.py ├── static │ └── three_js_plot.js ├── templates │ └── three_js_plot.html ├── version.py └── visualise.py ├── setup.py ├── src ├── common.f90 ├── core.pyf ├── dunavant.f90 ├── dunavant.pyf └── rwg.f90 └── test ├── Test MFIE Direct.ipynb ├── Test MFIE.ipynb ├── helpers.py ├── input ├── test_basis │ ├── SRR.msh │ └── rectangle.msh ├── test_horseshoe │ └── horseshoe_rect.msh ├── test_multipoles │ └── sphere.msh ├── test_poles │ └── srr.msh └── test_sphere │ └── sphere.msh ├── reference ├── test_basis │ ├── loop_star_basis_func.txt │ ├── loop_star_r.txt │ ├── rwg_basis_func.txt │ └── rwg_r.txt ├── test_horseshoe │ ├── eigenvector_0.txt │ ├── eigenvector_1.txt │ ├── eigenvector_2.txt │ ├── extinction.txt │ ├── surface_normals.txt │ └── surface_r.txt ├── test_multipoles │ └── pec_sphere_multipoles.pickle ├── test_poles │ ├── srr_pair_combined_poles.pickle │ └── srr_pair_separate_poles.pickle └── test_sphere │ ├── extinction_cfie.npy │ ├── extinction_efie.npy │ └── extinction_mfie.npy ├── run_geometry.py ├── test_array.py ├── test_basis.py ├── test_eig.py ├── test_helpers.py ├── test_horseshoe.py ├── test_mesh.py ├── test_multipoles.py ├── test_nonlinear_eig.py ├── test_parts.py ├── test_poles.py ├── test_sphere.py ├── test_vector.py └── time_sphere.py /.f2py_f2cmap: -------------------------------------------------------------------------------- 1 | {'real':{'WP':'double', 'DP':'double', 'SP':'float'}, 'complex':{'WP':'complex_double'}} 2 | #{'real':{'WP':'float', 'DP':'double', 'SP':'float'}, 'complex':{'WP':'complex_float'}} 3 | #{'real':{'REAL64':'double', 'REAL32':'float'}, 'complex':{'REAL64':'complex_double', 'REAL32':'complex_float'}} 4 | #{'real':{'WP':'float', 'DP':'double', 'SP':'float', 'REAL64':'double', 'REAL32':'float'}, 'complex':{'WP':'complex_double', 'REAL64':'complex_double', 'REAL32':'complex_float'}} -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.py[cod] 2 | 3 | # C extensions 4 | *.so 5 | 6 | # Packages 7 | *.egg 8 | *.egg-info 9 | dist 10 | build 11 | eggs 12 | parts 13 | bin 14 | var 15 | sdist 16 | develop-eggs 17 | .installed.cfg 18 | lib 19 | lib64 20 | __pycache__ 21 | 22 | # Installer logs 23 | pip-log.txt 24 | 25 | # Unit test / coverage reports 26 | .coverage 27 | .tox 28 | nosetests.xml 29 | 30 | # Translations 31 | *.mo 32 | 33 | # Mr Developer 34 | .mr.developer.cfg 35 | .project 36 | .pydevproject 37 | doc/_build/ 38 | .ipynb_checkpoints 39 | src/*.html 40 | *.vtk 41 | *.session 42 | *.session.gui 43 | src/*module.c 44 | src/*f2pywrappers* 45 | junk/ 46 | RELEASE-VERSION 47 | .vscode/settings.json 48 | *.code-workspace 49 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM jupyter/minimal-notebook 2 | 3 | LABEL maintainer="David Powell " 4 | 5 | # Install gmsh and gfortran 6 | # Note that apt-get gmsh is required to install all dependencies 7 | USER root 8 | RUN apt-get update && \ 9 | apt-get install -y curl gfortran libxcursor1 gmsh && \ 10 | apt-get clean 11 | 12 | USER jovyan 13 | 14 | ENV JUPYTER_ENABLE_LAB yes 15 | 16 | # Install Python 3 packages 17 | RUN conda install --yes \ 18 | 'matplotlib=3.1.*' \ 19 | 'numpy=1.17.*' \ 20 | 'scipy=1.3.*' \ 21 | 'setuptools=42.0.*' \ 22 | 'pytest=5.3.*' \ 23 | 'jinja2=2.10.*' \ 24 | 'six=1.13.*' \ 25 | 'ipywidgets=7.5.*' \ 26 | 'dill=0.3.*' \ 27 | 'gmsh=4.4.*' \ 28 | 'meshio=3.2.*' \ 29 | && conda clean -yt 30 | 31 | RUN jupyter nbextension enable --py --sys-prefix widgetsnbextension 32 | RUN jupyter labextension install @jupyter-widgets/jupyterlab-manager 33 | 34 | # Install OpenModes 35 | ADD . /opt/openmodes 36 | WORKDIR /opt/openmodes 37 | RUN pip install . 38 | 39 | # Download the example notebooks 40 | ENV EXAMPLES_VERSION 1.3.2 41 | WORKDIR /tmp 42 | RUN curl -L https://github.com/DavidPowell/openmodes-examples/archive/${EXAMPLES_VERSION}.zip -o examples.zip && \ 43 | unzip -j examples.zip -d /home/jovyan/work/ && \ 44 | rm examples.zip 45 | 46 | # Trust the example notebooks 47 | WORKDIR /home/jovyan/work 48 | RUN mv Index.ipynb "** Start Here **.ipynb" 49 | RUN jupyter trust *.ipynb 50 | 51 | VOLUME /home/jovyan/work 52 | 53 | USER root 54 | 55 | CMD ["start-notebook.sh", "--NotebookApp.token=''"] 56 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.rst 2 | include ez_setup.py 3 | include .f2py_f2cmap -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | OpenModes 2 | ========= 3 | 4 | A Method of Moments (Boundary Element Method) code designed to find the 5 | modes of open resonators such as meta-atoms (the building blocks of 6 | metamaterials), (nano) antennas, scattering particles etc. Using these 7 | modes, broadband models of these elements can be created, enabling 8 | excitation, coupling between them and scattering to be solved easily. 9 | 10 | References 11 | ---------- 12 | 13 | The techniques used in this package, and the scientific results 14 | obtained, are described in the following publications and presentations: 15 | 16 | - *Interference between the modes of an all-dielectric meta-atom*, 17 | [2017 paper](http://dx.doi.org/10.1103/PhysRevApplied.7.034006) or 18 | [2016 arXiv preprint](https://arxiv.org/abs/1610.04980) 19 | - *Resonant dynamics of arbitrarily shaped meta-atoms*, [2014 20 | published paper](http://dx.doi.org/10.1103/PhysRevB.90.075108) or 21 | [2014 arXiv preprint](http://arxiv.org/abs/1405.3759) 22 | - *Modes in open metamaterial and nanophotonic systems*, [web 23 | presentation](http://people.physics.anu.edu.au/~dap124/aip2014/). 24 | 25 | Documentation 26 | ------------- 27 | 28 | Documentation is hosted on [Read the Docs](http://openmodes.readthedocs.io/en/latest/) 29 | 30 | Installation 31 | ------------ 32 | 33 | A [docker image](https://hub.docker.com/r/davidpowell/openmodes/) is available which contains 34 | the OpenModes, all required packages, the Jupyter web-based notebook interface and example notebooks. 35 | 36 | See the [installation instructions](http://openmodes.readthedocs.io/en/latest/install.html) 37 | for full details of how to install OpenModes via docker, or directly onto your machine. 38 | 39 | The source is available on [GitHub](https://github.com/DavidPowell/OpenModes), and binary packages 40 | are available from the [Python Package Index](https://pypi.python.org/pypi/OpenModes). 41 | 42 | Author 43 | ------ 44 | 45 | This program was written by David Powell, a Senior Lecturer with the 46 | School of Engineering and Information Technology at the University of 47 | New South Wales, Canberra Campus. 48 | -------------------------------------------------------------------------------- /build.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | del build /q /s 3 | del openmodes\*.pyd 4 | python setup.py clean 5 | python setup.py develop --no-deps 6 | 7 | -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | #f2py -c dunavant.f90 -m dunavant 2 | #f2py -c core_for.f90 fpbspl.f splev.f -m core_for --f90flags="-g -pg" -lgomp only: interpolate_greens_inplane face_integrals_hanninen face_integrals_interpolated impedance_core_interpolated impedance_core_hanninen 3 | #-fopenmp -fbounds-check -fimplicit-none 4 | # -DF2PY_REPORT_ON_ARRAY_COPY=1 5 | rm openmodes/*.so 6 | rm -Rf build 7 | python setup.py build_ext --inplace 8 | 9 | -------------------------------------------------------------------------------- /doc/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = _build 9 | 10 | # User-friendly check for sphinx-build 11 | ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) 12 | $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) 13 | endif 14 | 15 | # Internal variables. 16 | PAPEROPT_a4 = -D latex_paper_size=a4 17 | PAPEROPT_letter = -D latex_paper_size=letter 18 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 19 | # the i18n builder cannot share the environment and doctrees with the others 20 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 21 | 22 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext 23 | 24 | help: 25 | @echo "Please use \`make ' where is one of" 26 | @echo " html to make standalone HTML files" 27 | @echo " dirhtml to make HTML files named index.html in directories" 28 | @echo " singlehtml to make a single large HTML file" 29 | @echo " pickle to make pickle files" 30 | @echo " json to make JSON files" 31 | @echo " htmlhelp to make HTML files and a HTML help project" 32 | @echo " qthelp to make HTML files and a qthelp project" 33 | @echo " devhelp to make HTML files and a Devhelp project" 34 | @echo " epub to make an epub" 35 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 36 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 37 | @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" 38 | @echo " text to make text files" 39 | @echo " man to make manual pages" 40 | @echo " texinfo to make Texinfo files" 41 | @echo " info to make Texinfo files and run them through makeinfo" 42 | @echo " gettext to make PO message catalogs" 43 | @echo " changes to make an overview of all changed/added/deprecated items" 44 | @echo " xml to make Docutils-native XML files" 45 | @echo " pseudoxml to make pseudoxml-XML files for display purposes" 46 | @echo " linkcheck to check all external links for integrity" 47 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 48 | 49 | clean: 50 | rm -rf $(BUILDDIR)/* 51 | 52 | html: 53 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 54 | @echo 55 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 56 | 57 | dirhtml: 58 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 59 | @echo 60 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 61 | 62 | singlehtml: 63 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 64 | @echo 65 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 66 | 67 | pickle: 68 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 69 | @echo 70 | @echo "Build finished; now you can process the pickle files." 71 | 72 | json: 73 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 74 | @echo 75 | @echo "Build finished; now you can process the JSON files." 76 | 77 | htmlhelp: 78 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 79 | @echo 80 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 81 | ".hhp project file in $(BUILDDIR)/htmlhelp." 82 | 83 | qthelp: 84 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 85 | @echo 86 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 87 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 88 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/OpenModes.qhcp" 89 | @echo "To view the help file:" 90 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/OpenModes.qhc" 91 | 92 | devhelp: 93 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 94 | @echo 95 | @echo "Build finished." 96 | @echo "To view the help file:" 97 | @echo "# mkdir -p $$HOME/.local/share/devhelp/OpenModes" 98 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/OpenModes" 99 | @echo "# devhelp" 100 | 101 | epub: 102 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 103 | @echo 104 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 105 | 106 | latex: 107 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 108 | @echo 109 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 110 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 111 | "(use \`make latexpdf' here to do that automatically)." 112 | 113 | latexpdf: 114 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 115 | @echo "Running LaTeX files through pdflatex..." 116 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 117 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 118 | 119 | latexpdfja: 120 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 121 | @echo "Running LaTeX files through platex and dvipdfmx..." 122 | $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja 123 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 124 | 125 | text: 126 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 127 | @echo 128 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 129 | 130 | man: 131 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 132 | @echo 133 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 134 | 135 | texinfo: 136 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 137 | @echo 138 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." 139 | @echo "Run \`make' in that directory to run these through makeinfo" \ 140 | "(use \`make info' here to do that automatically)." 141 | 142 | info: 143 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 144 | @echo "Running Texinfo files through makeinfo..." 145 | make -C $(BUILDDIR)/texinfo info 146 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." 147 | 148 | gettext: 149 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale 150 | @echo 151 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 152 | 153 | changes: 154 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 155 | @echo 156 | @echo "The overview file is in $(BUILDDIR)/changes." 157 | 158 | linkcheck: 159 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 160 | @echo 161 | @echo "Link check complete; look for any errors in the above output " \ 162 | "or in $(BUILDDIR)/linkcheck/output.txt." 163 | 164 | doctest: 165 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 166 | @echo "Testing of doctests in the sources finished, look at the " \ 167 | "results in $(BUILDDIR)/doctest/output.txt." 168 | 169 | xml: 170 | $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml 171 | @echo 172 | @echo "Build finished. The XML files are in $(BUILDDIR)/xml." 173 | 174 | pseudoxml: 175 | $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml 176 | @echo 177 | @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." 178 | -------------------------------------------------------------------------------- /doc/_static/montage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DavidPowell/OpenModes/161bd0b30036c98caf4ab0cd463032a4ba22a382/doc/_static/montage.png -------------------------------------------------------------------------------- /doc/_templates/sidebar_links.html: -------------------------------------------------------------------------------- 1 | {%- block sidebarlinks %} 2 | 3 |
4 |

Links

5 | 9 |
10 | 11 | {%- endblock %} 12 | -------------------------------------------------------------------------------- /doc/_themes/custom-agogo/layout.html: -------------------------------------------------------------------------------- 1 | {# 2 | agogo/layout.html 3 | ~~~~~~~~~~~~~~~~~ 4 | 5 | Sphinx layout template for the agogo theme, originally written 6 | by Andi Albrecht. 7 | 8 | :copyright: Copyright 2007-2014 by the Sphinx team, see AUTHORS. 9 | :license: BSD, see LICENSE for details. 10 | 11 | #} 12 | {%- extends "basic/layout.html" %} 13 | 14 | {% block header %} 15 |
16 |
17 | {%- if logo %} 18 | 21 | {%- endif %} 22 | {%- block headertitle %} 23 | 25 | {%- endblock %} 26 |
27 |
28 | {% endblock %} 29 | 30 | {% block content %} 31 |
32 |
33 |
34 | {%- block document %} 35 | {{ super() }} 36 | {%- endblock %} 37 |
38 | 58 |
59 |
60 |
61 | {% endblock %} 62 | 63 | {% block footer %} 64 | 85 | {% endblock %} 86 | 87 | {% block relbar1 %}{% endblock %} 88 | {% block relbar2 %}{% endblock %} 89 | -------------------------------------------------------------------------------- /doc/_themes/custom-agogo/static/bgfooter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DavidPowell/OpenModes/161bd0b30036c98caf4ab0cd463032a4ba22a382/doc/_themes/custom-agogo/static/bgfooter.png -------------------------------------------------------------------------------- /doc/_themes/custom-agogo/static/bgtop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DavidPowell/OpenModes/161bd0b30036c98caf4ab0cd463032a4ba22a382/doc/_themes/custom-agogo/static/bgtop.png -------------------------------------------------------------------------------- /doc/_themes/custom-agogo/theme.conf: -------------------------------------------------------------------------------- 1 | [theme] 2 | inherit = basic 3 | stylesheet = agogo.css 4 | pygments_style = tango 5 | 6 | [options] 7 | bodyfont = "Verdana", Arial, sans-serif 8 | headerfont = "Courier New", Courier, monospace 9 | pagewidth = 70em 10 | documentwidth = 50em 11 | sidebarwidth = 20em 12 | bgcolor = #eeeeec 13 | headerbg = #555573 url(bgtop.png) top left repeat-x 14 | footerbg = url(bgfooter.png) top left repeat-x 15 | linkcolor = #F52E14 16 | headercolor1 = #04A1C4 17 | headercolor2 = #03809C 18 | headerlinkcolor = #fcaf3e 19 | textalign = justify -------------------------------------------------------------------------------- /doc/examples.rst: -------------------------------------------------------------------------------- 1 | Examples 2 | ======== 3 | 4 | **To quickly view output of examples online, click** 5 | `here `_. 6 | 7 | There are several example files which can be opened using the `Jupyter (IPython) notebook `_, 8 | an interactive web-based frontend to python which you will have installed if you 9 | followed the :doc:`Installation Instructions `. 10 | 11 | Running Examples 12 | ---------------- 13 | 14 | To run the examples yourself 15 | 16 | 1. Ensure that you have successfully installed OpenModes as per the 17 | :doc:`Installation Instructions `. 18 | 2. Download the `zip file `_ 19 | with all the examples and unzip them into a folder. 20 | 3. Open the IPython notebook. If you have installed python using the `Anaconda `_ 21 | distribution (the recommended way, especially on Windows), then the notebook should be available from the 22 | Launcher. Otherwise, open a command prompt and run ``ipython notebook`` 23 | 4. From the notebook file browser, navigate to the folder containing the examples and open one of the example notebooks 24 | 5. To re-run the whole example from the menu bar by select ``Cell->Run All``. You should try modifying the examples 25 | with different parameters, and you only need to run cells again if you have changed something. 26 | 27 | 28 | -------------------------------------------------------------------------------- /doc/features.rst: -------------------------------------------------------------------------------- 1 | Features and Limitations 2 | ========================= 3 | 4 | OpenModes implements the method of moments (MOM) numerical algorithm, 5 | which is a general approach for solving many electromagnetic scattering 6 | problems. However, the code has been optimised for a specific purpose, namely 7 | extracting simple physical models from full numerical analysis of 8 | structures which are approximately one wavelength in size or smaller. 9 | 10 | Features 11 | -------- 12 | * Highly flexible python scripting, allows a wide variety of problems 13 | to be solved and information to be extracted. 14 | * Modelling essentially arbitrary 3D geometries, including 15 | 2D thin layer structures 16 | * Web browser based UI, including interactive 3D plots. 17 | * Key routines are written in Fortran, making them very efficient 18 | * Multi-core CPUs can be utilised effectively, thanks to the use of OpenMP 19 | parallel constructs. 20 | * Object-oriented design allows different choices such as the type of basis 21 | function to be changed easily. 22 | 23 | Limitations 24 | ----------- 25 | * The software fully supports perfect electric conductors. Most features also 26 | work for dielectric materials as well, with support expected to improve. Plasmonic 27 | materials are currently untested. 28 | * Acceleration techniques such as the Fast Multipole Method (FMM) are not used, 29 | since they are not well-suited for sub-wavelength structures. 30 | * GPU computing is not currently supported. 31 | 32 | Architecture 33 | ------------ 34 | There is a fairly complex set of objects which together make up OpenModes. 35 | The user can largely avoid having to know about these, by creating a `Simulation` 36 | object, which hides many implementation details and provides functions to perform 37 | most common tasks. A few other key components are described here: 38 | 39 | * Meshes are generated externally, mostly using the `gmsh` program. There is some 40 | limited support for using `freecad` to create geometries. The geometries shipped 41 | with openmodes are in parametric form, allowing dimensions to be easily modified 42 | before meshing. Calling these external meshing utilities, and reading the resultant 43 | mesh, is handled by `mesh.py`. 44 | 45 | * When a mesh is added to the simulation, it is known as a Part, as defined in `part.py`. 46 | This stores the coordinates of the Part, and the corresponding mesh. 47 | 48 | * Basis functions are currently limited to RWG and loop-star functions. They are created 49 | over a mesh, using the routines in `basis.py`. They are created in a lazy fashion, using 50 | an object known as a `BasisContainer`. 51 | 52 | * Operators implement the mathematics at the heart of the program, solving equations 53 | such as EFIE, MFIE, PMCHWT or CTF. These operators call fast Fortran routines to fill 54 | the impedance matrices. 55 | 56 | * Impedance matrices are stored in special objects, defined in `impedance.py`. These 57 | objects can be indexed by parts, to conveniently find self and mutual impedance terms 58 | in a system of coupled objects. 59 | 60 | * Sources are electric and/or magnetic fields which excite the structure, depending on 61 | which operator is used. They are defined in `sources.py`, and simple incident fields 62 | such as a plane wave are defined. It should be relatively easy for the user to add 63 | some arbitrary incident field form. 64 | 65 | * Modes of a structure are found by looking for the poles of the Operator. They are 66 | stored in an object defined in `modes.py`, enabling their resonant frequency, 67 | current vector, and the Part they are associated with the be kept together. 68 | 69 | -------------------------------------------------------------------------------- /doc/gettingstarted.rst: -------------------------------------------------------------------------------- 1 | Getting started 2 | =============== 3 | 4 | 1. Install OpenModes as per the :doc:`installation instructions `. 5 | 6 | 2. Experiment with the :doc:`examples`. 7 | 8 | 3. Learn about python. 9 | 10 | OpenModes is mostly written in the python scripting language, which you also use to control it. 11 | If you want to learn more about python, please read this `tutorial`_, which focusses on 12 | scientific applications. 13 | 14 | 4. To create geometries of your own, learn to use the program `gmsh`_. 15 | 16 | .. _tutorial: https://scipy-lectures.github.io/ 17 | .. _gmsh: http://geuz.org/gmsh/ -------------------------------------------------------------------------------- /doc/index.rst: -------------------------------------------------------------------------------- 1 | .. OpenModes documentation master file, created by 2 | sphinx-quickstart on Tue Nov 05 15:21:38 2013. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | Overview 7 | ======== 8 | 9 | .. image:: _static/montage.png 10 | :width: 400px 11 | :align: right 12 | 13 | OpenModes is a code aimed at solving the modes of open electromagnetic structures. 14 | Using these modes, broadband models of these elements can be created, enabling 15 | excitation, coupling between them and scattering to be solved easily. 16 | 17 | Potential applications include metamaterials, (nano) antennas and other systems 18 | based on scattering from resonant elements. It is based on a fully numerical model 19 | of the system created using the method of moments (MOM). 20 | 21 | The techniques used in this package, and the scientific results obtained, are described in the 22 | following publications and presentations: 23 | 24 | - *Interference between the modes of an all-dielectric meta-atom*, `2017 paper `_ 25 | or `2016 arXiv preprint `_ 26 | - *Resonant dynamics of arbitrarily shaped meta-atoms*, `2014 published paper `_ or 27 | `2014 arXiv preprint `_ 28 | - *Modes in open metamaterial and nanophotonic systems*, `web presentation `_. 29 | 30 | This program was written by David Powell, a Senior Lecturer with the School of Engineering and 31 | Information Technology at the University of New South Wales, Canberra Campus. 32 | 33 | To begin using the software, follow the :doc:`getting started instructions `. 34 | 35 | The :doc:`examples` give an overview of OpenModes' capabilities, and can be viewed online. 36 | 37 | **To quickly view output of examples online, click** 38 | `here `_. 39 | 40 | .. toctree:: 41 | :hidden: 42 | 43 | gettingstarted 44 | whatsnew 45 | install 46 | examples 47 | features 48 | using 49 | maths 50 | thanks 51 | -------------------------------------------------------------------------------- /doc/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | REM Command file for Sphinx documentation 4 | 5 | if "%SPHINXBUILD%" == "" ( 6 | set SPHINXBUILD=sphinx-build 7 | ) 8 | set BUILDDIR=_build 9 | set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . 10 | set I18NSPHINXOPTS=%SPHINXOPTS% . 11 | if NOT "%PAPER%" == "" ( 12 | set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% 13 | set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% 14 | ) 15 | 16 | if "%1" == "" goto help 17 | 18 | if "%1" == "help" ( 19 | :help 20 | echo.Please use `make ^` where ^ is one of 21 | echo. html to make standalone HTML files 22 | echo. dirhtml to make HTML files named index.html in directories 23 | echo. singlehtml to make a single large HTML file 24 | echo. pickle to make pickle files 25 | echo. json to make JSON files 26 | echo. htmlhelp to make HTML files and a HTML help project 27 | echo. qthelp to make HTML files and a qthelp project 28 | echo. devhelp to make HTML files and a Devhelp project 29 | echo. epub to make an epub 30 | echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter 31 | echo. text to make text files 32 | echo. man to make manual pages 33 | echo. texinfo to make Texinfo files 34 | echo. gettext to make PO message catalogs 35 | echo. changes to make an overview over all changed/added/deprecated items 36 | echo. xml to make Docutils-native XML files 37 | echo. pseudoxml to make pseudoxml-XML files for display purposes 38 | echo. linkcheck to check all external links for integrity 39 | echo. doctest to run all doctests embedded in the documentation if enabled 40 | goto end 41 | ) 42 | 43 | if "%1" == "clean" ( 44 | for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i 45 | del /q /s %BUILDDIR%\* 46 | goto end 47 | ) 48 | 49 | 50 | %SPHINXBUILD% 2> nul 51 | if errorlevel 9009 ( 52 | echo. 53 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 54 | echo.installed, then set the SPHINXBUILD environment variable to point 55 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 56 | echo.may add the Sphinx directory to PATH. 57 | echo. 58 | echo.If you don't have Sphinx installed, grab it from 59 | echo.http://sphinx-doc.org/ 60 | exit /b 1 61 | ) 62 | 63 | if "%1" == "html" ( 64 | %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html 65 | if errorlevel 1 exit /b 1 66 | echo. 67 | echo.Build finished. The HTML pages are in %BUILDDIR%/html. 68 | goto end 69 | ) 70 | 71 | if "%1" == "dirhtml" ( 72 | %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml 73 | if errorlevel 1 exit /b 1 74 | echo. 75 | echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. 76 | goto end 77 | ) 78 | 79 | if "%1" == "singlehtml" ( 80 | %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml 81 | if errorlevel 1 exit /b 1 82 | echo. 83 | echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. 84 | goto end 85 | ) 86 | 87 | if "%1" == "pickle" ( 88 | %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle 89 | if errorlevel 1 exit /b 1 90 | echo. 91 | echo.Build finished; now you can process the pickle files. 92 | goto end 93 | ) 94 | 95 | if "%1" == "json" ( 96 | %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json 97 | if errorlevel 1 exit /b 1 98 | echo. 99 | echo.Build finished; now you can process the JSON files. 100 | goto end 101 | ) 102 | 103 | if "%1" == "htmlhelp" ( 104 | %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp 105 | if errorlevel 1 exit /b 1 106 | echo. 107 | echo.Build finished; now you can run HTML Help Workshop with the ^ 108 | .hhp project file in %BUILDDIR%/htmlhelp. 109 | goto end 110 | ) 111 | 112 | if "%1" == "qthelp" ( 113 | %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp 114 | if errorlevel 1 exit /b 1 115 | echo. 116 | echo.Build finished; now you can run "qcollectiongenerator" with the ^ 117 | .qhcp project file in %BUILDDIR%/qthelp, like this: 118 | echo.^> qcollectiongenerator %BUILDDIR%\qthelp\OpenModes.qhcp 119 | echo.To view the help file: 120 | echo.^> assistant -collectionFile %BUILDDIR%\qthelp\OpenModes.ghc 121 | goto end 122 | ) 123 | 124 | if "%1" == "devhelp" ( 125 | %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp 126 | if errorlevel 1 exit /b 1 127 | echo. 128 | echo.Build finished. 129 | goto end 130 | ) 131 | 132 | if "%1" == "epub" ( 133 | %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub 134 | if errorlevel 1 exit /b 1 135 | echo. 136 | echo.Build finished. The epub file is in %BUILDDIR%/epub. 137 | goto end 138 | ) 139 | 140 | if "%1" == "latex" ( 141 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 142 | if errorlevel 1 exit /b 1 143 | echo. 144 | echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. 145 | goto end 146 | ) 147 | 148 | if "%1" == "latexpdf" ( 149 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 150 | cd %BUILDDIR%/latex 151 | make all-pdf 152 | cd %BUILDDIR%/.. 153 | echo. 154 | echo.Build finished; the PDF files are in %BUILDDIR%/latex. 155 | goto end 156 | ) 157 | 158 | if "%1" == "latexpdfja" ( 159 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 160 | cd %BUILDDIR%/latex 161 | make all-pdf-ja 162 | cd %BUILDDIR%/.. 163 | echo. 164 | echo.Build finished; the PDF files are in %BUILDDIR%/latex. 165 | goto end 166 | ) 167 | 168 | if "%1" == "text" ( 169 | %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text 170 | if errorlevel 1 exit /b 1 171 | echo. 172 | echo.Build finished. The text files are in %BUILDDIR%/text. 173 | goto end 174 | ) 175 | 176 | if "%1" == "man" ( 177 | %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man 178 | if errorlevel 1 exit /b 1 179 | echo. 180 | echo.Build finished. The manual pages are in %BUILDDIR%/man. 181 | goto end 182 | ) 183 | 184 | if "%1" == "texinfo" ( 185 | %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo 186 | if errorlevel 1 exit /b 1 187 | echo. 188 | echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. 189 | goto end 190 | ) 191 | 192 | if "%1" == "gettext" ( 193 | %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale 194 | if errorlevel 1 exit /b 1 195 | echo. 196 | echo.Build finished. The message catalogs are in %BUILDDIR%/locale. 197 | goto end 198 | ) 199 | 200 | if "%1" == "changes" ( 201 | %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes 202 | if errorlevel 1 exit /b 1 203 | echo. 204 | echo.The overview file is in %BUILDDIR%/changes. 205 | goto end 206 | ) 207 | 208 | if "%1" == "linkcheck" ( 209 | %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck 210 | if errorlevel 1 exit /b 1 211 | echo. 212 | echo.Link check complete; look for any errors in the above output ^ 213 | or in %BUILDDIR%/linkcheck/output.txt. 214 | goto end 215 | ) 216 | 217 | if "%1" == "doctest" ( 218 | %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest 219 | if errorlevel 1 exit /b 1 220 | echo. 221 | echo.Testing of doctests in the sources finished, look at the ^ 222 | results in %BUILDDIR%/doctest/output.txt. 223 | goto end 224 | ) 225 | 226 | if "%1" == "xml" ( 227 | %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml 228 | if errorlevel 1 exit /b 1 229 | echo. 230 | echo.Build finished. The XML files are in %BUILDDIR%/xml. 231 | goto end 232 | ) 233 | 234 | if "%1" == "pseudoxml" ( 235 | %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml 236 | if errorlevel 1 exit /b 1 237 | echo. 238 | echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. 239 | goto end 240 | ) 241 | 242 | :end 243 | -------------------------------------------------------------------------------- /doc/thanks.rst: -------------------------------------------------------------------------------- 1 | Acknowledgements 2 | ================ 3 | 4 | Organisational Support 5 | ---------------------- 6 | 7 | David Powell, the author of this software, is employed by the `School of Engineering and Information Technology `_ at the University of New South Wales 8 | Canberra Campus, located at the Australian Defence Force Academy. Much of the work done on this code was conducted during 9 | his previous position at the `Nonlinear Physics Centre `_ 10 | of the `Australian National University `_. This Centre is 11 | one of the nodes of the `CUDOS Centre of Excellence `_, which 12 | has a `Metamaterials Project `_. 13 | 14 | Incorporated Software 15 | --------------------- 16 | 17 | * This program incorporates `Fortran code `_ 18 | for symmetric integration rules over triangles, written 19 | by John Burkardt, and distributed under the GNU LGPL license. 20 | 21 | * The automated extraction of version numbers from the git repository was written by 22 | `Douglas Creager `_. 23 | 24 | * Three-dimensional plots within the IPython notebook are enabled by the use of the `three.js `_ 25 | library, which is partly incorporated into this project. 26 | 27 | * Code for an `ordered set `_ originally written by 28 | Raymond Hettiger is incorporated under the MIT license. 29 | 30 | * Code for testing whether a point is within a polygon by softsurfer has been used, converted to 31 | python by Maciej Kalisiak. Adapted from `_. -------------------------------------------------------------------------------- /doc/using.rst: -------------------------------------------------------------------------------- 1 | Using and Contributing to OpenModes 2 | =================================== 3 | 4 | License 5 | ------- 6 | OpenModes is distributed under the GPLv3 license, which is included in the file 7 | LICENSE.txt. In practice this means that anyone is free to use and modify this 8 | software to solve their own problems, but it effectively cannot be used in 9 | any commercial product, as any modified version still carries the GPL license. 10 | 11 | Modifications and Contributions 12 | ------------------------------- 13 | If you modify this software, there is no legal obligation to share your 14 | modifications with anyone. However, any improvements made would be gratefully 15 | received, to enable all other users to benefit. The most effective way to do 16 | this is to create a fork of the project on GitHub, and submit a pull request. 17 | See `GitHub `_ for an explanation of these terms. 18 | 19 | Support 20 | ------- 21 | If you have a question about using or installing this software, or you think you 22 | have found a bug, then please search through the project's 23 | `GitHub Issue Tracker `_ 24 | to see if any other users have had similar problems. If not, please raise a new issue. 25 | I will endeavour to answer all questions when I can spare the time, but this 26 | will be assisted if you make your question as clear as possible, and explain in 27 | detail how to reproduce any problem you are having. 28 | 29 | Citing 30 | ------ 31 | It is expected that the major users of this software would be researchers in an 32 | academic environment. The following papers describe the scientific methods used 33 | in the code, and would most likey be relevant citations for any academic work 34 | which uses it: 35 | 36 | - *Interference between the modes of an all-dielectric meta-atom*, `2017 paper `_ 37 | or `2016 arXiv preprint `_ 38 | - *Resonant dynamics of arbitrarily shaped meta-atoms*, `2014 published paper `_ or 39 | `2014 arXiv preprint `_ 40 | 41 | -------------------------------------------------------------------------------- /doc/whatsnew.rst: -------------------------------------------------------------------------------- 1 | What's new in OpenModes 2 | ======================= 3 | 4 | **Release 1.3.2** 5 | 6 | December 2019 7 | 8 | - Updated Docker file now provided to simplify installation 9 | - Bug fixes 10 | 11 | **Release 1.3.0** 12 | 13 | November 2019 14 | 15 | - Fixes to enable compilation under MacOS 16 | - Updated documents, including mathematical details 17 | - Additional geometries, cylinder with hole and test pair of non-coplanar triangles 18 | - Now recommend Python 3.7 (windows binaries provided) and gmsh 4.x 19 | - Removed unused singularity integration code 20 | - Fixed some failing tests 21 | - Fixes for compatibility with scipy 1.0 22 | - Use meshio to robustly read gmsh mesh files 23 | - Update instructions to install gmsh using conda 24 | - Simplified installation instructions, by using gmsh from conda-forge 25 | 26 | **Release 1.2.0** 27 | 28 | October 2016 29 | 30 | - Multipole decomposition is now fully implemented, with the correct phase of multipole coefficients 31 | - Multipole moments can now be used to calculate extinction, total scattering cross section and scattering pattern 32 | - Saving results to a pickle file and reloading for post-processing is now more convenient and reliable. 33 | - gmsh 2.11.0 is now required, as features introduced in this version are used in included geometries 34 | - Move to the py.test testing framework, since nose is unmaintained 35 | 36 | **Release 1.1.0** 37 | 38 | May 2016 39 | 40 | - Modelling mutual interaction is re-implemented 41 | - Experimental support for far-field scattering from multipole coefficients 42 | - Improved performance of multipole decomposition 43 | - Additional geometries added 44 | 45 | **Release 1.0.0** 46 | 47 | March 2016 48 | 49 | - Dielectric objects can now be handled via a surface equivalent problem 50 | - New, more robust method for finding complex poles 51 | - New classes to conveniently represent modes and use them for modelling 52 | - Improved Python 3 support (Python 3.5 now recommended) 53 | - Improvements to 3D plotting 54 | - Working code for spherical multipole decomposition 55 | - Impedance matrix objects are greatly simplified 56 | - New pre-made geometries are included 57 | 58 | Note that code for old versions of OpenModes will need to be modified to work 59 | with the new version. All examples have been updated. 60 | 61 | **Release 0.0.5** 62 | 63 | March 2015 64 | 65 | - In addition to the usual EFIE (electric field integral equation), it is now possible to solve 66 | the MFIE (magnetic field integral equation). This code is not yet as well tested as the EFIE, 67 | and does not yet support all the same functionality. 68 | - Improved handling of singular integrals 69 | - Automated tests added 70 | - In addition to Python 2.7, OpenModes is now compatible with Python 3.3+. 71 | - Fixed missing static files, so 3D plotting in the browser should now work correctly 72 | 73 | **Release 0.0.4** 74 | 75 | December, 2014 76 | 77 | - New *3D Plotting in the web browser*, showing geometry and optionally charge 78 | and current distribution. There is an example file showing how this works 79 | - Fixed a further bug with installing from source package. 80 | - The examples have been moved to their own repository at https://github.com/DavidPowell/openmodes-examples. 81 | 82 | **Release 0.0.3** 83 | 84 | November, 2014 85 | 86 | - Fixed bugs which prevented installing from source package. Installation under 87 | Linux should now work correctly. 88 | - Documentation improvements 89 | 90 | **Release 0.0.2** 91 | 92 | August, 2014 93 | 94 | Major changes which are visible to the user: 95 | 96 | - The incident field weighting can now be performed in pure python code, instead 97 | of fortran subroutines. This makes it easier to model sources other than plane waves. 98 | - The use of logging is simplified, to allow simulation progress to be monitored. 99 | - Fixed several cases where data structures could not be pickled. This means that 100 | structures can be cached or transmitted across the network for parallel computing. 101 | - Additional pre-made geometries are included 102 | - Planar structures with internal holes are now handled correctly 103 | 104 | **Release 0.0.1** 105 | 106 | May, 2014 107 | 108 | The initial release, which was used to produce results for the arXiv paper 109 | and manuscript submitted for review. 110 | -------------------------------------------------------------------------------- /generate_wrappers.bat: -------------------------------------------------------------------------------- 1 | python -m numpy.f2py -m core -h src/core.pyf src/rwg.f90 src/common.f90 --overwrite-signature only: set_threads get_threads face_integrals_hanninen z_efie_faces_self z_efie_faces_mutual arcioni_singular z_mfie_faces_self z_mfie_faces_mutual face_integrals_yla_oijala : 2 | python -m numpy.f2py -m dunavant -h src/dunavant.pyf src/dunavant.f90 --overwrite-signature only: dunavant_order_num dunavant_rule : -------------------------------------------------------------------------------- /openmodes/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | #----------------------------------------------------------------------------- 3 | # OpenModes - An eigenmode solver for open electromagnetic resonantors 4 | # Copyright (C) 2013 David Powell 5 | # 6 | # This program is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation, either version 3 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # This program is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with this program. If not, see . 18 | #----------------------------------------------------------------------------- 19 | """ 20 | OpenModes - An eigenmode solver for open electromagnetic resonantors 21 | """ 22 | 23 | from openmodes.simulation import Simulation 24 | from openmodes.version import __version__ 25 | 26 | # allow the user to find the provided geometry files 27 | from pkg_resources import resource_filename 28 | geometry_dir = resource_filename('openmodes', 'geometry') 29 | 30 | # setup jinja template location 31 | from jinja2 import Environment, PackageLoader 32 | template_env = Environment(loader=PackageLoader('openmodes', 'templates')) 33 | 34 | # Set the logging format of the root logger. By default it will not be 35 | # displayed. In order to display the log messages, run 36 | # `import logging; logging.setLevel(logging.INFO)` for basic information 37 | # or `import logging; logging.setLevel(logging.DEBUG)` for quite 38 | # detailed information. 39 | # 40 | import logging 41 | log_format = '%(levelname)s - %(asctime)s - %(message)s' 42 | formatter = logging.Formatter(log_format) 43 | logger = logging.getLogger() 44 | for handler in logger.handlers: 45 | handler.formatter = formatter 46 | 47 | #__all__ = [openmodes.simulation.Simulation] 48 | -------------------------------------------------------------------------------- /openmodes/constants.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | #----------------------------------------------------------------------------- 3 | # OpenModes - An eigenmode solver for open electromagnetic resonantors 4 | # Copyright (C) 2013 David Powell 5 | # 6 | # This program is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation, either version 3 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # This program is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with this program. If not, see . 18 | #----------------------------------------------------------------------------- 19 | """ 20 | Various useful constants for electromagnetism. Most of these are already 21 | defined in scipy.constants, but are duplicated here for convenience. 22 | """ 23 | 24 | import numpy as np 25 | 26 | pi = np.pi 27 | c = 299792458.0 28 | mu_0 = 4e-7*pi 29 | epsilon_0 = 1.0 / (mu_0*c*c) 30 | eta_0 = mu_0*c 31 | -------------------------------------------------------------------------------- /openmodes/external/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DavidPowell/OpenModes/161bd0b30036c98caf4ab0cd463032a4ba22a382/openmodes/external/__init__.py -------------------------------------------------------------------------------- /openmodes/external/ordered_set.py: -------------------------------------------------------------------------------- 1 | """ 2 | An OrderedSet is a custom MutableSet that remembers its order, so that every 3 | entry has an index that can be looked up. 4 | 5 | Based on a recipe originally posted to ActiveState Recipes by Raymond Hettiger, 6 | and released under the MIT license. 7 | 8 | Rob Speer's changes are as follows: 9 | 10 | - changed the content from a doubly-linked list to a regular Python list. 11 | Seriously, who wants O(1) deletes but O(N) lookups by index? 12 | - add() returns the index of the added item 13 | - index() just returns the index of an item 14 | - added a __getstate__ and __setstate__ so it can be pickled 15 | - added __getitem__ 16 | """ 17 | import collections 18 | 19 | SLICE_ALL = slice(None) 20 | __version__ = '1.3' 21 | 22 | 23 | def is_iterable(obj): 24 | """ 25 | Are we being asked to look up a list of things, instead of a single thing? 26 | We check for the `__iter__` attribute so that this can cover types that 27 | don't have to be known by this module, such as NumPy arrays. 28 | 29 | Strings, however, should be considered as atomic values to look up, not 30 | iterables. 31 | 32 | We don't need to check for the Python 2 `unicode` type, because it doesn't 33 | have an `__iter__` attribute anyway. 34 | """ 35 | return hasattr(obj, '__iter__') and not isinstance(obj, str) 36 | 37 | 38 | class OrderedSet(collections.MutableSet): 39 | """ 40 | An OrderedSet is a custom MutableSet that remembers its order, so that 41 | every entry has an index that can be looked up. 42 | """ 43 | def __init__(self, iterable=None): 44 | self.items = [] 45 | self.map = {} 46 | if iterable is not None: 47 | self |= iterable 48 | 49 | def __len__(self): 50 | return len(self.items) 51 | 52 | def __getitem__(self, index): 53 | """ 54 | Get the item at a given index. 55 | 56 | If `index` is a slice, you will get back that slice of items. If it's 57 | the slice [:], exactly the same object is returned. (If you want an 58 | independent copy of an OrderedSet, use `OrderedSet.copy()`.) 59 | 60 | If `index` is an iterable, you'll get the OrderedSet of items 61 | corresponding to those indices. This is similar to NumPy's 62 | "fancy indexing". 63 | """ 64 | if index == SLICE_ALL: 65 | return self 66 | elif hasattr(index, '__index__') or isinstance(index, slice): 67 | result = self.items[index] 68 | if isinstance(result, list): 69 | return OrderedSet(result) 70 | else: 71 | return result 72 | elif is_iterable(index): 73 | return OrderedSet([self.items[i] for i in index]) 74 | else: 75 | raise TypeError("Don't know how to index an OrderedSet by %r" % 76 | index) 77 | 78 | def copy(self): 79 | return OrderedSet(self) 80 | 81 | def __getstate__(self): 82 | if len(self) == 0: 83 | # The state can't be an empty list. 84 | # We need to return a truthy value, or else __setstate__ won't be run. 85 | # 86 | # This could have been done more gracefully by always putting the state 87 | # in a tuple, but this way is backwards- and forwards- compatible with 88 | # previous versions of OrderedSet. 89 | return (None,) 90 | else: 91 | return list(self) 92 | 93 | def __setstate__(self, state): 94 | if state == (None,): 95 | self.__init__([]) 96 | else: 97 | self.__init__(state) 98 | 99 | def __contains__(self, key): 100 | return key in self.map 101 | 102 | def add(self, key): 103 | """ 104 | Add `key` as an item to this OrderedSet, then return its index. 105 | 106 | If `key` is already in the OrderedSet, return the index it already 107 | had. 108 | """ 109 | if key not in self.map: 110 | self.map[key] = len(self.items) 111 | self.items.append(key) 112 | return self.map[key] 113 | append = add 114 | 115 | def index(self, key): 116 | """ 117 | Get the index of a given entry, raising an IndexError if it's not 118 | present. 119 | 120 | `key` can be an iterable of entries that is not a string, in which case 121 | this returns a list of indices. 122 | """ 123 | if is_iterable(key): 124 | return [self.index(subkey) for subkey in key] 125 | return self.map[key] 126 | 127 | def discard(self, key): 128 | raise NotImplementedError( 129 | "Cannot remove items from an existing OrderedSet" 130 | ) 131 | 132 | def __iter__(self): 133 | return iter(self.items) 134 | 135 | def __reversed__(self): 136 | return reversed(self.items) 137 | 138 | def __repr__(self): 139 | if not self: 140 | return '%s()' % (self.__class__.__name__,) 141 | return '%s(%r)' % (self.__class__.__name__, list(self)) 142 | 143 | def __eq__(self, other): 144 | if isinstance(other, OrderedSet): 145 | return len(self) == len(other) and self.items == other.items 146 | try: 147 | other_as_set = set(other) 148 | except TypeError: 149 | # If `other` can't be converted into a set, it's not equal. 150 | return False 151 | else: 152 | return set(self) == other_as_set 153 | 154 | -------------------------------------------------------------------------------- /openmodes/external/point_in_polygon.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # routine for performing the "point in polygon" inclusion test 4 | 5 | # Copyright 2001, softSurfer (www.softsurfer.com) 6 | # This code may be freely used and modified for any purpose 7 | # providing that this copyright notice is included with it. 8 | # SoftSurfer makes no warranty for this code, and cannot be held 9 | # liable for any real or imagined damage resulting from its use. 10 | # Users of this code must verify correctness for their application. 11 | 12 | # translated to Python by Maciej Kalisiak 13 | 14 | # http://www.dgp.toronto.edu/~mac/e-stuff/ 15 | 16 | # a Point is represented as a tuple: (x,y) 17 | 18 | #=================================================================== 19 | 20 | # is_left(): tests if a point is Left|On|Right of an infinite line. 21 | 22 | # Input: three points P0, P1, and P2 23 | # Return: >0 for P2 left of the line through P0 and P1 24 | # =0 for P2 on the line 25 | # <0 for P2 right of the line 26 | # See: the January 2001 Algorithm "Area of 2D and 3D Triangles and Polygons" 27 | 28 | def is_left(P0, P1, P2): 29 | return (P1[0] - P0[0]) * (P2[1] - P0[1]) - (P2[0] - P0[0]) * (P1[1] - P0[1]) 30 | 31 | #=================================================================== 32 | 33 | # cn_PnPoly(): crossing number test for a point in a polygon 34 | # Input: P = a point, 35 | # V[] = vertex points of a polygon 36 | # Return: 0 = outside, 1 = inside 37 | # This code is patterned after [Franklin, 2000] 38 | 39 | def cn_PnPoly(P, V): 40 | cn = 0 # the crossing number counter 41 | 42 | # repeat the first vertex at end 43 | V = tuple(V[:])+(V[0],) 44 | 45 | # loop through all edges of the polygon 46 | for i in range(len(V)-1): # edge from V[i] to V[i+1] 47 | if ((V[i][1] <= P[1] and V[i+1][1] > P[1]) # an upward crossing 48 | or (V[i][1] > P[1] and V[i+1][1] <= P[1])): # a downward crossing 49 | # compute the actual edge-ray intersect x-coordinate 50 | vt = (P[1] - V[i][1]) / float(V[i+1][1] - V[i][1]) 51 | if P[0] < V[i][0] + vt * (V[i+1][0] - V[i][0]): # P[0] < intersect 52 | cn += 1 # a valid crossing of y=P[1] right of P[0] 53 | 54 | return cn % 2 # 0 if even (out), and 1 if odd (in) 55 | 56 | #=================================================================== 57 | 58 | # wn_PnPoly(): winding number test for a point in a polygon 59 | # Input: P = a point, 60 | # V[] = vertex points of a polygon 61 | # Return: wn = the winding number (=0 only if P is outside V[]) 62 | 63 | def wn_PnPoly(P, V): 64 | wn = 0 # the winding number counter 65 | 66 | # repeat the first vertex at end 67 | V = tuple(V[:]) + (V[0],) 68 | 69 | # loop through all edges of the polygon 70 | for i in range(len(V)-1): # edge from V[i] to V[i+1] 71 | if V[i][1] <= P[1]: # start y <= P[1] 72 | if V[i+1][1] > P[1]: # an upward crossing 73 | if is_left(V[i], V[i+1], P) > 0: # P left of edge 74 | wn += 1 # have a valid up intersect 75 | else: # start y > P[1] (no test needed) 76 | if V[i+1][1] <= P[1]: # a downward crossing 77 | if is_left(V[i], V[i+1], P) < 0: # P right of edge 78 | wn -= 1 # have a valid down intersect 79 | return wn 80 | -------------------------------------------------------------------------------- /openmodes/external/three.js/Detector.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author alteredq / http://alteredqualia.com/ 3 | * @author mr.doob / http://mrdoob.com/ 4 | */ 5 | 6 | var Detector = { 7 | 8 | canvas: !! window.CanvasRenderingContext2D, 9 | webgl: ( function () { try { var canvas = document.createElement( 'canvas' ); return !! ( window.WebGLRenderingContext && ( canvas.getContext( 'webgl' ) || canvas.getContext( 'experimental-webgl' ) ) ); } catch( e ) { return false; } } )(), 10 | workers: !! window.Worker, 11 | fileapi: window.File && window.FileReader && window.FileList && window.Blob, 12 | 13 | getWebGLErrorMessage: function () { 14 | 15 | var element = document.createElement( 'div' ); 16 | element.id = 'webgl-error-message'; 17 | element.style.fontFamily = 'monospace'; 18 | element.style.fontSize = '13px'; 19 | element.style.fontWeight = 'normal'; 20 | element.style.textAlign = 'center'; 21 | element.style.background = '#fff'; 22 | element.style.color = '#000'; 23 | element.style.padding = '1.5em'; 24 | element.style.width = '400px'; 25 | element.style.margin = '5em auto 0'; 26 | 27 | if ( ! this.webgl ) { 28 | 29 | element.innerHTML = window.WebGLRenderingContext ? [ 30 | 'Your graphics card does not seem to support WebGL.
', 31 | 'Find out how to get it here.' 32 | ].join( '\n' ) : [ 33 | 'Your browser does not seem to support WebGL.
', 34 | 'Find out how to get it here.' 35 | ].join( '\n' ); 36 | 37 | } 38 | 39 | return element; 40 | 41 | }, 42 | 43 | addGetWebGLMessage: function ( parameters ) { 44 | 45 | var parent, id, element; 46 | 47 | parameters = parameters || {}; 48 | 49 | parent = parameters.parent !== undefined ? parameters.parent : document.body; 50 | id = parameters.id !== undefined ? parameters.id : 'oldie'; 51 | 52 | element = Detector.getWebGLErrorMessage(); 53 | element.id = id; 54 | 55 | parent.appendChild( element ); 56 | 57 | } 58 | 59 | }; 60 | 61 | // browserify support 62 | if ( typeof module === 'object' ) { 63 | 64 | module.exports = Detector; 65 | 66 | } 67 | -------------------------------------------------------------------------------- /openmodes/external/three.js/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright © 2010-2014 three.js authors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /openmodes/external/three.js/README.md: -------------------------------------------------------------------------------- 1 | This directory contains required code from the javascript library three.js. 2 | See http://threejs.org or https://github.com/mrdoob/three.js/ 3 | 4 | See the file LICENSE for information on license and copyright. 5 | 6 | The included files are taken from version r69. 7 | -------------------------------------------------------------------------------- /openmodes/geometry/SRR.geo: -------------------------------------------------------------------------------- 1 | // a 2D split ring resonator 2 | 3 | // allow the geometric parameters to be specified on the command-line 4 | If (!Exists(inner_radius)) 5 | inner_radius = 3.5e-3; 6 | EndIf 7 | 8 | If (!Exists(outer_radius)) 9 | outer_radius = 4e-3; 10 | EndIf 11 | 12 | If (!Exists(gap_width)) 13 | gap_width = 1e-3; 14 | EndIf 15 | 16 | If (!Exists(mesh_tol)) 17 | mesh_tol = 2e-3; 18 | EndIf 19 | 20 | srr_p = newp-1; 21 | 22 | // Define the SRR 23 | // define all points on the face 24 | Point(srr_p+1) = {0, 0, 0, mesh_tol}; 25 | Point(srr_p+2) = {-Sqrt(inner_radius^2-(0.5*gap_width)^2), -0.5*gap_width, 0, mesh_tol}; 26 | Point(srr_p+3) = {inner_radius, 0, 0, mesh_tol}; 27 | Point(srr_p+4) = {-Sqrt(inner_radius^2-(0.5*gap_width)^2), 0.5*gap_width, 0, mesh_tol}; 28 | Point(srr_p+5) = {-Sqrt(outer_radius^2-(0.5*gap_width)^2), 0.5*gap_width, 0, mesh_tol}; 29 | Point(srr_p+6) = {outer_radius, 0, 0, mesh_tol}; 30 | Point(srr_p+7) = {-Sqrt(outer_radius^2-(0.5*gap_width)^2), -0.5*gap_width, 0, mesh_tol}; 31 | 32 | srr_l = newl-1; 33 | 34 | // then the lines making the face 35 | Circle(srr_l+1) = {srr_p+2, srr_p+1, srr_p+3}; 36 | Circle(srr_l+2) = {srr_p+3, srr_p+1, srr_p+4}; 37 | Line(srr_l+3) = {srr_p+4, srr_p+5}; 38 | Circle(srr_l+4) = {srr_p+5, srr_p+1, srr_p+6}; 39 | Circle(srr_l+5) = {srr_p+6, srr_p+1, srr_p+7}; 40 | Line(srr_l+6) = {srr_p+7, srr_p+2}; 41 | 42 | Line Loop(srr_l+7) = {srr_l+1, srr_l+2, srr_l+3, srr_l+4, srr_l+5, srr_l+6}; 43 | 44 | srr_s = news; 45 | 46 | Plane Surface(srr_s) = {srr_l+7}; 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /openmodes/geometry/asymmetric_ring.geo: -------------------------------------------------------------------------------- 1 | // An asymmetric split ring, as per Fedotov 2 | 3 | // first design the SRR 4 | outer_radius = 6e-3; 5 | inner_radius = 5.2e-3; 6 | 7 | angle1 = 0.5*140.00*Pi/180; 8 | angle2 = 0.5*160.00*Pi/180; 9 | 10 | lc = 2e-3; 11 | 12 | p = newp-1; 13 | 14 | Point(p+1) = {0, 0, 0, lc}; 15 | Point(p+2) = {inner_radius*Sin(angle1), inner_radius*Cos(angle1), 0, lc}; 16 | Point(p+3) = {-inner_radius*Sin(angle1), inner_radius*Cos(angle1), 0, lc}; 17 | Point(p+4) = {-outer_radius*Sin(angle1), outer_radius*Cos(angle1), 0, lc}; 18 | Point(p+5) = {outer_radius*Sin(angle1), outer_radius*Cos(angle1), 0, lc}; 19 | 20 | l = newl-1; 21 | 22 | Circle(l+1) = {p+2, p+1, p+3}; 23 | Line(l+2) = {p+3, p+4}; 24 | Circle(l+3) = {p+4, p+1, p+5}; 25 | Line(l+4) = {p+5, p+2}; 26 | 27 | Line Loop(l+5) = {l+1, l+2, l+3, l+4}; 28 | 29 | s = news-1; 30 | 31 | Plane Surface(s+1) = {l+5}; 32 | 33 | Point(p+6) = {inner_radius*Sin(angle2), -inner_radius*Cos(angle2), 0, lc}; 34 | Point(p+7) = {-inner_radius*Sin(angle2), -inner_radius*Cos(angle2), 0, lc}; 35 | Point(p+8) = {-outer_radius*Sin(angle2), -outer_radius*Cos(angle2), 0, lc}; 36 | Point(p+9) = {outer_radius*Sin(angle2), -outer_radius*Cos(angle2), 0, lc}; 37 | 38 | Circle(l+6) = {p+6, p+1, p+7}; 39 | Line(l+7) = {p+7, p+8}; 40 | Circle(l+8) = {p+8, p+1, p+9}; 41 | Line(l+9) = {p+9, p+6}; 42 | 43 | Line Loop(l+10) = {l+6, l+7, l+8, l+9}; 44 | 45 | Plane Surface(s+2) = {l+10}; 46 | 47 | Physical Surface("left") = {s+1}; 48 | Physical Surface("right") = {s+2}; 49 | 50 | -------------------------------------------------------------------------------- /openmodes/geometry/box.geo: -------------------------------------------------------------------------------- 1 | // a box with sharp edges 2 | 3 | // geometric parameters specifiable from the command line 4 | If (!Exists(len_x)) 5 | len_x = 12e-3; 6 | EndIf 7 | 8 | If (!Exists(len_y)) 9 | len_y = 12e-3; 10 | EndIf 11 | 12 | If (!Exists(len_z)) 13 | len_z = 3e-3; 14 | EndIf 15 | 16 | If (!Exists(mesh_tol)) 17 | mesh_tol = 3e-3; 18 | EndIf 19 | 20 | p = newp-1; 21 | 22 | // define all points on the bottom face 23 | Point(p+1) = {-0.5*len_x, -0.5*len_y, -0.5*len_z, mesh_tol}; 24 | Point(p+2) = {-0.5*len_x, 0.5*len_y, -0.5*len_z, mesh_tol}; 25 | Point(p+3) = { 0.5*len_x, 0.5*len_y, -0.5*len_z, mesh_tol}; 26 | Point(p+4) = { 0.5*len_x, -0.5*len_y, -0.5*len_z, mesh_tol}; 27 | 28 | 29 | l = newl-1; 30 | 31 | // then the lines making the face 32 | Line(l+1) = {p+1, p+2}; 33 | Line(l+2) = {p+2, p+3}; 34 | Line(l+3) = {p+3, p+4}; 35 | Line(l+4) = {p+4, p+1}; 36 | 37 | Line Loop(l+9) = {l+1, l+2, l+3, l+4}; 38 | 39 | s = news; 40 | 41 | // gmsh extrusian has problems with surface normals, which are fixed 42 | // by using the following sequence of commands 43 | Plane Surface(s) = {-(l+9)}; 44 | out[] = Extrude{0,0,len_z}{ Surface{s}; }; 45 | Reverse Surface{s}; 46 | 47 | -------------------------------------------------------------------------------- /openmodes/geometry/canonical_spiral.geo: -------------------------------------------------------------------------------- 1 | lc = 2e-3; 2 | 3 | If (!Exists(inner_radius)) 4 | inner_radius = 3e-3; 5 | EndIf 6 | 7 | If (!Exists(outer_radius)) 8 | outer_radius = 4e-3; 9 | EndIf 10 | 11 | If (!Exists(gap_width)) 12 | gap_width = 3e-3; 13 | EndIf 14 | 15 | If (!Exists(arm_length)) 16 | arm_length = 10e-3; 17 | EndIf 18 | 19 | gap_inner_x = -Sqrt(inner_radius^2-(0.5*gap_width)^2); 20 | gap_outer_x = -Sqrt(outer_radius^2-(0.5*gap_width)^2); 21 | gap_r = 0.5*gap_width; 22 | 23 | p = newp-1; 24 | l = newl-1; 25 | loop_l = newll; 26 | s = news-1; 27 | 28 | // The loop face 29 | Point(p+1) = {0, 0, 0, lc}; 30 | Point(p+2) = {gap_inner_x, -gap_r, 0, lc}; 31 | Point(p+3) = {inner_radius, 0, 0, lc}; 32 | Point(p+4) = {gap_inner_x, gap_r, 0, lc}; 33 | Point(p+5) = {gap_outer_x, gap_r, 0, lc}; 34 | Point(p+6) = {outer_radius, 0, 0, lc}; 35 | Point(p+7) = {gap_outer_x, -gap_r, 0, lc}; 36 | 37 | Circle(l+1) = {p+2, p+1, p+3}; 38 | Circle(l+2) = {p+3, p+1, p+4}; 39 | Line(l+3) = {p+4, p+5}; 40 | Circle(l+4) = {p+5, p+1, p+6}; 41 | Circle(l+5) = {p+6, p+1, p+7}; 42 | Line(l+6) = {p+7, p+2}; 43 | 44 | Line Loop(loop_l+1) = {l+1, l+2, l+3, l+4, l+5, l+6}; 45 | Plane Surface(s+1) = {loop_l+1}; 46 | 47 | // The curved lower join 48 | Point(p+8) = {gap_outer_x, -gap_r, -gap_r, lc}; 49 | Point(p+9) = {gap_outer_x, 0, -gap_r, lc}; 50 | Point(p+10) = {gap_inner_x, 0, -gap_r, lc}; 51 | Point(p+11) = {gap_inner_x, -gap_r, -gap_r, lc}; 52 | 53 | Circle(l+7) = {p+7, p+8, p+9}; 54 | Circle(l+11) = {p+10, p+11, p+2}; 55 | Line(l+12) = {p+9, p+10}; 56 | 57 | Line Loop(loop_l+2) = {-(l+6), l+7, l+12, l+11}; 58 | Ruled Surface(s+2) = {loop_l+2}; 59 | 60 | // The lower arm 61 | Point(p+12) = {gap_outer_x, 0, -arm_length, lc}; 62 | Point(p+13) = {gap_inner_x, 0, -arm_length, lc}; 63 | Point(p+14) = {gap_outer_x, 0, -arm_length, lc}; 64 | Point(p+15) = {gap_inner_x, 0, -arm_length, lc}; 65 | 66 | Line(l+8) = {p+9, p+12}; 67 | Line(l+9) = {p+12, p+13}; 68 | Line(l+10) = {p+13, p+10}; 69 | 70 | Line Loop(loop_l+3) = {l+8, l+9, l+10, -(l+12)}; 71 | Plane Surface(s+3) = {loop_l+3}; 72 | 73 | // The curved upper join 74 | Point(p+16) = {gap_outer_x, gap_r, gap_r, lc}; 75 | Point(p+17) = {gap_outer_x, 0, gap_r, lc}; 76 | Point(p+18) = {gap_inner_x, 0, gap_r, lc}; 77 | Point(p+19) = {gap_inner_x, gap_r, gap_r, lc}; 78 | 79 | Circle(l+13) = {p+5, p+16, p+17}; 80 | Line(l+14) = {p+17, p+18}; 81 | Circle(l+15) = {p+18, p+19, p+4}; 82 | 83 | Line Loop(loop_l+4) = {l+13, l+14, l+15, l+3}; 84 | Ruled Surface(s+4) = {loop_l+4}; 85 | 86 | // The upper arm 87 | Point(p+20) = {gap_outer_x, 0, arm_length, lc}; 88 | Point(p+21) = {gap_inner_x, 0, arm_length, lc}; 89 | Point(p+22) = {gap_outer_x, 0, arm_length, lc}; 90 | Point(p+23) = {gap_inner_x, 0, arm_length, lc}; 91 | 92 | Line(l+16) = {p+17, p+20}; 93 | Line(l+17) = {p+20, p+21}; 94 | Line(l+18) = {p+21, p+18}; 95 | 96 | Line Loop(loop_l+5) = {l+16, l+17, l+18, -(l+14)}; 97 | Plane Surface(s+5) = {loop_l+5}; 98 | 99 | Compound Surface(s+6) = {s+1, s+2, s+3, s+4, s+5, s+6}; 100 | Physical Surface(s+7) = {s+6}; 101 | -------------------------------------------------------------------------------- /openmodes/geometry/circle.geo: -------------------------------------------------------------------------------- 1 | // a planar circle 2 | 3 | If (!Exists(outer_radius)) 4 | outer_radius = 4e-3; 5 | EndIf 6 | 7 | If (!Exists(mesh_tol)) 8 | mesh_tol = 3e-3; 9 | EndIf 10 | 11 | p = newp-1; 12 | 13 | // define all points on the face 14 | Point(p+1) = {0, 0, 0, mesh_tol}; 15 | Point(p+4) = {-outer_radius, 0, 0, mesh_tol}; 16 | Point(p+5) = {outer_radius, 0, 0, mesh_tol}; 17 | 18 | l = newl-1; 19 | 20 | // then the lines making the face 21 | Circle(l+3) = {p+5, p+1, p+4}; 22 | Circle(l+4) = {p+4, p+1, p+5}; 23 | 24 | Line Loop(l+8) = {l+3, l+4}; 25 | 26 | s = news; 27 | 28 | Plane Surface(s) = {l+8}; 29 | 30 | 31 | -------------------------------------------------------------------------------- /openmodes/geometry/circled_cross.geo: -------------------------------------------------------------------------------- 1 | // a cross with circular arcs on the ends of its arms, or optionally a gammadion 2 | // specify the parameters positive_arc_angle and negative_arc_angle (in degrees) to control 3 | // the circular arcs at the ends of the arms 4 | 5 | If (!Exists(width)) 6 | width = 1e-3; 7 | EndIf 8 | 9 | If (!Exists(r_outer)) 10 | r_outer = 10e-3; 11 | EndIf 12 | 13 | If (!Exists(positive_arc_angle)) 14 | positive_arc_angle = 10.0; 15 | EndIf 16 | 17 | If (!Exists(negative_arc_angle)) 18 | negative_arc_angle = positive_arc_angle; 19 | EndIf 20 | 21 | If (!Exists(complementary_radius)) 22 | complementary_radius = 0.0; 23 | EndIf 24 | 25 | If (!Exists(mesh_tol)) 26 | mesh_tol=2e-3; 27 | EndIf 28 | 29 | negative_arc_angle = negative_arc_angle/180.0*Pi; 30 | positive_arc_angle = positive_arc_angle/180.0*Pi; 31 | 32 | p = newp-1; 33 | 34 | r_inner = r_outer-width; 35 | 36 | // Define the dipole 37 | // define all points on the face 38 | Point(p+1) = {0, 0, 0, mesh_tol}; 39 | 40 | Point(p+2) = {-0.5*width, 0.5*width, 0, mesh_tol}; 41 | 42 | y_inner = Sqrt(r_inner^2 -(0.5*width)^2); 43 | y_outer = Sqrt(r_outer^2 -(0.5*width)^2); 44 | 45 | Point(p+3) = {-0.5*width, y_inner, 0.0, mesh_tol}; 46 | 47 | If (negative_arc_angle != 0.0) 48 | Point(p+4) = {-r_inner*Sin(negative_arc_angle), r_inner*Cos(negative_arc_angle), 0.0, mesh_tol}; 49 | Point(p+5) = {-r_outer*Sin(negative_arc_angle), r_outer*Cos(negative_arc_angle), 0.0, mesh_tol}; 50 | EndIf 51 | 52 | If (negative_arc_angle == 0.0) 53 | Point(p+4) = {-0.5*width, 0.5*(y_inner+y_outer), 0.0, mesh_tol}; 54 | Point(p+5) = {-0.5*width, y_outer, 0.0, mesh_tol}; 55 | EndIf 56 | 57 | If (positive_arc_angle != 0.0) 58 | Point(p+6) = {r_outer*Sin(positive_arc_angle), r_outer*Cos(positive_arc_angle), 0.0, mesh_tol}; 59 | Point(p+7) = {r_inner*Sin(positive_arc_angle), r_inner*Cos(positive_arc_angle), 0.0, mesh_tol}; 60 | EndIf 61 | 62 | If (positive_arc_angle == 0.0) 63 | Point(p+6) = {0.5*width, y_outer, 0.0, mesh_tol}; 64 | Point(p+7) = {0.5*width, 0.5*(y_inner+y_outer), 0.0, mesh_tol}; 65 | EndIf 66 | 67 | Point(p+8) = {0.5*width, y_inner, 0.0, mesh_tol}; 68 | 69 | l = newl-1; 70 | 71 | Line(l+1) = {p+2, p+3}; 72 | 73 | If (negative_arc_angle != 0.0) 74 | Circle(l+2) = {p+3, p+1, p+4}; 75 | EndIf 76 | If (negative_arc_angle == 0.0) 77 | Line(l+2) = {p+3, p+4}; 78 | EndIf 79 | 80 | Line(l+3) = {p+4, p+5}; 81 | Circle(l+4) = {p+5, p+1, p+6}; 82 | Line(l+5) = {p+6, p+7}; 83 | 84 | If (positive_arc_angle != 0.0) 85 | Circle(l+6) = {p+7, p+1, p+8}; 86 | EndIf 87 | If (positive_arc_angle == 0.0) 88 | Line(l+6) = {p+7, p+8}; 89 | EndIf 90 | 91 | 92 | copy1[] = Rotate {{0, 0, 1}, {0, 0, 0}, -0.5*Pi} {Duplicata {Point {p+2, p+3, p+4, p+5, p+6, p+7, p+8 }; Line {l+1, l+2, l+3, l+4, l+5, l+6 };}}; 93 | copy2[] = Rotate {{0, 0, 1}, {0, 0, 0}, Pi} {Duplicata {Point {p+2, p+3, p+4, p+5, p+6, p+7, p+8 }; Line {l+1, l+2, l+3, l+4, l+5, l+6 };}}; 94 | copy3[] = Rotate {{0, 0, 1}, {0, 0, 0}, 0.5*Pi} {Duplicata {Point {p+2, p+3, p+4, p+5, p+6, p+7, p+8 }; Line {l+1, l+2, l+3, l+4, l+5, l+6 };}}; 95 | 96 | l2 = newl-1; 97 | 98 | Line(l2+1) = {p+8, copy1[0]}; 99 | Line(l2+2) = {copy1[6], copy2[0]}; 100 | Line(l2+3) = {copy2[6], copy3[0]}; 101 | Line(l2+4) = {copy3[6], l+2}; 102 | 103 | ll = newll; 104 | 105 | 106 | Line Loop(ll) = {l+1, l+2, l+3, l+4, l+5, l+6, l2+1, 107 | copy1[7], copy1[8], copy1[9], copy1[10], copy1[11], copy1[12], l2+2, 108 | copy2[7], copy2[8], copy2[9], copy2[10], copy2[11], copy2[12], l2+3, 109 | copy3[7], copy3[8], copy3[9], copy3[10], copy3[11], copy3[12], l2+4}; 110 | 111 | s = news; 112 | 113 | If (complementary_radius == 0.0) 114 | Plane Surface(s) = {ll}; 115 | Physical Surface(s+1) = {s}; 116 | EndIf 117 | 118 | // Optionally, create the complementary structure, and embed it 119 | // in a circle of given radius. Setting this radius to 0 (the default) 120 | // means that the positive structure will be created. 121 | If (complementary_radius != 0.0) 122 | p2 = newp-1; 123 | Point(p2+1) = {complementary_radius, 0, 0, mesh_tol}; 124 | Point(p2+2) = {0, complementary_radius, 0, mesh_tol}; 125 | Point(p2+3) = {-complementary_radius, 0, 0, mesh_tol}; 126 | Point(p2+4) = {0, -complementary_radius, 0, mesh_tol}; 127 | 128 | l = newl-1; 129 | Circle(l+1) = {p2+1, p+1, p2+2}; 130 | Circle(l+2) = {p2+2, p+1, p2+3}; 131 | Circle(l+3) = {p2+3, p+1, p2+4}; 132 | Circle(l+4) = {p2+4, p+1, p2+1}; 133 | 134 | Line Loop(ll+1) = {l+1, l+2, l+3, l+4}; 135 | 136 | Plane Surface(s) = {ll+1, ll}; 137 | Physical Surface(s+1) = {s}; 138 | EndIf 139 | -------------------------------------------------------------------------------- /openmodes/geometry/closed_ring.geo: -------------------------------------------------------------------------------- 1 | // a planar closed ring 2 | 3 | If (!Exists(inner_radius)) 4 | inner_radius = 3.5e-3; 5 | EndIf 6 | 7 | If (!Exists(outer_radius)) 8 | outer_radius = 4e-3; 9 | EndIf 10 | 11 | If (!Exists(mesh_tol)) 12 | mesh_tol = 2e-3; 13 | EndIf 14 | 15 | p = newp-1; 16 | 17 | // define all points on the face 18 | Point(p+1) = {0, 0, 0, mesh_tol}; 19 | Point(p+2) = {-inner_radius, 0, 0, mesh_tol}; 20 | Point(p+3) = {inner_radius, 0, 0, mesh_tol}; 21 | Point(p+4) = {-outer_radius, 0, 0, mesh_tol}; 22 | Point(p+5) = {outer_radius, 0, 0, mesh_tol}; 23 | 24 | l = newl-1; 25 | 26 | // then the lines making the face 27 | Circle(l+1) = {p+2, p+1, p+3}; 28 | Circle(l+2) = {p+3, p+1, p+2}; 29 | 30 | Circle(l+3) = {p+5, p+1, p+4}; 31 | Circle(l+4) = {p+4, p+1, p+5}; 32 | 33 | Line Loop(l+7) = {l+1, l+2}; 34 | Line Loop(l+8) = {l+3, l+4}; 35 | 36 | s = news; 37 | 38 | Plane Surface(s) = {l+8, l+7}; 39 | 40 | 41 | -------------------------------------------------------------------------------- /openmodes/geometry/cross.geo: -------------------------------------------------------------------------------- 1 | // a flat cross 2 | 3 | If (!Exists(width)) 4 | width = 1e-3; 5 | EndIf 6 | 7 | If (!Exists(height)) 8 | height = 10e-3; 9 | EndIf 10 | 11 | If (!Exists(complementary_radius)) 12 | complementary_radius = 0.0; 13 | EndIf 14 | 15 | If (!Exists(mesh_tol)) 16 | mesh_tol=2e-3; 17 | EndIf 18 | 19 | p = newp-1; 20 | 21 | // Define the dipole 22 | // define all points on the face 23 | Point(p+1) = {-0.5*width, -0.5*height, 0, mesh_tol}; 24 | Point(p+2) = { 0.5*width, -0.5*height, 0, mesh_tol}; 25 | Point(p+3) = { 0.5*width, -0.5*width, 0, mesh_tol}; 26 | Point(p+4) = {0.5*height, -0.5*width, 0, mesh_tol}; 27 | Point(p+5) = {0.5*height, 0.5*width, 0, mesh_tol}; 28 | Point(p+6) = {0.5*width, 0.5*width, 0, mesh_tol}; 29 | Point(p+7) = {0.5*width, 0.5*height, 0, mesh_tol}; 30 | Point(p+8) = {-0.5*width, 0.5*height, 0, mesh_tol}; 31 | Point(p+9) = {-0.5*width, 0.5*width, 0, mesh_tol}; 32 | Point(p+10) = {-0.5*height, 0.5*width, 0, mesh_tol}; 33 | Point(p+11) = {-0.5*height, -0.5*width, 0, mesh_tol}; 34 | Point(p+12) = {-0.5*width, -0.5*width, 0, mesh_tol}; 35 | 36 | l = newl-1; 37 | 38 | Line(l+1) = {1, 2}; 39 | Line(l+2) = {2, 3}; 40 | Line(l+3) = {3, 4}; 41 | Line(l+4) = {4, 5}; 42 | Line(l+5) = {5, 6}; 43 | Line(l+6) = {6, 7}; 44 | Line(l+7) = {7, 8}; 45 | Line(l+8) = {8, 9}; 46 | Line(l+9) = {9, 10}; 47 | Line(l+10) = {10, 11}; 48 | Line(l+11) = {11, 12}; 49 | Line(l+12) = {12, 1}; 50 | 51 | ll = newll; 52 | Line Loop(ll) = {l+1, l+2, l+3, l+4, l+5, l+6, l+7, l+8, l+9, l+10, l+11, l+12}; 53 | 54 | s = news; 55 | 56 | If (complementary_radius == 0.0) 57 | Plane Surface(s) = {ll}; 58 | Physical Surface(s+1) = {s}; 59 | EndIf 60 | 61 | // Optionally, create the complementary structure, and embed it 62 | // in a circle of given radius. Setting this radius to 0 (the default) 63 | // means that the positive structure will be created. 64 | If (complementary_radius != 0.0) 65 | p2 = newp-1; 66 | Point(p2+1) = {complementary_radius, 0, 0, mesh_tol}; 67 | Point(p2+2) = {0, complementary_radius, 0, mesh_tol}; 68 | Point(p2+3) = {-complementary_radius, 0, 0, mesh_tol}; 69 | Point(p2+4) = {0, -complementary_radius, 0, mesh_tol}; 70 | Point(p2+5) = {0, 0, 0, mesh_tol}; 71 | 72 | l = newl-1; 73 | Circle(l+1) = {p2+1, p2+5, p2+2}; 74 | Circle(l+2) = {p2+2, p2+5, p2+3}; 75 | Circle(l+3) = {p2+3, p2+5, p2+4}; 76 | Circle(l+4) = {p2+4, p2+5, p2+1}; 77 | 78 | Line Loop(ll+1) = {l+1, l+2, l+3, l+4}; 79 | 80 | Plane Surface(s) = {ll+1, ll}; 81 | Physical Surface(s+1) = {s}; 82 | EndIf 83 | -------------------------------------------------------------------------------- /openmodes/geometry/cylinder_hole.geo: -------------------------------------------------------------------------------- 1 | // A cylinder with rounded edges, having a partial hole through one side 2 | // See Alaee et al, Phys Rev B 92, 245130 (2015) 3 | 4 | // outer radius 5 | If (!Exists(radius)) 6 | radius = 4e-3; 7 | EndIf 8 | 9 | // radius of rounding 10 | If (!Exists(rounding)) 11 | rounding = radius*0.2; 12 | EndIf 13 | 14 | // total height 15 | If (!Exists(height)) 16 | height = 4e-3; 17 | EndIf 18 | 19 | // hole radius 20 | If (!Exists(hole_radius)) 21 | hole_radius = radius*0.3; 22 | EndIf 23 | 24 | // hole height 25 | If (!Exists(hole_height)) 26 | hole_height = height*0.5; 27 | EndIf 28 | 29 | If (!Exists(mesh_tol)) 30 | mesh_tol = radius/3.0; 31 | EndIf 32 | 33 | 34 | p = newp-1; 35 | l = newl-1; 36 | 37 | 38 | bottom_z = -height*0.5; 39 | top_z = height*0.5; 40 | 41 | Point(p+1) = { 0, 0, bottom_z, mesh_tol }; 42 | Point(p+2) = { radius-rounding, 0, bottom_z, mesh_tol }; 43 | Point(p+3) = { radius-rounding, 0, bottom_z+rounding, mesh_tol }; 44 | Point(p+4) = { radius, 0, bottom_z+rounding, mesh_tol }; 45 | Point(p+5) = { radius, 0, top_z-rounding, mesh_tol }; 46 | Point(p+6) = { radius-rounding, 0, top_z-rounding, mesh_tol }; 47 | Point(p+7) = { radius-rounding, 0, top_z, mesh_tol }; 48 | Point(p+8) = { hole_radius+rounding, 0, top_z, mesh_tol }; 49 | Point(p+9) = { hole_radius+rounding, 0, top_z-rounding, mesh_tol }; 50 | Point(p+10) = { hole_radius, 0, top_z-rounding, mesh_tol }; 51 | Point(p+11) = { hole_radius, 0, top_z-hole_height+rounding, mesh_tol }; 52 | Point(p+12) = { hole_radius-rounding, 0, top_z-hole_height+rounding, mesh_tol }; 53 | Point(p+13) = { hole_radius-rounding, 0, top_z-hole_height, mesh_tol }; 54 | Point(p+14) = {0, 0, top_z-hole_height, mesh_tol }; 55 | 56 | Line(l+1) = { p+1, p+2 }; 57 | Circle(l+2) = {p+2, p+3, p+4}; 58 | Line(l+3) = {p+4, p+5}; 59 | Circle(l+4) = {p+5, p+6, p+7}; 60 | Line(l+5) = {p+7, p+8}; 61 | Circle(l+6) = {p+8, p+9, p+10}; 62 | Line(l+7) = {p+10, p+11}; 63 | Circle(l+8) = {p+11, p+12, p+13}; 64 | Line(l+9) = {p+13, p+14}; 65 | 66 | // The list "sides" will contain all the surfaces of the final object 67 | sides[] = {}; 68 | For it In {1:9} 69 | rotated_geo[] = Extrude{ {0, 0, 1}, { 0, 0, 0 }, 0.5*Pi} {Line{l+it};}; 70 | sides += rotated_geo[1]; 71 | 72 | rotated_geo[] = Extrude{ {0, 0, 1}, { 0, 0, 0 }, 0.5*Pi} {Line{rotated_geo[0]};}; 73 | sides += rotated_geo[1]; 74 | 75 | rotated_geo[] = Extrude{ {0, 0, 1}, { 0, 0, 0 }, 0.5*Pi} {Line{rotated_geo[0]};}; 76 | sides += rotated_geo[1]; 77 | 78 | rotated_geo[] = Extrude{ {0, 0, 1}, { 0, 0, 0 }, 0.5*Pi} {Line{rotated_geo[0]};}; 79 | sides += rotated_geo[1]; 80 | 81 | EndFor 82 | 83 | -------------------------------------------------------------------------------- /openmodes/geometry/cylinder_hollow.geo: -------------------------------------------------------------------------------- 1 | // A cylinder with a hole, which may be off-centre 2 | 3 | SetFactory("OpenCASCADE"); 4 | 5 | Mesh.Algorithm = 6; 6 | Mesh.CharacteristicLengthMin = mesh_tol; 7 | Mesh.CharacteristicLengthMax = mesh_tol; 8 | 9 | // inner radius 10 | If (!Exists(inner_radius)) 11 | inner_radius = 1e-3; 12 | EndIf 13 | 14 | // outer radius 15 | If (!Exists(outer_radius)) 16 | outer_radius = 2e-3; 17 | EndIf 18 | 19 | // radius of filleting edges 20 | If (!Exists(rounding)) 21 | rounding = 0.2e-3; 22 | EndIf 23 | 24 | // height of cylinder 25 | If (!Exists(height)) 26 | height = 4e-3; 27 | EndIf 28 | 29 | // offset of inner hole from centre 30 | If (!Exists(offset)) 31 | offset = 0.2e-3; 32 | EndIf 33 | 34 | Cylinder(1) = {0,0,-0.5*height, 0,0,height, outer_radius}; 35 | Cylinder(2) = {offset,0,-0.5*height, 0,0,height, inner_radius}; 36 | BooleanDifference(3) = { Volume{1}; Delete; }{ Volume{2}; Delete; }; 37 | Fillet{3}{1,3,4,5}{rounding} 38 | 39 | -------------------------------------------------------------------------------- /openmodes/geometry/cylinder_rounded.geo: -------------------------------------------------------------------------------- 1 | // a cylinder with rounded edges 2 | 3 | // outer radius 4 | If (!Exists(radius)) 5 | radius = 4e-3; 6 | EndIf 7 | 8 | // radius of rounding 9 | If (!Exists(rounding)) 10 | rounding = radius*0.2; 11 | EndIf 12 | 13 | // total height 14 | If (!Exists(height)) 15 | height = 4e-3; 16 | EndIf 17 | 18 | If (!Exists(mesh_tol)) 19 | mesh_tol = radius/3.0; 20 | EndIf 21 | 22 | p = newp-1; 23 | l = newl-1; 24 | 25 | end_radius = radius-rounding; 26 | 27 | // The bottom face 28 | bottom_z = -height*0.5; 29 | 30 | Point(p+1) = {0, 0, bottom_z, mesh_tol}; 31 | Point(p+2) = {-end_radius, 0, bottom_z, mesh_tol}; 32 | Point(p+3) = {0, end_radius, bottom_z, mesh_tol}; 33 | Point(p+4) = {end_radius, 0, bottom_z, mesh_tol}; 34 | Point(p+5) = {0, -end_radius, bottom_z, mesh_tol}; 35 | 36 | Circle(l+1) = {p+2, p+1, p+3}; 37 | Circle(l+2) = {p+3, p+1, p+4}; 38 | Circle(l+3) = {p+4, p+1, p+5}; 39 | Circle(l+4) = {p+5, p+1, p+2}; 40 | 41 | // The bottom full width circle 42 | lower_z = -height*0.5+rounding; 43 | 44 | Point(p+6) = {0, 0, lower_z, mesh_tol}; 45 | Point(p+7) = {-radius, 0, lower_z, mesh_tol}; 46 | Point(p+8) = {0, radius, lower_z, mesh_tol}; 47 | Point(p+9) = {radius, 0, lower_z, mesh_tol}; 48 | Point(p+10) = {0, -radius, lower_z, mesh_tol}; 49 | 50 | Circle(l+5) = {p+7, p+6, p+8}; 51 | Circle(l+6) = {p+8, p+6, p+9}; 52 | Circle(l+7) = {p+9, p+6, p+10}; 53 | Circle(l+8) = {p+10, p+6, p+7}; 54 | 55 | // The top full width circle 56 | upper_z = height*0.5-rounding; 57 | 58 | Point(p+11) = {0, 0, upper_z, mesh_tol}; 59 | Point(p+12) = {-radius, 0, upper_z, mesh_tol}; 60 | Point(p+13) = {0, radius, upper_z, mesh_tol}; 61 | Point(p+14) = {radius, 0, upper_z, mesh_tol}; 62 | Point(p+15) = {0, -radius, upper_z, mesh_tol}; 63 | 64 | Circle(l+9) = {p+12, p+11, p+13}; 65 | Circle(l+10) = {p+13, p+11, p+14}; 66 | Circle(l+11) = {p+14, p+11, p+15}; 67 | Circle(l+12) = {p+15, p+11, p+12}; 68 | 69 | // The top face 70 | top_z = height*0.5; 71 | 72 | Point(p+16) = {0, 0, top_z, mesh_tol}; 73 | Point(p+17) = {-end_radius, 0, top_z, mesh_tol}; 74 | Point(p+18) = {0, end_radius, top_z, mesh_tol}; 75 | Point(p+19) = {end_radius, 0, top_z, mesh_tol}; 76 | Point(p+20) = {0, -end_radius, top_z, mesh_tol}; 77 | 78 | Circle(l+13) = {p+17, p+16, p+18}; 79 | Circle(l+14) = {p+18, p+16, p+19}; 80 | Circle(l+15) = {p+19, p+16, p+20}; 81 | Circle(l+16) = {p+20, p+16, p+17}; 82 | 83 | // The bottom rounded sections 84 | 85 | Point(p+21) = {-end_radius, 0, lower_z, mesh_tol}; 86 | Point(p+22) = {0, end_radius, lower_z, mesh_tol}; 87 | Point(p+23) = {end_radius, 0, lower_z, mesh_tol}; 88 | Point(p+24) = {0, -end_radius, lower_z, mesh_tol}; 89 | 90 | Circle(l+17) = {p+2, p+21, p+7}; 91 | Circle(l+18) = {p+3, p+22, p+8}; 92 | Circle(l+19) = {p+4, p+23, p+9}; 93 | Circle(l+20) = {p+5, p+24, p+10}; 94 | 95 | // The top rounded sections 96 | 97 | Point(p+25) = {-end_radius, 0, upper_z, mesh_tol}; 98 | Point(p+26) = {0, end_radius, upper_z, mesh_tol}; 99 | Point(p+27) = {end_radius, 0, upper_z, mesh_tol}; 100 | Point(p+28) = {0, -end_radius, upper_z, mesh_tol}; 101 | 102 | Circle(l+21) = {12, 25, 17}; 103 | Circle(l+22) = {13, 26, 18}; 104 | Circle(l+23) = {14, 27, 19}; 105 | Circle(l+24) = {15, 28, 20}; 106 | 107 | // The sides 108 | 109 | Line(l+25) = {p+7, p+12}; 110 | Line(l+26) = {p+8, p+13}; 111 | Line(l+27) = {p+9, p+14}; 112 | Line(l+28) = {p+10, p+15}; 113 | 114 | // Form the surfaces 115 | s = news-1; 116 | 117 | // top and bottom 118 | Line Loop(l+100) = {l+1, l+2, l+3, l+4}; 119 | Plane Surface(s+1) = {l+100}; 120 | Line Loop(l+101) = {l+13, l+14, l+15, l+16}; 121 | Plane Surface(s+2) = {-(l+101)}; 122 | 123 | // sides 124 | Line Loop(l+102) = {l+5, -(l+25), -(l+9), (l+26)}; 125 | Ruled Surface(s+3) = {-(l+102)}; 126 | Line Loop(l+103) = {l+6, -(l+26), -(l+10), (l+27)}; 127 | Ruled Surface(s+4) = {-(l+103)}; 128 | Line Loop(l+104) = {l+7, -(l+27), -(l+11), (l+28)}; 129 | Ruled Surface(s+5) = {-(l+104)}; 130 | Line Loop(l+105) = {l+8, -(l+28), -(l+12), (l+25)}; 131 | Ruled Surface(s+6) = {-(l+105)}; 132 | 133 | // bottom rounding 134 | Line Loop(l+106) = {l+1, -(l+17), -(l+5), (l+18)}; 135 | Ruled Surface(s+7) = {-(l+106)}; 136 | Line Loop(l+107) = {l+2, -(l+18), -(l+6), (l+19)}; 137 | Ruled Surface(s+8) = {-(l+107)}; 138 | Line Loop(l+108) = {l+3, -(l+19), -(l+7), (l+20)}; 139 | Ruled Surface(s+9) = {-(l+108)}; 140 | Line Loop(l+109) = {l+4, -(l+20), -(l+8), (l+17)}; 141 | Ruled Surface(s+10) = {-(l+109)}; 142 | 143 | // top rounding 144 | Line Loop(l+110) = {l+9, -(l+21), -(l+13), (l+22)}; 145 | Ruled Surface(s+11) = {-(l+110)}; 146 | Line Loop(l+111) = {l+10, -(l+22), -(l+14), (l+23)}; 147 | Ruled Surface(s+12) = {-(l+111)}; 148 | Line Loop(l+112) = {l+11, -(l+23), -(l+15), (l+24)}; 149 | Ruled Surface(s+13) = {-(l+112)}; 150 | Line Loop(l+113) = {l+12, -(l+24), -(l+16), (l+21)}; 151 | Ruled Surface(s+14) = {-(l+113)}; 152 | 153 | // Close the surface 154 | Surface Loop(s+50) = {s+1, s+2, s+3, s+4, s+5, s+6, s+7, s+8, s+9, s+10, s+11, s+12, s+13, s+14}; 155 | Volume(1) = {s+50}; 156 | -------------------------------------------------------------------------------- /openmodes/geometry/ellipsoid.geo: -------------------------------------------------------------------------------- 1 | // An ellipsoid sphere 2 | 3 | // Allow the radii to be specified on the command line 4 | If (!Exists(radius_x)) 5 | radius_x = 10e-3; 6 | EndIf 7 | 8 | If (!Exists(radius_y)) 9 | radius_y = 12e-3; 10 | EndIf 11 | 12 | If (!Exists(radius_z)) 13 | radius_z = 8e-3; 14 | EndIf 15 | 16 | // base element size on radius 17 | If (!Exists(mesh_tol)) 18 | mesh_tol = radius_x*0.2; 19 | EndIf 20 | 21 | Point(1) = {0.0,0.0,0.0,mesh_tol}; 22 | Point(2) = {radius_x,0.0,0.0,mesh_tol}; 23 | Point(3) = {0,radius_y,0.0,mesh_tol}; 24 | Point(4) = {-radius_x,0,0.0,mesh_tol}; 25 | Point(5) = {0,-radius_y,0.0,mesh_tol}; 26 | Point(6) = {0,0,-radius_z,mesh_tol}; 27 | Point(7) = {0,0,radius_z,mesh_tol}; 28 | 29 | // Find the points on the major axis for each ellipse 30 | If (radius_x > radius_y) 31 | major_xy = 2; 32 | EndIf 33 | If (radius_x <= radius_y) 34 | major_xy = 3; 35 | EndIf 36 | 37 | If (radius_x > radius_z) 38 | major_xz = 2; 39 | EndIf 40 | If (radius_x <= radius_z) 41 | major_xz = 6; 42 | EndIf 43 | 44 | If (radius_y > radius_z) 45 | major_yz = 3; 46 | EndIf 47 | If (radius_y <= radius_z) 48 | major_yz = 6; 49 | EndIf 50 | 51 | Ellipse(1) = {2,1,major_xy,3}; 52 | Ellipse(2) = {3,1,major_xy,4}; 53 | Ellipse(3) = {4,1,major_xy,5}; 54 | Ellipse(4) = {5,1,major_xy,2}; 55 | Ellipse(5) = {3,1,major_yz,6}; 56 | Ellipse(6) = {6,1,major_yz,5}; 57 | Ellipse(7) = {5,1,major_yz,7}; 58 | Ellipse(8) = {7,1,major_yz,3}; 59 | Ellipse(9) = {2,1,major_xz,7}; 60 | Ellipse(10) = {7,1,major_xz,4}; 61 | Ellipse(11) = {4,1,major_xz,6}; 62 | Ellipse(12) = {6,1,major_xz,2}; 63 | 64 | Line Loop(13) = {2,8,-10}; 65 | Ruled Surface(14) = {13}; 66 | Line Loop(15) = {10,3,7}; 67 | Ruled Surface(16) = {15}; 68 | Line Loop(17) = {-8,-9,1}; 69 | Ruled Surface(18) = {17}; 70 | Line Loop(19) = {-11,-2,5}; 71 | Ruled Surface(20) = {19}; 72 | Line Loop(21) = {-5,-12,-1}; 73 | Ruled Surface(22) = {21}; 74 | Line Loop(23) = {-3,11,6}; 75 | Ruled Surface(24) = {23}; 76 | Line Loop(25) = {-7,4,9}; 77 | Ruled Surface(26) = {25}; 78 | Line Loop(27) = {-4,12,-6}; 79 | Ruled Surface(28) = {27}; 80 | Surface Loop(29) = {28,26,16,14,20,24,22,18}; 81 | 82 | Volume(30) = {29}; 83 | 84 | -------------------------------------------------------------------------------- /openmodes/geometry/elliptical_cylinder.geo: -------------------------------------------------------------------------------- 1 | // an elliptical cylinder with rounded edges 2 | 3 | // radius in x dimension 4 | If (!Exists(radius_x)) 5 | radius_x = 4e-3; 6 | EndIf 7 | 8 | // radius in y dimension 9 | If (!Exists(radius_y)) 10 | radius_y = 6e-3; 11 | EndIf 12 | 13 | // radius of rounding 14 | If (!Exists(rounding)) 15 | rounding = radius_x*0.2; 16 | EndIf 17 | 18 | // total height 19 | If (!Exists(height)) 20 | height = 4e-3; 21 | EndIf 22 | 23 | If (!Exists(mesh_tol)) 24 | mesh_tol = radius_x/3.0; 25 | EndIf 26 | 27 | p = newp-1; 28 | l = newl-1; 29 | 30 | end_radius_x = radius_x-rounding; 31 | end_radius_y = radius_y-rounding; 32 | 33 | // The bottom face 34 | bottom_z = -height*0.5; 35 | 36 | Point(p+1) = {0, 0, bottom_z, mesh_tol}; 37 | Point(p+2) = {-end_radius_x, 0, bottom_z, mesh_tol}; 38 | Point(p+3) = {0, end_radius_y, bottom_z, mesh_tol}; 39 | Point(p+4) = {end_radius_x, 0, bottom_z, mesh_tol}; 40 | Point(p+5) = {0, -end_radius_y, bottom_z, mesh_tol}; 41 | 42 | // determine a point on the major axis 43 | If (radius_x > radius_y) 44 | major_point = p+2; 45 | Else 46 | major_point = p+3; 47 | EndIf 48 | 49 | // start, centre, point on major axis, end 50 | Ellipse(l+1) = {p+2, p+1, major_point, p+3}; 51 | Ellipse(l+2) = {p+3, p+1, major_point, p+4}; 52 | Ellipse(l+3) = {p+4, p+1, major_point, p+5}; 53 | Ellipse(l+4) = {p+5, p+1, major_point, p+2}; 54 | 55 | // The bottom full width circle 56 | lower_z = -height*0.5+rounding; 57 | 58 | Point(p+6) = {0, 0, lower_z, mesh_tol}; 59 | Point(p+7) = {-radius_x, 0, lower_z, mesh_tol}; 60 | Point(p+8) = {0, radius_y, lower_z, mesh_tol}; 61 | Point(p+9) = {radius_x, 0, lower_z, mesh_tol}; 62 | Point(p+10) = {0, -radius_y, lower_z, mesh_tol}; 63 | 64 | // determine a point on the major axis 65 | If (radius_x > radius_y) 66 | major_point = p+7; 67 | Else 68 | major_point = p+8; 69 | EndIf 70 | 71 | Ellipse(l+5) = {p+7, p+6, major_point, p+8}; 72 | Ellipse(l+6) = {p+8, p+6, major_point, p+9}; 73 | Ellipse(l+7) = {p+9, p+6, major_point, p+10}; 74 | Ellipse(l+8) = {p+10, p+6, major_point, p+7}; 75 | 76 | 77 | // The top full width circle 78 | upper_z = height*0.5-rounding; 79 | 80 | Point(p+11) = {0, 0, upper_z, mesh_tol}; 81 | Point(p+12) = {-radius_x, 0, upper_z, mesh_tol}; 82 | Point(p+13) = {0, radius_y, upper_z, mesh_tol}; 83 | Point(p+14) = {radius_x, 0, upper_z, mesh_tol}; 84 | Point(p+15) = {0, -radius_y, upper_z, mesh_tol}; 85 | 86 | // determine a point on the major axis 87 | If (radius_x > radius_y) 88 | major_point = p+12; 89 | Else 90 | major_point = p+13; 91 | EndIf 92 | 93 | Ellipse(l+9) = {p+12, p+11, major_point, p+13}; 94 | Ellipse(l+10) = {p+13, p+11, major_point, p+14}; 95 | Ellipse(l+11) = {p+14, p+11, major_point, p+15}; 96 | Ellipse(l+12) = {p+15, p+11, major_point, p+12}; 97 | 98 | 99 | // The top face 100 | top_z = height*0.5; 101 | 102 | Point(p+16) = {0, 0, top_z, mesh_tol}; 103 | Point(p+17) = {-end_radius_x, 0, top_z, mesh_tol}; 104 | Point(p+18) = {0, end_radius_y, top_z, mesh_tol}; 105 | Point(p+19) = {end_radius_x, 0, top_z, mesh_tol}; 106 | Point(p+20) = {0, -end_radius_y, top_z, mesh_tol}; 107 | 108 | // determine a point on the major axis 109 | If (radius_x > radius_y) 110 | major_point = p+17; 111 | Else 112 | major_point = p+18; 113 | EndIf 114 | 115 | Ellipse(l+13) = {p+17, p+16, major_point, p+18}; 116 | Ellipse(l+14) = {p+18, p+16, major_point, p+19}; 117 | Ellipse(l+15) = {p+19, p+16, major_point, p+20}; 118 | Ellipse(l+16) = {p+20, p+16, major_point, p+17}; 119 | 120 | 121 | // The bottom rounded sections 122 | 123 | Point(p+21) = {-end_radius_x, 0, lower_z, mesh_tol}; 124 | Point(p+22) = {0, end_radius_y, lower_z, mesh_tol}; 125 | Point(p+23) = {end_radius_x, 0, lower_z, mesh_tol}; 126 | Point(p+24) = {0, -end_radius_y, lower_z, mesh_tol}; 127 | 128 | Circle(l+17) = {p+2, p+21, p+7}; 129 | Circle(l+18) = {p+3, p+22, p+8}; 130 | Circle(l+19) = {p+4, p+23, p+9}; 131 | Circle(l+20) = {p+5, p+24, p+10}; 132 | 133 | // The top rounded sections 134 | 135 | Point(p+25) = {-end_radius_x, 0, upper_z, mesh_tol}; 136 | Point(p+26) = {0, end_radius_y, upper_z, mesh_tol}; 137 | Point(p+27) = {end_radius_x, 0, upper_z, mesh_tol}; 138 | Point(p+28) = {0, -end_radius_y, upper_z, mesh_tol}; 139 | 140 | Circle(l+21) = {12, 25, 17}; 141 | Circle(l+22) = {13, 26, 18}; 142 | Circle(l+23) = {14, 27, 19}; 143 | Circle(l+24) = {15, 28, 20}; 144 | 145 | // The sides 146 | 147 | Line(l+25) = {p+7, p+12}; 148 | Line(l+26) = {p+8, p+13}; 149 | Line(l+27) = {p+9, p+14}; 150 | Line(l+28) = {p+10, p+15}; 151 | 152 | // Form the surfaces 153 | s = news-1; 154 | 155 | // top and bottom 156 | Line Loop(l+100) = {l+1, l+2, l+3, l+4}; 157 | Plane Surface(s+1) = {l+100}; 158 | Line Loop(l+101) = {l+13, l+14, l+15, l+16}; 159 | Plane Surface(s+2) = {-(l+101)}; 160 | 161 | // sides 162 | Line Loop(l+102) = {l+5, -(l+25), -(l+9), (l+26)}; 163 | Ruled Surface(s+3) = {-(l+102)}; 164 | Line Loop(l+103) = {l+6, -(l+26), -(l+10), (l+27)}; 165 | Ruled Surface(s+4) = {-(l+103)}; 166 | Line Loop(l+104) = {l+7, -(l+27), -(l+11), (l+28)}; 167 | Ruled Surface(s+5) = {-(l+104)}; 168 | Line Loop(l+105) = {l+8, -(l+28), -(l+12), (l+25)}; 169 | Ruled Surface(s+6) = {-(l+105)}; 170 | 171 | // bottom rounding 172 | Line Loop(l+106) = {l+1, -(l+17), -(l+5), (l+18)}; 173 | Ruled Surface(s+7) = {-(l+106)}; 174 | Line Loop(l+107) = {l+2, -(l+18), -(l+6), (l+19)}; 175 | Ruled Surface(s+8) = {-(l+107)}; 176 | Line Loop(l+108) = {l+3, -(l+19), -(l+7), (l+20)}; 177 | Ruled Surface(s+9) = {-(l+108)}; 178 | Line Loop(l+109) = {l+4, -(l+20), -(l+8), (l+17)}; 179 | Ruled Surface(s+10) = {-(l+109)}; 180 | 181 | // top rounding 182 | Line Loop(l+110) = {l+9, -(l+21), -(l+13), (l+22)}; 183 | Ruled Surface(s+11) = {-(l+110)}; 184 | Line Loop(l+111) = {l+10, -(l+22), -(l+14), (l+23)}; 185 | Ruled Surface(s+12) = {-(l+111)}; 186 | Line Loop(l+112) = {l+11, -(l+23), -(l+15), (l+24)}; 187 | Ruled Surface(s+13) = {-(l+112)}; 188 | Line Loop(l+113) = {l+12, -(l+24), -(l+16), (l+21)}; 189 | Ruled Surface(s+14) = {-(l+113)}; 190 | 191 | // Close the surface 192 | Surface Loop(s+50) = {s+1, s+2, s+3, s+4, s+5, s+6, s+7, s+8, s+9, s+10, s+11, s+12, s+13, s+14}; 193 | Volume(1) = {s+50}; 194 | -------------------------------------------------------------------------------- /openmodes/geometry/horseshoe_rect.geo: -------------------------------------------------------------------------------- 1 | // a horseshoe without rounded edges 2 | 3 | // geometric parameters specifiable from the command line 4 | If (!Exists(width)) 5 | width = 12e-3; 6 | EndIf 7 | 8 | If (!Exists(length)) 9 | length = 12e-3; 10 | EndIf 11 | 12 | If (!Exists(height)) 13 | height = 3e-3; 14 | EndIf 15 | 16 | If (!Exists(track)) 17 | track = 3e-3; 18 | EndIf 19 | 20 | If (!Exists(mesh_tol)) 21 | mesh_tol = 5e-3; 22 | EndIf 23 | 24 | p = newp-1; 25 | 26 | // define all points on the bottom face 27 | Point(p+1) = {-0.5*width, -0.5*length, 0, mesh_tol}; 28 | Point(p+2) = {-0.5*width, 0.5*length, 0, mesh_tol}; 29 | Point(p+3) = { 0.5*width, 0.5*length, 0, mesh_tol}; 30 | Point(p+4) = { 0.5*width, -0.5*length, 0, mesh_tol}; 31 | Point(p+5) = { 0.5*width-track, -0.5*length, 0, mesh_tol}; 32 | Point(p+6) = { 0.5*width-track, 0.5*length-track, 0, mesh_tol}; 33 | Point(p+7) = { -0.5*width+track, 0.5*length-track, 0, mesh_tol}; 34 | Point(p+8) = { -0.5*width+track, -0.5*length, 0, mesh_tol}; 35 | 36 | l = newl-1; 37 | 38 | // then the lines making the face 39 | Line(l+1) = {p+1, p+2}; 40 | Line(l+2) = {p+2, p+3}; 41 | Line(l+3) = {p+3, p+4}; 42 | Line(l+4) = {p+4, p+5}; 43 | Line(l+5) = {p+5, p+6}; 44 | Line(l+6) = {p+6, p+7}; 45 | Line(l+7) = {p+7, p+8}; 46 | Line(l+8) = {p+8, p+1}; 47 | 48 | Line Loop(l+9) = {l+1, l+2, l+3, l+4, l+5, l+6, l+7, l+8}; 49 | 50 | s = news; 51 | 52 | // gmsh extrusian has problems with surface normals, which are fixed 53 | // by using the following sequence of commands 54 | Plane Surface(s) = {-(l+9)}; 55 | out[] = Extrude{0,0,height}{ Surface{s}; }; 56 | Reverse Surface{s}; 57 | 58 | -------------------------------------------------------------------------------- /openmodes/geometry/isosceles.geo: -------------------------------------------------------------------------------- 1 | // an isosceles triangle 2 | 3 | If (!Exists(width)) 4 | width = 1e-3; 5 | EndIf 6 | 7 | If (!Exists(height)) 8 | height = 10e-3; 9 | EndIf 10 | 11 | lc = 5e-3; 12 | 13 | p = newp-1; 14 | 15 | // Define the dipole 16 | // define all points on the face 17 | Point(p+1) = {-0.5*width, 0, 0, lc}; 18 | Point(p+2) = { 0.5*width, 0, 0, lc}; 19 | Point(p+3) = { 0, height, 0, lc}; 20 | 21 | l = newl-1; 22 | 23 | // then the lines making the face 24 | Line(l+1) = {p+1, p+2}; 25 | Line(l+2) = {p+2, p+3}; 26 | Line(l+3) = {p+3, p+1}; 27 | 28 | Line Loop(l+5) = {l+1, l+2, l+3}; 29 | 30 | s = news; 31 | 32 | Plane Surface(s) = {l+5}; 33 | -------------------------------------------------------------------------------- /openmodes/geometry/rectangle.geo: -------------------------------------------------------------------------------- 1 | // a flat rectangle 2 | 3 | If (!Exists(width)) 4 | width = 1e-3; 5 | EndIf 6 | 7 | If (!Exists(height)) 8 | height = 10e-3; 9 | EndIf 10 | 11 | If (!Exists(mesh_tol)) 12 | mesh_tol = 3e-3; 13 | EndIf 14 | 15 | p = newp-1; 16 | 17 | // Define the dipole 18 | // define all points on the face 19 | Point(p+1) = {-0.5*width, -0.5*height, 0, mesh_tol}; 20 | Point(p+2) = { 0.5*width, -0.5*height, 0, mesh_tol}; 21 | Point(p+3) = { 0.5*width, 0.5*height, 0, mesh_tol}; 22 | Point(p+4) = {-0.5*width, 0.5*height, 0, mesh_tol}; 23 | 24 | l = newl-1; 25 | 26 | // then the lines making the face 27 | Line(l+1) = {p+1, p+2}; 28 | Line(l+2) = {p+2, p+3}; 29 | Line(l+3) = {p+3, p+4}; 30 | Line(l+4) = {p+4, p+1}; 31 | 32 | Line Loop(l+5) = {l+1, l+2, l+3, l+4}; 33 | 34 | s = news; 35 | 36 | Plane Surface(s) = {l+5}; 37 | -------------------------------------------------------------------------------- /openmodes/geometry/single-bent.geo: -------------------------------------------------------------------------------- 1 | // a single pair of bent triangles, representing one RWG basis function 2 | 3 | width = 1e-3; 4 | height = 2e-3; 5 | lift = 0.5e-3; 6 | 7 | lc = 2e-3; 8 | 9 | p = newp-1; 10 | 11 | // define all points on the face 12 | Point(p+1) = {-0.5*width, 0, 0, lc}; 13 | Point(p+2) = { 0, -0.5*height, lift, lc}; 14 | Point(p+3) = { 0.5*width, 0, 0, lc}; 15 | Point(p+4) = {0, 0.5*height, 0, lc}; 16 | 17 | l = newl-1; 18 | 19 | // then the lines making the face 20 | Line(l+1) = {p+1, p+2}; 21 | Line(l+2) = {p+2, p+3}; 22 | Line(l+3) = {p+3, p+4}; 23 | Line(l+4) = {p+4, p+1}; 24 | 25 | Line Loop(l+5) = {l+1, l+2, l+3, l+4}; 26 | 27 | s = news; 28 | 29 | Plane Surface(s) = {l+5}; 30 | -------------------------------------------------------------------------------- /openmodes/geometry/single.geo: -------------------------------------------------------------------------------- 1 | // a single pair of co-planar triangles, representing one RWG basis function 2 | 3 | width = 1e-3; 4 | height = 2e-3; 5 | 6 | lc = 2e-3; 7 | 8 | p = newp-1; 9 | 10 | // define all points on the face 11 | Point(p+1) = {-0.5*width, 0, 0, lc}; 12 | Point(p+2) = { 0, -0.5*height, 0, lc}; 13 | Point(p+3) = { 0.5*width, 0, 0, lc}; 14 | Point(p+4) = {0, 0.5*height, 0, lc}; 15 | 16 | l = newl-1; 17 | 18 | // then the lines making the face 19 | Line(l+1) = {p+1, p+2}; 20 | Line(l+2) = {p+2, p+3}; 21 | Line(l+3) = {p+3, p+4}; 22 | Line(l+4) = {p+4, p+1}; 23 | 24 | Line Loop(l+5) = {l+1, l+2, l+3, l+4}; 25 | 26 | s = news; 27 | 28 | Plane Surface(s) = {l+5}; 29 | -------------------------------------------------------------------------------- /openmodes/geometry/solid_L.geo: -------------------------------------------------------------------------------- 1 | // A solid L-shape extruded to a certain thickness 2 | 3 | // geometric parameters specifiable from the command line 4 | If (!Exists(hor_len)) 5 | hor_len = 12e-3; 6 | EndIf 7 | 8 | If (!Exists(hor_width)) 9 | hor_width = 4e-3; 10 | EndIf 11 | 12 | If (!Exists(vert_len)) 13 | vert_len = 12e-3; 14 | EndIf 15 | 16 | If (!Exists(vert_width)) 17 | vert_width = 6e-3; 18 | EndIf 19 | 20 | If (!Exists(thickness)) 21 | thickness = 3e-3; 22 | EndIf 23 | 24 | If (!Exists(mesh_tol)) 25 | mesh_tol = 3e-3; 26 | EndIf 27 | 28 | p = newp-1; 29 | 30 | // define all points on the bottom face 31 | Point(p+1) = {0, 0, 0, mesh_tol}; 32 | Point(p+2) = {0, vert_len, 0, mesh_tol}; 33 | Point(p+3) = {vert_width, vert_len, 0, mesh_tol}; 34 | Point(p+4) = {vert_width, hor_width, 0, mesh_tol}; 35 | Point(p+5) = {hor_len, hor_width, 0, mesh_tol}; 36 | Point(p+6) = {hor_len, 0, 0, mesh_tol}; 37 | 38 | l = newl-1; 39 | 40 | // then the lines making the face 41 | Line(l+1) = {p+1, p+2}; 42 | Line(l+2) = {p+2, p+3}; 43 | Line(l+3) = {p+3, p+4}; 44 | Line(l+4) = {p+4, p+5}; 45 | Line(l+5) = {p+5, p+6}; 46 | Line(l+6) = {p+6, p+1}; 47 | 48 | ll = newll; 49 | 50 | Line Loop(ll) = {l+1, l+2, l+3, l+4, l+5, l+6}; 51 | 52 | s = news; 53 | 54 | // gmsh extrusian has problems with surface normals, which are fixed 55 | // by using the following sequence of commands 56 | Plane Surface(s) = {-(ll)}; 57 | out[] = Extrude{0,0,thickness}{ Surface{s}; }; 58 | Reverse Surface{s}; 59 | 60 | -------------------------------------------------------------------------------- /openmodes/geometry/sphere.geo: -------------------------------------------------------------------------------- 1 | // A sphere 2 | 3 | // Allow radius to be specified on the command line 4 | If (!Exists(radius)) 5 | radius = 10e-3; 6 | EndIf 7 | 8 | // base element size on radius 9 | If (!Exists(mesh_tol)) 10 | mesh_tol = radius*0.2; 11 | EndIf 12 | 13 | Point(1) = {0.0,0.0,0.0,mesh_tol}; 14 | Point(2) = {radius,0.0,0.0,mesh_tol}; 15 | Point(3) = {0,radius,0.0,mesh_tol}; 16 | Point(4) = {-radius,0,0.0,mesh_tol}; 17 | Point(5) = {0,-radius,0.0,mesh_tol}; 18 | Point(6) = {0,0,-radius,mesh_tol}; 19 | Point(7) = {0,0,radius,mesh_tol}; 20 | 21 | Circle(1) = {2,1,3}; 22 | Circle(2) = {3,1,4}; 23 | Circle(3) = {4,1,5}; 24 | Circle(4) = {5,1,2}; 25 | Circle(5) = {3,1,6}; 26 | Circle(6) = {6,1,5}; 27 | Circle(7) = {5,1,7}; 28 | Circle(8) = {7,1,3}; 29 | Circle(9) = {2,1,7}; 30 | Circle(10) = {7,1,4}; 31 | Circle(11) = {4,1,6}; 32 | Circle(12) = {6,1,2}; 33 | 34 | Line Loop(13) = {2,8,-10}; 35 | Ruled Surface(14) = {13}; 36 | Line Loop(15) = {10,3,7}; 37 | Ruled Surface(16) = {15}; 38 | Line Loop(17) = {-8,-9,1}; 39 | Ruled Surface(18) = {17}; 40 | Line Loop(19) = {-11,-2,5}; 41 | Ruled Surface(20) = {19}; 42 | Line Loop(21) = {-5,-12,-1}; 43 | Ruled Surface(22) = {21}; 44 | Line Loop(23) = {-3,11,6}; 45 | Ruled Surface(24) = {23}; 46 | Line Loop(25) = {-7,4,9}; 47 | Ruled Surface(26) = {25}; 48 | Line Loop(27) = {-4,12,-6}; 49 | Ruled Surface(28) = {27}; 50 | Surface Loop(29) = {28,26,16,14,20,24,22,18}; 51 | 52 | Volume(30) = {29}; 53 | 54 | -------------------------------------------------------------------------------- /openmodes/geometry/torus.geo: -------------------------------------------------------------------------------- 1 | // A torus 2 | 3 | // Allow radius to be specified on the command line 4 | If (!Exists(major_radius)) 5 | major_radius = 10e-3; 6 | EndIf 7 | 8 | If (!Exists(minor_radius)) 9 | minor_radius = 3e-3; 10 | EndIf 11 | 12 | // base element size on radius 13 | lc = minor_radius; 14 | 15 | Point(1) = {major_radius,0.0,0.0,lc}; 16 | Point(2) = {major_radius+minor_radius, 0.0, 0.0, lc}; 17 | Point(3) = {major_radius, 0.0, minor_radius, lc}; 18 | Point(4) = {major_radius-minor_radius, 0.0, 0.0, lc}; 19 | Point(5) = {major_radius, 0.0, -minor_radius, lc}; 20 | 21 | Circle(1) = {2, 1, 3}; 22 | Circle(2) = {3, 1, 4}; 23 | Circle(3) = {4, 1, 5}; 24 | Circle(4) = {5, 1, 2}; 25 | Line Loop(5) = {1, 2, 3, 4}; 26 | Plane Surface(1) = {5}; 27 | 28 | Extrude {{0, 0, 1}, {0, 0, 0}, 2*Pi/3} { 29 | Surface{1}; 30 | } 31 | 32 | Extrude {{0, 0, 1}, {0, 0, 0}, 2*Pi/3} { 33 | Surface{27}; 34 | } 35 | 36 | Extrude {{0, 0, 1}, {0, 0, 0}, 2*Pi/3} { 37 | Surface{49}; 38 | } 39 | 40 | Compound Volume(4) = {1, 2, 3}; 41 | 42 | // delete elements which aren't needed to prevent 43 | // meshing of internal surfaces 44 | Delete{ Volume{1, 2, 3};} 45 | Delete{ Surface{1, 27, 49};} 46 | -------------------------------------------------------------------------------- /openmodes/geometry/v_antenna.geo: -------------------------------------------------------------------------------- 1 | // Flat V-shaped antenna, based on design of Capasso, with all ends rounded 2 | 3 | // allow the geometric parameters to be specified on the command-line 4 | 5 | // the width of each arm 6 | If (!Exists(width)) 7 | width = 5e-3; 8 | EndIf 9 | 10 | // the length of each arm 11 | If (!Exists(length)) 12 | length = 20.0e-3; 13 | EndIf 14 | 15 | // The angular span between the arm centres in degrees 16 | If (!Exists(span)) 17 | span = 45.0; 18 | EndIf 19 | 20 | // The orientation of the centre of the V relative to the x-axis is "orientation" 21 | If (!Exists(orientation)) 22 | orientation = 0.0; 23 | EndIf 24 | 25 | // If span is greater than 180 degrees, modify both span and 26 | // orientation so that span is less than 180 27 | If (span > 180.0) 28 | span = 360.0 - span; 29 | orientation = orientation + 180.0; 30 | EndIf 31 | 32 | // the angle parallel to each arm 33 | first_parallel = (orientation - span*0.5)/180.0*Pi; 34 | second_parallel = (orientation + span*0.5)/180.0*Pi; 35 | 36 | // the half width of strips 37 | hw = 0.5*width; 38 | 39 | // reduced length of the unrounded parts of strips 40 | rl = length-width; 41 | 42 | // the point where the two edges touch inside the V 43 | //touch = 2*hw/Tan(span/180.0*Pi); 44 | touch = hw/Sin(0.5*span/180.0*Pi); 45 | 46 | lc = 2e-3; 47 | 48 | p = newp-1; 49 | Point(p+1) = {0, 0, 0, lc}; 50 | Point(p+2) = {hw*Sin(first_parallel), -hw*Cos(first_parallel), 0.0, lc}; 51 | Point(p+3) = {hw*Sin(first_parallel)+rl*Cos(first_parallel), -hw*Cos(first_parallel)+rl*Sin(first_parallel), 0.0, lc}; 52 | Point(p+4) = {rl*Cos(first_parallel), rl*Sin(first_parallel), 0.0, lc}; 53 | Point(p+5) = {(rl+hw)*Cos(first_parallel), (rl+hw)*Sin(first_parallel), 0.0, lc}; 54 | Point(p+6) = {-hw*Sin(first_parallel)+rl*Cos(first_parallel), hw*Cos(first_parallel)+rl*Sin(first_parallel), 0.0, lc}; 55 | Point(p+7) = {touch*Cos(orientation/180.0*Pi), touch*Sin(orientation/180.0*Pi), 0, lc}; 56 | Point(p+8) = {hw*Sin(second_parallel)+rl*Cos(second_parallel), -hw*Cos(second_parallel)+rl*Sin(second_parallel), 0.0, lc}; 57 | Point(p+9) = {rl*Cos(second_parallel), rl*Sin(second_parallel), 0.0, lc}; 58 | Point(p+10) = {(rl+hw)*Cos(second_parallel), (rl+hw)*Sin(second_parallel), 0.0, lc}; 59 | Point(p+11) = {-hw*Sin(second_parallel)+rl*Cos(second_parallel), hw*Cos(second_parallel)+rl*Sin(second_parallel), 0.0, lc}; 60 | Point(p+12) = {-hw*Sin(second_parallel), hw*Cos(second_parallel), 0.0, lc}; 61 | 62 | 63 | l = newl-1; 64 | Line(l+1) = {p+2, p+3}; 65 | Circle(l+2) = {p+3, p+4, p+5}; 66 | Circle(l+3) = {p+5, p+4, p+6}; 67 | Line(l+4) = {p+6, p+7}; 68 | Line(l+5) = {p+7, p+8}; 69 | Circle(l+6) = {p+8, p+9, p+10}; 70 | Circle(l+7) = {p+10, p+9, p+11}; 71 | Line(l+8) = {p+11, p+12}; 72 | Circle(l+9) = {p+12, p+1, p+2}; 73 | Compound Line(l+10) = {l+1, l+2, l+3, l+4, l+5, l+6, l+7, l+8, l+9}; 74 | 75 | 76 | ll = newll; 77 | //Line Loop(ll) = {l+1, l+2, l+3, l+4, l+5, l+6, l+7, l+8, l+9}; 78 | //Line Loop(ll) = {l+2, l+3, l+4, l+5, l+6, l+7, -(l+10)}; 79 | Line Loop(ll) = {l+10}; 80 | 81 | s = news; 82 | Plane Surface(s) = {ll}; 83 | Physical Surface(s+1) = {s}; 84 | 85 | -------------------------------------------------------------------------------- /openmodes/helpers.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | #----------------------------------------------------------------------------- 3 | # OpenModes - An eigenmode solver for open electromagnetic resonantors 4 | # Copyright (C) 2013 David Powell 5 | # 6 | # This program is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation, either version 3 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # This program is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with this program. If not, see . 18 | #----------------------------------------------------------------------------- 19 | 20 | import functools 21 | import uuid 22 | import weakref 23 | import numpy as np 24 | import numbers 25 | from collections import defaultdict 26 | import six 27 | 28 | 29 | def inc_slice(s, inc): 30 | """Increment a slice so that it starts at the current stop, and the current 31 | stop is incremented by some amount""" 32 | return slice(s.stop, s.stop+inc) 33 | 34 | 35 | class cached_property(object): 36 | """ 37 | A property that is only computed once per instance and then replaces itself 38 | with an ordinary attribute. Deleting the attribute resets the property. 39 | 40 | Taken from https://github.com/pydanny/cached-property/blob/master/cached_property.py 41 | Original source: https://github.com/bottlepy/bottle/commit/fa7733e075da0d790d809aa3d2f53071897e6f76 42 | Copyright under MIT License 43 | """ 44 | 45 | def __init__(self, func): 46 | self.__doc__ = getattr(func, '__doc__') 47 | self.func = func 48 | 49 | def __get__(self, obj, cls): 50 | if obj is None: 51 | return self 52 | value = obj.__dict__[self.func.__name__] = self.func(obj) 53 | return value 54 | 55 | 56 | class MeshError(Exception): 57 | "An exeception indicating a failure generating or reading the mesh" 58 | pass 59 | 60 | 61 | class Identified(object): 62 | """An object which can be uniquely identified by an id number. It is 63 | assumed that any object which subclasses Identified is immutable, so that 64 | its id can be used for caching complex results which depend on this object. 65 | """ 66 | 67 | def __init__(self): 68 | self.id = uuid.uuid4() 69 | 70 | def __hash__(self): 71 | return self.id.__hash__() 72 | 73 | def __eq__(self, other): 74 | return hasattr(other, 'id') and (self.id == other.id) 75 | 76 | def __repr__(self): 77 | "Represent the object by its id, in addition to its memory address" 78 | return ("<%s at 0x%08x with id %s>" % (str(self.__class__)[8:-2], 79 | id(self), 80 | str(self.id))) 81 | 82 | 83 | class PicklableRef(object): 84 | """A weak reference which can be pickled. This is achieved by 85 | creating a strong reference to the object at pickling time, then restoring 86 | the weak reference when unpickling. Note that unless the object being 87 | referenced is also pickled and referenced after unpickling, the weak 88 | reference will be dead after unpickling. 89 | """ 90 | 91 | def __init__(self, obj, callback=None): 92 | self.ref = weakref.ref(obj, callback) 93 | 94 | def __call__(self): 95 | return self.ref() 96 | 97 | def __getstate__(self): 98 | return {'ref': self.ref()} 99 | 100 | def __setstate__(self, state): 101 | self.ref = weakref.ref(state['ref']) 102 | 103 | 104 | def memoize(obj): 105 | """A simple decorator to memoize function calls. Pays particular attention 106 | to numpy arrays and objects which are subclasses of Identified. It is 107 | assumed that in such cases, the object does not change if its `id` is the 108 | same""" 109 | cache = obj.cache = {} 110 | 111 | def get_key(item): 112 | if isinstance(item, (six.string_types, numbers.Number)): 113 | return item 114 | elif isinstance(item, Identified): 115 | return str(item.id) 116 | elif isinstance(item, np.ndarray): 117 | return item.tostring() 118 | else: 119 | return str(item) 120 | 121 | @functools.wraps(obj) 122 | def memoizer(*args, **kwargs): 123 | key_arg = tuple(get_key(arg) for arg in args) 124 | key_kwarg = tuple((kw, get_key(arg)) for (kw, arg) 125 | in kwargs.items()) 126 | key = (key_arg, key_kwarg) 127 | 128 | if key not in cache: 129 | cache[key] = obj(*args, **kwargs) 130 | return cache[key] 131 | return memoizer 132 | 133 | 134 | def equivalence(relations): 135 | """Determine the equivalence classes between objects 136 | 137 | Following numerical recipes section 8.6 138 | 139 | Parameters 140 | ---------- 141 | relations: list 142 | Each element of the list is a tuple containing the identities of two 143 | equivalent items. Each item can be any hashable type 144 | 145 | Returns 146 | ------- 147 | class_items: list of set 148 | Each set 149 | """ 150 | 151 | # first put each item in its own equivalence class 152 | classes = {} 153 | for j, k in relations: 154 | classes[j] = j 155 | classes[k] = k 156 | 157 | for relation in relations: 158 | j, k = relation 159 | 160 | # track the anscestor of each 161 | while classes[j] != j: 162 | j = classes[j] 163 | 164 | while classes[k] != k: 165 | k = classes[k] 166 | 167 | # if not already related, then relate items 168 | if j != k: 169 | classes[j] = k 170 | 171 | # The final sweep 172 | for j in classes.keys(): 173 | while classes[j] != classes[classes[j]]: 174 | classes[j] = classes[classes[j]] 175 | 176 | # Now reverse the storage arrangement, so that all items of the same 177 | # class are grouped together into a set 178 | classes_reverse = defaultdict(set) 179 | for item, item_class in classes.items(): 180 | classes_reverse[item_class].add(item) 181 | 182 | # the class names are arbitrary, so just return the list of sets 183 | return list(classes_reverse.values()) 184 | 185 | 186 | def wrap_if_constant(func): 187 | """If passed a constant, wrap it in a function. If passed a function, just 188 | return it as is""" 189 | if hasattr(func, '__call__'): 190 | return func 191 | else: 192 | return lambda x: func 193 | -------------------------------------------------------------------------------- /openmodes/material.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | #----------------------------------------------------------------------------- 3 | # OpenModes - An eigenmode solver for open electromagnetic resonantors 4 | # Copyright (C) 2013 David Powell 5 | # 6 | # This program is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation, either version 3 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # This program is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with this program. If not, see . 18 | #----------------------------------------------------------------------------- 19 | """ 20 | Routines to describe materials 21 | """ 22 | from __future__ import division 23 | 24 | import numpy as np 25 | 26 | from openmodes.helpers import Identified, wrap_if_constant 27 | from openmodes.constants import eta_0 28 | 29 | 30 | class IsotropicMaterial(Identified): 31 | "An isotropic material with a given permittivity and permeability" 32 | def __init__(self, name, epsilon_r, mu_r): 33 | super(IsotropicMaterial, self).__init__() 34 | self.name = name 35 | self.epsilon_r = wrap_if_constant(epsilon_r) 36 | self.mu_r = wrap_if_constant(mu_r) 37 | 38 | def eta(self, s): 39 | "Absolute impedance of the material" 40 | return eta_0*self.eta_r(s) 41 | 42 | def eta_r(self, s): 43 | "Impedance of the material relative to free space" 44 | return np.sqrt(self.mu_r(s)/self.epsilon_r(s)) 45 | 46 | def n(self, s): 47 | "Refractive index of the material" 48 | return np.sqrt(self.mu_r(s)*self.epsilon_r(s)) 49 | 50 | # a constant for free space 51 | FreeSpace = IsotropicMaterial("Free space", 1.0, 1.0) 52 | 53 | # a constant for a perfect electric conductor 54 | PecMaterial = IsotropicMaterial("Perfect electric conductor", -np.inf, -np.inf) 55 | -------------------------------------------------------------------------------- /openmodes/mesh/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | #----------------------------------------------------------------------------- 3 | # OpenModes - An eigenmode solver for open electromagnetic resonantors 4 | # Copyright (C) 2013 David Powell 5 | # 6 | # This program is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation, either version 3 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # This program is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with this program. If not, see . 18 | #----------------------------------------------------------------------------- 19 | """ 20 | Operator classes 21 | """ 22 | 23 | from .mesh import TriangularSurfaceMesh, nodes_not_in_edge, shared_nodes, combine_mesh 24 | -------------------------------------------------------------------------------- /openmodes/mesh/freecad.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # ----------------------------------------------------------------------------- 3 | # OpenModes - An eigenmode solver for open electromagnetic resonantors 4 | # Copyright (C) 2013-2015 David Powell 5 | # 6 | # This program is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation, either version 3 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # This program is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with this program. If not, see . 18 | # ----------------------------------------------------------------------------- 19 | "Create meshes using FreeCAD" 20 | 21 | from __future__ import division 22 | 23 | import numpy as np 24 | 25 | import FreeCAD 26 | import Part 27 | import MeshPart 28 | 29 | 30 | def freecad_mesh(geometry): 31 | mesh = MeshPart.meshFromShape(geometry, Fineness=2, SecondOrder=0, 32 | Optimize=1, AllowQuad=0) 33 | 34 | result = {} 35 | 36 | # Point IDs might be in arbitrary order, so create a lookup to be sure 37 | # that we have the correct point ids 38 | point_ids = {} 39 | points = [] 40 | for point_count, point in enumerate(mesh.Points): 41 | points.append([point.x, point.y, point.z]) 42 | point_ids[point.Index] = point_count 43 | 44 | result['nodes'] = np.array(points, dtype=np.float64) 45 | 46 | triangles = [] 47 | for facet_count, facet in enumerate(mesh.Facets): 48 | if len(facet.PointIndices) != 3: 49 | raise NotImplementedError("Only triangles currently supported") 50 | triangles.append([point_ids[n] for n in facet.PointIndices]) 51 | 52 | result['triangles'] = np.array(triangles) 53 | 54 | return result 55 | 56 | 57 | def box(x, y, z, rounding=None): 58 | """Create a box, optionally rounding the edges 59 | 60 | Parameters 61 | ---------- 62 | x, y, z: float 63 | Size of the box in 3 dimensions 64 | rounding: float, optional 65 | If specified, the edges will be rounded with this radius 66 | """ 67 | box = Part.makeBox(x, y, z) 68 | 69 | centre = FreeCAD.Vector(-0.5*x, -0.5*y, -0.5*z) 70 | box.Placement.Base = centre 71 | 72 | if rounding is not None: 73 | box = box.makeFillet(rounding, box.Edges) 74 | return box 75 | 76 | 77 | #bar = box(500, 150, 200, 10) 78 | #mesh = freecad_mesh(bar) 79 | 80 | #MeshPart.meshFromShape(box,GrowthRate=0.3,SegPerEdge=1,SegPerRadius=2,SecondOrder=0,Optimize=1,AllowQuad=0) -------------------------------------------------------------------------------- /openmodes/model.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | #----------------------------------------------------------------------------- 3 | # OpenModes - An eigenmode solver for open electromagnetic resonantors 4 | # Copyright (C) 2013 David Powell 5 | # 6 | # This program is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation, either version 3 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # This program is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with this program. If not, see . 18 | #----------------------------------------------------------------------------- 19 | "Fit scalar models to numerically calculated impedance data" 20 | 21 | from __future__ import division 22 | import numpy as np 23 | from openmodes.impedance import ImpedanceMatrixLA, EfieImpedanceMatrixLA 24 | from openmodes.modes import SplitModes 25 | 26 | 27 | class ModelMutualWeight(object): 28 | """A model where mutual terms come from directly weighting the mutual 29 | terms of the full impedance matrix""" 30 | 31 | def __init__(self, modes): 32 | """ 33 | Parameters 34 | ---------- 35 | modes : Modes object 36 | The modes object from which to create 37 | """ 38 | self.modes = modes 39 | self.parts = modes.parts 40 | self.parent_part = modes.parent_part 41 | self.macro_container = modes.macro_container 42 | self.symmetric = modes.operator.reciprocal 43 | self.impedance_class = ImpedanceMatrixLA 44 | self.vl = self.modes.vl 45 | self.vr = self.modes.vr 46 | 47 | def impedance_self(self, s, part_o, Z_full): 48 | "Self impedance of one part" 49 | s_o = self.modes.s[0, part_o] 50 | Z_full.matrices['Z'][part_o, part_o] = np.diag(s_o*(s-s_o)/s) 51 | # TODO: impedance derivative 52 | 53 | def impedance_mutual(self, s, part_o, part_s, Z_full): 54 | "Impedance between two parts, by weighting matrix" 55 | vl = self.vl[:, part_o, :, part_o] 56 | vr = self.vr[:, part_s, :, part_s] 57 | z_weighted = self.modes.operator.impedance(s, part_o, part_s).weight(vr, vl) 58 | 59 | # If the model has the same impedance class as the full matrix, 60 | # then store all sub-matrices. Otherwise just get the combined value. 61 | try: 62 | Z_full[part_o, part_s] = z_weighted 63 | except KeyError: 64 | Z_full.matrices['Z'][part_o, part_s] = z_weighted.val().simple_view() 65 | 66 | def impedance(self, s): 67 | """Impedance matrix 68 | 69 | Parameters 70 | ---------- 71 | s : complex 72 | Frequency at which to calculate impedance 73 | """ 74 | Z = self.impedance_class(self.parent_part, self.parent_part, 75 | self.macro_container, ('modes',), ('modes',)) 76 | 77 | Z.md['s'] = s 78 | Z.md['symmetric'] = self.symmetric 79 | Z.md['operator'] = self.modes.operator 80 | 81 | for count_o, part_o in enumerate(self.parts): 82 | for count_s, part_s in enumerate(self.parts): 83 | if count_o == count_s: 84 | self.impedance_self(s, part_o, Z) 85 | elif self.symmetric and (count_o > count_s): 86 | # account for symmetry of operator 87 | Z[part_o, part_s] = Z[part_s, part_o].T 88 | else: 89 | self.impedance_mutual(s, part_o, part_s, Z) 90 | return Z 91 | 92 | 93 | class EfieModelMutualWeight(ModelMutualWeight): 94 | """A model where mutual terms come from directly weighting the mutual 95 | terms of the full impedance matrix""" 96 | 97 | def __init__(self, modes): 98 | ModelMutualWeight.__init__(self, modes) 99 | self.impedance_class = EfieImpedanceMatrixLA 100 | 101 | def impedance_self(self, s, part_o, Z_full): 102 | "Self impedance of one part" 103 | s_o = self.modes.s[0, part_o] 104 | Z_full.matrices['S'][part_o, part_o] = np.diag(s_o*(s-s_o)) 105 | Z_full.matrices['L'][part_o, part_o] = 0.0 106 | # TODO: derivatives 107 | 108 | 109 | class ModelSplit(ModelMutualWeight): 110 | "A model of modes which have been split into real and imaginary parts" 111 | def __init__(self, modes): 112 | if not isinstance(modes, SplitModes): 113 | modes = modes.split_real_imag() 114 | super(ModelSplit, self).__init__(modes) 115 | 116 | def impedance_self(self, s, part_o, Z_full): 117 | "Self impedance of one part" 118 | s_o = self.modes.s[0, part_o] 119 | num_modes = len(s_o)//2 120 | s_r = s_o[:num_modes] 121 | s_i = s_o[num_modes:] 122 | Z_self = np.diag(s_r + (s_i**2 - s_r**2)/s)*0.5 123 | Z_mutual = np.diag(s_i - 2*s_r*s_i/s)*0.5 124 | Z_full.matrices['Z'][part_o, part_o] = np.vstack((np.hstack((Z_self, Z_mutual)), 125 | np.hstack((Z_mutual, -Z_self)))) 126 | 127 | 128 | class EfieModelSplit(EfieModelMutualWeight): 129 | "A model of modes which have been split into real and imaginary parts" 130 | def __init__(self, modes): 131 | if not isinstance(modes, SplitModes): 132 | modes = modes.split_real_imag() 133 | super(EfieModelSplit, self).__init__(modes) 134 | 135 | def impedance_self(self, s, part_o, Z_full): 136 | "Self impedance of one part" 137 | s_o = self.modes.s[0, part_o] 138 | num_modes = len(s_o)//2 139 | s_r = s_o[:num_modes] 140 | s_i = s_o[num_modes:] 141 | Z_self = np.diag(s_r*s + (s_i**2 - s_r**2))*0.5 142 | Z_mutual = np.diag(s_i*s - 2*s_r*s_i)*0.5 143 | Z_full.matrices['S'][part_o, part_o] = np.vstack((np.hstack((Z_self, Z_mutual)), 144 | np.hstack((Z_mutual, -Z_self)))) 145 | Z_full.matrices['L'][part_o, part_o] = 0.0 146 | -------------------------------------------------------------------------------- /openmodes/operator/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | #----------------------------------------------------------------------------- 3 | # OpenModes - An eigenmode solver for open electromagnetic resonantors 4 | # Copyright (C) 2013 David Powell 5 | # 6 | # This program is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation, either version 3 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # This program is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with this program. If not, see . 18 | #----------------------------------------------------------------------------- 19 | """ 20 | Operator classes 21 | """ 22 | 23 | from .pec import EfieOperator, MfieOperator, CfieOperator 24 | -------------------------------------------------------------------------------- /openmodes/operator/rwg.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | #----------------------------------------------------------------------------- 3 | # OpenModes - An eigenmode solver for open electromagnetic resonantors 4 | # Copyright (C) 2013 David Powell 5 | # 6 | # This program is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation, either version 3 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # This program is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with this program. If not, see . 18 | #----------------------------------------------------------------------------- 19 | """This module contains most of the matrix construction routines which are 20 | fully specific to RWG and related basis functions""" 21 | 22 | import numpy as np 23 | 24 | from openmodes.operator.singularities import singular_impedance_rwg 25 | from openmodes.core import z_mfie_faces_self, z_mfie_faces_mutual 26 | from openmodes.core import z_efie_faces_self, z_efie_faces_mutual 27 | from openmodes.constants import pi, c 28 | 29 | 30 | def impedance_curl_G(s, integration_rule, basis_o, nodes_o, basis_s, nodes_s, 31 | normals, self_impedance, epsilon, mu, num_singular_terms, 32 | singularity_accuracy, tangential_form): 33 | """Calculates the impedance matrix corresponding to the equation: 34 | fm . curl(G) . fn 35 | for RWG and related basis functions""" 36 | 37 | transform_o, _ = basis_o.transformation_matrices 38 | num_faces_o = len(basis_o.mesh.polygons) 39 | 40 | c_mat = c/np.sqrt(epsilon*mu) 41 | gamma_0 = s/c_mat 42 | 43 | if self_impedance: 44 | # calculate self impedance 45 | 46 | singular_terms = singular_impedance_rwg(basis_o, 47 | num_terms=num_singular_terms, 48 | rel_tol=singularity_accuracy, 49 | normals=normals) 50 | 51 | if tangential_form: 52 | singular_terms = singular_terms["T_MFIE"] 53 | else: 54 | singular_terms = singular_terms["N_MFIE"] 55 | 56 | if np.any(np.isnan(singular_terms[0])): 57 | raise ValueError("NaN returned in singular impedance terms") 58 | 59 | num_faces_s = num_faces_o 60 | res = z_mfie_faces_self(nodes_o, basis_o.mesh.polygons, 61 | basis_o.mesh.polygon_areas, gamma_0, 62 | integration_rule.points, 63 | integration_rule.weights, normals, 64 | tangential_form, *singular_terms) 65 | 66 | transform_s = transform_o 67 | 68 | else: 69 | # calculate mutual impedance 70 | num_faces_s = len(basis_s.mesh.polygons) 71 | 72 | res = z_mfie_faces_mutual(nodes_o, basis_o.mesh.polygons, 73 | nodes_s, basis_s.mesh.polygons, 74 | gamma_0, integration_rule.points, 75 | integration_rule.weights, normals, 76 | tangential_form) 77 | 78 | transform_s, _ = basis_s.transformation_matrices 79 | 80 | Z_faces, Z_dgamma_faces = res 81 | 82 | if np.any(np.isnan(Z_faces)): 83 | raise ValueError("NaN returned in impedance matrix") 84 | 85 | if np.any(np.isnan(Z_dgamma_faces)): 86 | raise ValueError("NaN returned in impedance matrix derivative") 87 | 88 | 89 | Z = transform_o.dot(transform_s.dot(Z_faces.reshape(num_faces_o*3, 90 | num_faces_s*3, 91 | order='C').T).T) 92 | 93 | Z_dgamma = transform_o.dot(transform_s.dot(Z_dgamma_faces.reshape(num_faces_o*3, 94 | num_faces_s*3, 95 | order='C').T).T) 96 | 97 | return Z, Z_dgamma/c_mat 98 | 99 | 100 | def impedance_G(s, integration_rule, basis_o, nodes_o, basis_s, nodes_s, 101 | normals, self_impedance, epsilon, mu, num_singular_terms, 102 | singularity_accuracy, frequency_derivatives=False): 103 | """Calculates the impedance matrix corresponding to the equation: 104 | fm . (I + grad grad) G . fn 105 | for RWG or loop-star basis functions 106 | 107 | No factors of epsilon/mu are included, as these can vary depending on 108 | the operator 109 | """ 110 | 111 | transform_L_o, transform_S_o = basis_o.transformation_matrices 112 | num_faces_o = len(basis_o.mesh.polygons) 113 | 114 | c_mat = c/np.sqrt(epsilon*mu) 115 | gamma_0 = s/c_mat 116 | 117 | if (self_impedance): 118 | # calculate self impedance 119 | 120 | singular_terms = singular_impedance_rwg(basis_o, 121 | num_terms=num_singular_terms, 122 | rel_tol=singularity_accuracy, 123 | normals=normals) 124 | singular_terms = singular_terms["T_EFIE"] 125 | if (np.any(np.isnan(singular_terms[0])) or 126 | np.any(np.isnan(singular_terms[1]))): 127 | raise ValueError("NaN returned in singular impedance terms") 128 | 129 | num_faces_s = num_faces_o 130 | res = z_efie_faces_self(nodes_o, basis_o.mesh.polygons, gamma_0, 131 | integration_rule.points, 132 | integration_rule.weights, *singular_terms) 133 | 134 | transform_L_s = transform_L_o 135 | transform_S_s = transform_S_o 136 | 137 | else: 138 | # calculate mutual impedance 139 | 140 | num_faces_s = len(basis_s.mesh.polygons) 141 | 142 | res = z_efie_faces_mutual(nodes_o, basis_o.mesh.polygons, nodes_s, 143 | basis_s.mesh.polygons, gamma_0, 144 | integration_rule.points, 145 | integration_rule.weights) 146 | 147 | transform_L_s, transform_S_s = basis_s.transformation_matrices 148 | 149 | A_faces, phi_faces, A_dgamma_faces, phi_dgamma_faces = res 150 | 151 | if np.any(np.isnan(A_faces)) or np.any(np.isnan(phi_faces)): 152 | raise ValueError("NaN returned in impedance matrix") 153 | 154 | if np.any(np.isnan(A_dgamma_faces)) or np.any(np.isnan(phi_dgamma_faces)): 155 | raise ValueError("NaN returned in impedance matrix derivative") 156 | 157 | L = transform_L_o.dot(transform_L_s.dot(A_faces.reshape(num_faces_o*3, 158 | num_faces_s*3, 159 | order='C').T).T) 160 | S = transform_S_o.dot(transform_S_s.dot(phi_faces.T).T) 161 | 162 | L /= 4*pi 163 | S /= pi 164 | 165 | if not frequency_derivatives: 166 | return L, S 167 | 168 | # transform the frequency derivatives 169 | dL_ds = transform_L_o.dot(transform_L_s.dot(A_dgamma_faces.reshape(num_faces_o*3, 170 | num_faces_s*3, 171 | order='C').T).T) 172 | dS_ds = transform_S_o.dot(transform_S_s.dot(phi_dgamma_faces.T).T) 173 | 174 | dL_ds /= c_mat*4*pi 175 | dS_ds /= c_mat*pi 176 | 177 | return L, S, dL_ds, dS_ds 178 | -------------------------------------------------------------------------------- /openmodes/sources.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | #----------------------------------------------------------------------------- 3 | # OpenModes - An eigenmode solver for open electromagnetic resonantors 4 | # Copyright (C) 2013 David Powell 5 | # 6 | # This program is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation, either version 3 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # This program is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with this program. If not, see . 18 | #----------------------------------------------------------------------------- 19 | 20 | "Classes which represent possible distributions of the incident field" 21 | 22 | import numpy as np 23 | from openmodes.constants import c 24 | from openmodes.material import FreeSpace 25 | from numpy import sin, cos 26 | 27 | 28 | class PlaneWaveSource(object): 29 | def __init__(self, e_inc, k_hat, material=FreeSpace, p_inc=None): 30 | """Generate a plane wave with a given direction of propagation and 31 | magnitude and direction of the electric field 32 | 33 | Parameters 34 | ---------- 35 | e_inc: ndarray[3] 36 | Incident field polarisation in free space 37 | k_hat: ndarray[3], real 38 | Incident wave vector in free space 39 | n: real, optional 40 | Refractive index of the background medium, defaults to free space 41 | eta: real, optional 42 | Characteristic impedance of background medium, defaults to free 43 | space 44 | p_inc: real, optional 45 | If specified, the incident power will be scaled to ensure constant 46 | power. 47 | """ 48 | 49 | self.e_inc = np.asarray(e_inc) 50 | k_hat = np.asarray(k_hat) 51 | self.k_hat = k_hat/np.sqrt(np.dot(k_hat, k_hat)) 52 | self.material = material 53 | self.h_inc = np.cross(k_hat, self.e_inc) # unscaled 54 | self.p_inc = p_inc 55 | 56 | def electric_field(self, s, r): 57 | """Calculate the electric field distribution at a given frequency 58 | 59 | Parameters 60 | ---------- 61 | s : complex 62 | Complex frequency at which to evaluate fields 63 | r : ndarray, real 64 | The locations at which to calculate the field. This array can have 65 | an arbitrary number of dimensions. The last dimension must of size 66 | 3, corresponding to the three cartesian coordinates 67 | 68 | Returns 69 | ------- 70 | E : ndarray, complex 71 | An array with the same dimensions as r, giving the field at each 72 | point 73 | """ 74 | jk = self.k_hat*self.material.n(s)*s/c 75 | 76 | e_inc = self.e_inc 77 | 78 | if self.p_inc is not None: 79 | # scale the incident power if requested 80 | h_inc = self.h_inc/self.material.eta(s) 81 | p_unscaled = np.sqrt(np.sum(np.cross(e_inc, h_inc.conj()).real)**2) 82 | e_inc = e_inc*np.sqrt(self.p_inc/p_unscaled) 83 | 84 | # Dimensions are expanded so that r can have an arbitrary number 85 | # of dimensions 86 | return e_inc*np.exp(np.dot(r, -jk))[..., None] 87 | 88 | def magnetic_field(self, s, r): 89 | """Calculate the magnetic field distribution at a given frequency 90 | 91 | Parameters 92 | ---------- 93 | s : complex 94 | Complex frequency at which to evaluate fields 95 | r : ndarray, real 96 | The locations at which to calculate the field. This array can have 97 | an arbitrary number of dimensions. The last dimension must of size 98 | 3, corresponding to the three cartesian coordinates 99 | 100 | Returns 101 | ------- 102 | E : ndarray, complex 103 | An array with the same dimensions as r, giving the field at each 104 | point 105 | """ 106 | jk = self.k_hat*self.material.n(s)*s/c 107 | 108 | h_inc = self.h_inc/self.material.eta(s) 109 | if self.p_inc is not None: 110 | # scale the incident power if requested 111 | h_inc = self.h_inc/self.material.eta(s) 112 | p_unscaled = np.sqrt(np.sum(np.cross(self.e_inc, h_inc.conj()).real)**2) 113 | h_inc = h_inc*np.sqrt(self.p_inc/p_unscaled) 114 | 115 | return h_inc*np.exp(np.dot(r, -jk))[..., None] 116 | 117 | 118 | def planewave_angles(theta, phi, alpha, degrees=True, material=FreeSpace, 119 | p_inc=1.0): 120 | """Create a plane-wave with direction and (linear) polarisation specified 121 | by angles. By default, it is scaled to unit intensity. 122 | 123 | Parameters 124 | ---------- 125 | theta: float 126 | Propagation vector, declination from z axis 127 | phi: float 128 | Propagation vector, azimuthal angle with respect to x axis 129 | alpha: float 130 | Polarisation angle of uncertain interpretation 131 | degrees: bool, optional 132 | If True, angles are in degrees, otherwise in radians 133 | material: Material, optional 134 | The background material 135 | p_inc: integer or None, optional 136 | The scaling to apply to the incident field 137 | """ 138 | if degrees: 139 | theta = np.deg2rad(theta) 140 | phi = np.deg2rad(phi) 141 | alpha = np.deg2rad(alpha) 142 | 143 | k_hat = [-sin(theta)*cos(phi), -sin(theta)*sin(phi), -cos(theta)] 144 | 145 | e = [cos(phi)*cos(theta)*cos(alpha)-sin(phi)*sin(alpha), 146 | sin(phi)*cos(theta)*cos(alpha)+cos(phi)*sin(alpha), 147 | -sin(theta)*cos(alpha)] 148 | 149 | return PlaneWaveSource(e, k_hat, material, p_inc=p_inc) 150 | -------------------------------------------------------------------------------- /openmodes/templates/three_js_plot.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | Wireframe 4 | Format 5 | 11 | 12 | Arrow Length 13 | 14 | 15 |
16 | 17 | -------------------------------------------------------------------------------- /openmodes/version.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | #----------------------------------------------------------------------------- 3 | # OpenModes - An eigenmode solver for open electromagnetic resonantors 4 | # Copyright (C) 2013 David Powell 5 | # 6 | # This program is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation, either version 3 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # This program is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with this program. If not, see . 18 | #----------------------------------------------------------------------------- 19 | 20 | """This file contains the version number of OpenModes. It will be called by 21 | setup.py before installing, generating source package etc. 22 | 23 | If the source is controlled by git, then git will be used to get the latest 24 | tag and any subsequent updates. 25 | 26 | Otherwise, a hard-coded value will be used - this must be UPDATED MANUALLY 27 | before tagging each release 28 | 29 | Based on public domain code from Douglas Creager 30 | """ 31 | 32 | from subprocess import Popen, PIPE 33 | import os.path as osp 34 | 35 | # THIS MUST BE UPDATED MANUALLY FOR NON-GIT 36 | RELEASE_VERSION = "1.3.2" 37 | 38 | 39 | def version_git(): 40 | try: 41 | openmodes_path = osp.normpath(osp.join(osp.dirname(osp.realpath(__file__)), "..")) 42 | p = Popen(['git', 'describe', '--abbrev=%d' % 4], stdout=PIPE, 43 | stderr=PIPE, universal_newlines=True, cwd=openmodes_path) 44 | p.stderr.close() 45 | line = p.stdout.readlines()[0] 46 | return line.strip() 47 | except: 48 | return None 49 | 50 | __version__ = version_git() or RELEASE_VERSION 51 | -------------------------------------------------------------------------------- /src/common.f90: -------------------------------------------------------------------------------- 1 | ! OpenModes - An eigenmode solver for open electromagnetic resonantors 2 | ! Copyright (C) 2013 David Powell 3 | ! 4 | ! This program is free software: you can redistribute it and/or modify 5 | ! it under the terms of the GNU General Public License as published by 6 | ! the Free Software Foundation, either version 3 of the License, or 7 | ! (at your option) any later version. 8 | ! 9 | ! This program is distributed in the hope that it will be useful, 10 | ! but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | ! MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | ! GNU General Public License for more details. 13 | ! 14 | ! You should have received a copy of the GNU General Public License 15 | ! along with this program. If not, see . 16 | 17 | 18 | 19 | 20 | 21 | !!use iso_c_binding 22 | ! 23 | !subroutine test_wrapped(a) bind(c, name='test_wrapped_') 24 | ! use constants 25 | ! use iso_c_binding 26 | ! 27 | ! implicit none 28 | ! 29 | ! complex(c_float_complex), value, intent(in) :: a 30 | ! 31 | ! print *, "input value is ", a 32 | ! 33 | !end subroutine 34 | ! 35 | 36 | module constants 37 | implicit none 38 | 39 | integer, parameter :: WP = 8 ! working precision, can be 4 or 8 40 | ! when adjusting working precision, need to change line in file .f2py_f2cmap 41 | integer, parameter :: DP = 8 42 | integer, parameter :: SP = 4 43 | end module 44 | 45 | 46 | subroutine set_threads(n) 47 | ! Set the number of openmp threads 48 | ! -1 signifies to have as many threads as CPUs 49 | use omp_lib 50 | !use mkl_service 51 | implicit none 52 | 53 | integer, intent(in) :: n 54 | 55 | if (n == -1) then 56 | call omp_set_num_threads(omp_get_num_procs()) 57 | else 58 | call omp_set_num_threads(n) 59 | end if 60 | end subroutine 61 | 62 | subroutine get_threads(n) 63 | ! Get the number of openmp threads 64 | use omp_lib 65 | implicit none 66 | 67 | integer, intent(out) :: n 68 | 69 | n = omp_get_max_threads() 70 | end subroutine 71 | 72 | module vectors 73 | ! interface 74 | ! 75 | ! pure function cross_product(a, b) 76 | ! use constants 77 | ! implicit none 78 | ! real(WP), intent(in), dimension(:) :: a, b 79 | ! real(WP), dimension(3) :: cross_product 80 | ! end function 81 | ! 82 | ! pure function mag(vector) 83 | ! use constants 84 | ! implicit none 85 | ! 86 | ! real(WP), intent(in), dimension(:) :: vector 87 | ! real(WP) :: mag 88 | ! end function 89 | ! end interface 90 | 91 | contains 92 | 93 | 94 | pure function cross_product(a, b) 95 | use constants 96 | implicit none 97 | real(WP), intent(in), dimension(:) :: a, b 98 | real(WP), dimension(3) :: cross_product 99 | 100 | cross_product(1) = a(2)*b(3) - a(3)*b(2) 101 | cross_product(2) = a(3)*b(1) - a(1)*b(3) 102 | cross_product(3) = a(1)*b(2) - a(2)*b(1) 103 | 104 | end function 105 | 106 | pure function mag(vector) 107 | use constants 108 | implicit none 109 | 110 | real(WP), intent(in), dimension(:) :: vector 111 | real(WP) :: mag 112 | 113 | mag = sqrt(dot_product(vector, vector)) 114 | end function 115 | 116 | 117 | end module vectors -------------------------------------------------------------------------------- /src/dunavant.pyf: -------------------------------------------------------------------------------- 1 | ! -*- f90 -*- 2 | ! Note: the context of this file is case sensitive. 3 | 4 | python module dunavant ! in 5 | interface ! in :dunavant 6 | subroutine dunavant_order_num(rule,order_num) ! in :dunavant:src/dunavant.f90 7 | integer(kind=4) intent(in) :: rule 8 | integer(kind=4) intent(out) :: order_num 9 | end subroutine dunavant_order_num 10 | subroutine dunavant_rule(rule,order_num,xy,w) ! in :dunavant:src/dunavant.f90 11 | integer(kind=4) intent(in) :: rule 12 | integer(kind=4) intent(in) :: order_num 13 | real(kind=8) dimension(2,order_num),intent(out),depend(order_num) :: xy 14 | real(kind=8) dimension(order_num),intent(out),depend(order_num) :: w 15 | end subroutine dunavant_rule 16 | end interface 17 | end python module dunavant 18 | 19 | ! This file was auto-generated with f2py (version:2). 20 | ! See http://cens.ioc.ee/projects/f2py2e/ 21 | -------------------------------------------------------------------------------- /test/Test MFIE.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "metadata": { 3 | "name": "", 4 | "signature": "" 5 | }, 6 | "nbformat": 3, 7 | "nbformat_minor": 0, 8 | "worksheets": [ 9 | { 10 | "cells": [ 11 | { 12 | "cell_type": "code", 13 | "collapsed": false, 14 | "input": [ 15 | "import os.path as osp\n", 16 | "\n", 17 | "import numpy as np\n", 18 | "import matplotlib.pyplot as plt\n", 19 | "%matplotlib inline\n", 20 | "import scipy.linalg as la\n", 21 | "\n", 22 | "import logging\n", 23 | "logging.getLogger().setLevel(logging.DEBUG)\n", 24 | "\n", 25 | "import openmodes\n", 26 | "import openmodes.basis\n", 27 | "from openmodes.operator import EfieOperator, MfieOperator\n", 28 | "from openmodes.operator.mfie import TMfieOperator\n", 29 | "from openmodes.sources import PlaneWaveSource\n", 30 | "from openmodes.integration import DunavantRule" 31 | ], 32 | "language": "python", 33 | "metadata": {}, 34 | "outputs": [], 35 | "prompt_number": "" 36 | }, 37 | { 38 | "cell_type": "code", 39 | "collapsed": false, 40 | "input": [ 41 | "from openmodes.ipython import init_3d\n", 42 | "init_3d()" 43 | ], 44 | "language": "python", 45 | "metadata": {}, 46 | "outputs": [], 47 | "prompt_number": "" 48 | }, 49 | { 50 | "cell_type": "code", 51 | "collapsed": false, 52 | "input": [ 53 | "basis_class = openmodes.basis.DivRwgBasis\n", 54 | "#basis_class = openmodes.basis.LoopStarBasis\n", 55 | "\n", 56 | "\n", 57 | "name = 'sphere'\n", 58 | "\n", 59 | "parameters = {'radius': 5e-3, 'mesh_tol': 2e-3}\n", 60 | "\n", 61 | "sim_efie = openmodes.Simulation(name=\"EFIE\", operator_class=EfieOperator,\n", 62 | " basis_class=basis_class)\n", 63 | "sim_mfie = openmodes.Simulation(name=\"MFIE\", operator_class=MfieOperator,\n", 64 | " basis_class=basis_class)\n", 65 | "\n", 66 | "mesh = sim_efie.load_mesh(osp.join(openmodes.geometry_dir, name+'.geo'),\n", 67 | " parameters=parameters)\n", 68 | "sim_efie.place_part(mesh)\n", 69 | "sim_mfie.place_part(mesh)" 70 | ], 71 | "language": "python", 72 | "metadata": {}, 73 | "outputs": [], 74 | "prompt_number": "" 75 | }, 76 | { 77 | "cell_type": "code", 78 | "collapsed": false, 79 | "input": [ 80 | "#num_freqs = len(freqs)\n", 81 | "\n", 82 | "#extinction_efie = np.empty(num_freqs, np.complex128)\n", 83 | "#extinction_mfie = np.empty(num_freqs, np.complex128)\n", 84 | "\n", 85 | "e_inc = np.array([1, 0, 0], dtype=np.complex128)#/np.sqrt(2)\n", 86 | "k_hat = np.array([0, 0, 1], dtype=np.complex128)\n", 87 | "\n", 88 | "pw = PlaneWaveSource(e_inc, k_hat)\n", 89 | "\n", 90 | "\n", 91 | "s= 2j*np.pi*1e9\n", 92 | "Z_efie = sim_efie.impedance(s)\n", 93 | "V_efie = sim_efie.source_vector(pw, s)\n", 94 | "I_efie = Z_efie.solve(V_efie)\n", 95 | "\n", 96 | "Z_mfie = sim_mfie.impedance(s)\n", 97 | "V_mfie = sim_mfie.source_vector(pw, s)\n", 98 | "I_mfie = Z_mfie.solve(V_mfie)\n" 99 | ], 100 | "language": "python", 101 | "metadata": {}, 102 | "outputs": [], 103 | "prompt_number": "" 104 | }, 105 | { 106 | "cell_type": "code", 107 | "collapsed": false, 108 | "input": [ 109 | "#%debug\n", 110 | "from numpy import savetxt\n", 111 | "savetxt('nodes.txt', mesh.nodes)\n", 112 | "print mesh.polygons[0], mesh.polygons[96]\n", 113 | "print mesh.surface_normals[0]" 114 | ], 115 | "language": "python", 116 | "metadata": {}, 117 | "outputs": [], 118 | "prompt_number": "" 119 | }, 120 | { 121 | "cell_type": "code", 122 | "collapsed": false, 123 | "input": [ 124 | "sim_efie.plot_3d(I_efie*100, output_format='webgl')" 125 | ], 126 | "language": "python", 127 | "metadata": {}, 128 | "outputs": [] 129 | }, 130 | { 131 | "cell_type": "code", 132 | "collapsed": false, 133 | "input": [ 134 | "#V2 = sim_mfie.empty_vector()\n", 135 | "#V2[:] = la.solve(Z_mfie[:].basis_o.gram_matrix, V_mfie)" 136 | ], 137 | "language": "python", 138 | "metadata": {}, 139 | "outputs": [] 140 | }, 141 | { 142 | "cell_type": "code", 143 | "collapsed": false, 144 | "input": [ 145 | "sim_mfie.plot_3d(I_mfie*100, output_format='webgl')" 146 | ], 147 | "language": "python", 148 | "metadata": {}, 149 | "outputs": [] 150 | }, 151 | { 152 | "cell_type": "markdown", 153 | "metadata": {}, 154 | "source": [ 155 | "Confirm that the absolute magnitude of the EFIE and MFIE solutions also agree" 156 | ] 157 | }, 158 | { 159 | "cell_type": "code", 160 | "collapsed": false, 161 | "input": [ 162 | "plt.figure(figsize=(10, 5))\n", 163 | "plt.plot(abs(I_efie), 'x')\n", 164 | "plt.plot(abs(I_mfie), '+')\n", 165 | "plt.show()" 166 | ], 167 | "language": "python", 168 | "metadata": {}, 169 | "outputs": [] 170 | } 171 | ], 172 | "metadata": {} 173 | } 174 | ] 175 | } -------------------------------------------------------------------------------- /test/helpers.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | #----------------------------------------------------------------------------- 3 | # OpenModes - An eigenmode solver for open electromagnetic resonantors 4 | # Copyright (C) 2013 David Powell 5 | # 6 | # This program is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation, either version 3 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # This program is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with this program. If not, see . 18 | #----------------------------------------------------------------------------- 19 | """Helper routines for tests""" 20 | 21 | import numpy as np 22 | import os.path as osp 23 | import os 24 | import pickle 25 | from numpy.testing import assert_allclose 26 | 27 | 28 | def write_1d_complex(filename, data): 29 | "Write a 1D complex array to a text file" 30 | with open(filename, "wt") as outfile: 31 | for d in data: 32 | outfile.write("%.8e %.8e\n" % (d.real, d.imag)) 33 | 34 | 35 | def read_1d_complex(filename): 36 | "Read a 1D complex array from a text file" 37 | 38 | data = [] 39 | with open(filename, "rt") as infile: 40 | for d in infile: 41 | d = d.split() 42 | data.append(float(d[0])+1j*float(d[1])) 43 | 44 | return np.array(data, dtype=np.complex128) 45 | 46 | 47 | def write_2d_real(filename, data): 48 | "Write a 2D real array to a text file" 49 | with open(filename, "wt") as outfile: 50 | for row in data: 51 | for d in row: 52 | outfile.write("%.8e " % d) 53 | outfile.write("\n") 54 | 55 | 56 | def read_2d_real(filename): 57 | "Read a 2D real array from a text file" 58 | 59 | data = [] 60 | with open(filename, "rt") as infile: 61 | for row in infile: 62 | row = row.split() 63 | data.append([float(d) for d in row]) 64 | 65 | return np.array(data, dtype=np.float64) 66 | 67 | 68 | def get_test_dir(tests_filename): 69 | location, fname = osp.split(tests_filename) 70 | fname = osp.splitext(fname)[0] 71 | test_dir = osp.join(location, 'reference', fname) 72 | if not osp.exists(test_dir): 73 | os.makedirs(test_dir) 74 | return test_dir 75 | 76 | 77 | def get_input_dir(tests_filename, name=None): 78 | location, fname = osp.split(tests_filename) 79 | fname = osp.splitext(fname)[0] 80 | input_dir = osp.join(location, 'input', fname) 81 | if name is not None: 82 | input_dir = osp.join(input_dir, name) 83 | if not osp.exists(input_dir): 84 | os.makedirs(input_dir) 85 | return input_dir 86 | 87 | 88 | def compare_ref(val, reference, rtol, name): 89 | "Compare a value against a stored reference" 90 | if isinstance(val, np.ndarray): 91 | # Compare numpy arrays 92 | assert_allclose(val, reference, rtol=rtol, 93 | err_msg='Result "{}" differs from reference'.format(name)) 94 | elif isinstance(val, dict): 95 | # Compare dictionaries, ensuring keys and all values are the same 96 | assert(set(val.keys()) == set(reference.keys())) 97 | for key in val.keys(): 98 | compare_ref(val[key], reference[key], rtol, "{}[{}]".format(name, key)) 99 | else: 100 | raise ValueError("Unable to compare {} of type {} to reference".format(name, type(val))) 101 | 102 | 103 | def run_test(func, tests_filename): 104 | "Run a test function and check the output against reference" 105 | results = func() 106 | test_dir = get_test_dir(tests_filename) 107 | 108 | with open(osp.join(test_dir, results['name'])+".pickle", "rb") as infile: 109 | reference = pickle.load(infile) 110 | 111 | assert(set(reference.keys()) == set(results['results'].keys())) 112 | for name, val in results['results'].items(): 113 | try: 114 | rtol = results['rtol'][name] 115 | except: 116 | rtol = 1e-7 117 | 118 | compare_ref(val, reference[name], rtol, name) 119 | 120 | 121 | def create_reference(func, tests_filename): 122 | "Run a test function and generate reference output" 123 | results = func() 124 | test_dir = get_test_dir(tests_filename) 125 | 126 | with open(osp.join(test_dir, results['name'])+".pickle", "wb") as outfile: 127 | pickle.dump(results['results'], outfile, protocol=2, fix_imports=True) 128 | -------------------------------------------------------------------------------- /test/input/test_basis/SRR.msh: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DavidPowell/OpenModes/161bd0b30036c98caf4ab0cd463032a4ba22a382/test/input/test_basis/SRR.msh -------------------------------------------------------------------------------- /test/input/test_basis/rectangle.msh: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DavidPowell/OpenModes/161bd0b30036c98caf4ab0cd463032a4ba22a382/test/input/test_basis/rectangle.msh -------------------------------------------------------------------------------- /test/input/test_horseshoe/horseshoe_rect.msh: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DavidPowell/OpenModes/161bd0b30036c98caf4ab0cd463032a4ba22a382/test/input/test_horseshoe/horseshoe_rect.msh -------------------------------------------------------------------------------- /test/input/test_multipoles/sphere.msh: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DavidPowell/OpenModes/161bd0b30036c98caf4ab0cd463032a4ba22a382/test/input/test_multipoles/sphere.msh -------------------------------------------------------------------------------- /test/input/test_poles/srr.msh: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DavidPowell/OpenModes/161bd0b30036c98caf4ab0cd463032a4ba22a382/test/input/test_poles/srr.msh -------------------------------------------------------------------------------- /test/input/test_sphere/sphere.msh: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DavidPowell/OpenModes/161bd0b30036c98caf4ab0cd463032a4ba22a382/test/input/test_sphere/sphere.msh -------------------------------------------------------------------------------- /test/reference/test_horseshoe/extinction.txt: -------------------------------------------------------------------------------- 1 | 2.68221927e-14 1.53181237e-08 2 | 2.15434757e-12 4.59318017e-08 3 | 1.67432146e-11 7.69400345e-08 4 | 6.52141835e-11 1.08617963e-07 5 | 1.81719368e-10 1.41260339e-07 6 | 4.15861842e-10 1.75191076e-07 7 | 8.36844397e-10 2.10774742e-07 8 | 1.53962263e-09 2.48430887e-07 9 | 2.65397999e-09 2.88652454e-07 10 | 4.35799604e-09 3.32030025e-07 11 | 6.89830353e-09 3.79284471e-07 12 | 1.06211193e-08 4.31311759e-07 13 | 1.60208512e-08 4.89245598e-07 14 | 2.38182404e-08 5.54546509e-07 15 | 3.50897767e-08 6.29130419e-07 16 | 5.14893923e-08 7.15556542e-07 17 | 7.56430229e-08 8.17302991e-07 18 | 1.11881688e-07 9.39165258e-07 19 | 1.67670082e-07 1.08779568e-06 20 | 2.56536004e-07 1.27226577e-06 21 | 4.04374449e-07 1.50386800e-06 22 | 6.63359985e-07 1.79139461e-06 23 | 1.14029688e-06 2.11519493e-06 24 | 2.02146532e-06 2.31602948e-06 25 | 3.33466983e-06 1.81756741e-06 26 | 4.00337612e-06 1.27640793e-07 27 | 3.18083994e-06 -1.30336104e-06 28 | 2.11569254e-06 -1.67519125e-06 29 | 1.40839131e-06 -1.58140538e-06 30 | 9.83928752e-07 -1.38599560e-06 31 | 7.22453767e-07 -1.19366686e-06 32 | 5.53299441e-07 -1.02592901e-06 33 | 4.38758850e-07 -8.83036374e-07 34 | 3.58349822e-07 -7.60804385e-07 35 | 3.00475055e-07 -6.54878167e-07 36 | 2.58286861e-07 -5.61659349e-07 37 | 2.27596468e-07 -4.78366285e-07 38 | 2.05773375e-07 -4.02900991e-07 39 | 1.91139308e-07 -3.33702645e-07 40 | 1.82618945e-07 -2.69629129e-07 41 | 1.79528776e-07 -2.09870043e-07 42 | 1.81442310e-07 -1.53885356e-07 43 | 1.88098032e-07 -1.01362718e-07 44 | 1.99331093e-07 -5.21871366e-08 45 | 2.15017751e-07 -6.41756584e-09 46 | 2.35026345e-07 3.57344516e-08 47 | 2.59171922e-07 7.39285505e-08 48 | 2.87174242e-07 1.07725659e-07 49 | 3.18621250e-07 1.36627512e-07 50 | 3.52942109e-07 1.60126999e-07 51 | 3.89395196e-07 1.77768243e-07 52 | 4.27076494e-07 1.89212150e-07 53 | 4.64952079e-07 1.94299767e-07 54 | 5.01914712e-07 1.93103449e-07 55 | 5.36859434e-07 1.85955596e-07 56 | 5.68768074e-07 1.73447368e-07 57 | 5.96789284e-07 1.56395186e-07 58 | 6.20300761e-07 1.35779455e-07 59 | 6.38943907e-07 1.12665779e-07 60 | 6.52627240e-07 8.81220066e-08 61 | 6.61501358e-07 6.31439141e-08 62 | 6.65913241e-07 3.85988299e-08 63 | 6.66349855e-07 1.51914868e-08 64 | 6.63380617e-07 -6.54837070e-09 65 | 6.57605921e-07 -2.62604888e-08 66 | 6.49615944e-07 -4.37361748e-08 67 | 6.39961133e-07 -5.88934602e-08 68 | 6.29133765e-07 -7.17499623e-08 69 | 6.17558775e-07 -8.23967358e-08 70 | 6.05591606e-07 -9.09750939e-08 71 | 5.93520902e-07 -9.76573201e-08 72 | 5.81574187e-07 -1.02631439e-07 73 | 5.69925082e-07 -1.06089757e-07 74 | 5.58701045e-07 -1.08220654e-07 75 | 5.47990954e-07 -1.09203031e-07 76 | 5.37852128e-07 -1.09202833e-07 77 | 5.28316593e-07 -1.08371130e-07 78 | 5.19396495e-07 -1.06843339e-07 79 | 5.11088693e-07 -1.04739227e-07 80 | 5.03378580e-07 -1.02163432e-07 81 | 4.96243222e-07 -9.92063081e-08 82 | 4.89653919e-07 -9.59449315e-08 83 | 4.83578277e-07 -9.24441816e-08 84 | 4.77981916e-07 -8.87578239e-08 85 | 4.72829875e-07 -8.49295684e-08 86 | 4.68087813e-07 -8.09941002e-08 87 | 4.63723058e-07 -7.69781062e-08 88 | 4.59705527e-07 -7.29013349e-08 89 | 4.56008528e-07 -6.87777388e-08 90 | 4.52609393e-07 -6.46167333e-08 91 | 4.49489879e-07 -6.04245810e-08 92 | 4.46636240e-07 -5.62058608e-08 93 | 4.44038910e-07 -5.19649227e-08 94 | 4.41691761e-07 -4.77071820e-08 95 | 4.39591001e-07 -4.34400848e-08 96 | 4.37733834e-07 -3.91736079e-08 97 | 4.36117101e-07 -3.49202299e-08 98 | 4.34736102e-07 -3.06944111e-08 99 | 4.33583772e-07 -2.65117164e-08 100 | 4.32650302e-07 -2.23877700e-08 101 | 4.31923189e-07 -1.83372340e-08 102 | -------------------------------------------------------------------------------- /test/reference/test_multipoles/pec_sphere_multipoles.pickle: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DavidPowell/OpenModes/161bd0b30036c98caf4ab0cd463032a4ba22a382/test/reference/test_multipoles/pec_sphere_multipoles.pickle -------------------------------------------------------------------------------- /test/reference/test_poles/srr_pair_combined_poles.pickle: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DavidPowell/OpenModes/161bd0b30036c98caf4ab0cd463032a4ba22a382/test/reference/test_poles/srr_pair_combined_poles.pickle -------------------------------------------------------------------------------- /test/reference/test_poles/srr_pair_separate_poles.pickle: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DavidPowell/OpenModes/161bd0b30036c98caf4ab0cd463032a4ba22a382/test/reference/test_poles/srr_pair_separate_poles.pickle -------------------------------------------------------------------------------- /test/reference/test_sphere/extinction_cfie.npy: -------------------------------------------------------------------------------- 1 | 1.20705755e-12 4.14866400e-09 2 | 2.11405256e-12 1.24159481e-08 3 | 6.19531110e-12 2.07225288e-08 4 | 1.93695367e-11 2.90939565e-08 5 | 5.02035013e-11 3.75555649e-08 6 | 1.10135442e-10 4.61321729e-08 7 | 2.13520771e-10 5.48478988e-08 8 | 3.77676235e-10 6.37259725e-08 9 | 6.22929468e-10 7.27885335e-08 10 | 9.72673770e-10 8.20564079e-08 11 | 1.45342633e-09 9.15488585e-08 12 | 2.09488695e-09 1.01283303e-07 13 | 2.92999293e-09 1.11274993e-07 14 | 3.99496438e-09 1.21536652e-07 15 | 5.32933198e-09 1.32078058e-07 16 | 6.97593728e-09 1.42905570e-07 17 | 8.98089252e-09 1.54021598e-07 18 | 1.13934843e-08 1.65423997e-07 19 | 1.42660017e-08 1.77105399e-07 20 | 1.76534664e-08 1.89052475e-07 21 | 2.16132382e-08 2.01245134e-07 22 | 2.62044668e-08 2.13655677e-07 23 | 3.14873581e-08 2.26247908e-07 24 | 3.75222217e-08 2.38976262e-07 25 | 4.43682701e-08 2.51784952e-07 26 | 5.20821396e-08 2.64607230e-07 27 | 6.07161179e-08 2.77364791e-07 28 | 7.03160716e-08 2.89967443e-07 29 | 8.09190916e-08 3.02313101e-07 30 | 9.25508977e-08 3.14288245e-07 31 | 1.05223080e-07 3.25768916e-07 32 | 1.18930295e-07 3.36622383e-07 33 | 1.33647564e-07 3.46709522e-07 34 | 1.49327884e-07 3.55887983e-07 35 | 1.65900362e-07 3.64016100e-07 36 | 1.83269126e-07 3.70957483e-07 37 | 2.01313235e-07 3.76586110e-07 38 | 2.19887789e-07 3.80791678e-07 39 | 2.38826363e-07 3.83484892e-07 40 | 2.57944785e-07 3.84602305e-07 41 | 2.77046172e-07 3.84110325e-07 42 | 2.95926972e-07 3.82008022e-07 43 | 3.14383704e-07 3.78328444e-07 44 | 3.32219917e-07 3.73138268e-07 45 | 3.49252927e-07 3.66535766e-07 46 | 3.65319840e-07 3.58647207e-07 47 | 3.80282481e-07 3.49621981e-07 48 | 3.94030924e-07 3.39626803e-07 49 | 4.06485485e-07 3.28839447e-07 50 | 4.17597179e-07 3.17442452e-07 51 | 4.27346746e-07 3.05617187e-07 52 | 4.35742501e-07 2.93538616e-07 53 | 4.42817273e-07 2.81370954e-07 54 | 4.48624765e-07 2.69264355e-07 55 | 4.53235620e-07 2.57352627e-07 56 | 4.56733472e-07 2.45751927e-07 57 | 4.59211179e-07 2.34560303e-07 58 | 4.60767398e-07 2.23857946e-07 59 | 4.61503593e-07 2.13707981e-07 60 | 4.61521517e-07 2.04157644e-07 61 | 4.60921185e-07 1.95239696e-07 62 | 4.59799282e-07 1.86973966e-07 63 | 4.58247999e-07 1.79368913e-07 64 | 4.56354201e-07 1.72423149e-07 65 | 4.54198903e-07 1.66126866e-07 66 | 4.51856961e-07 1.60463142e-07 67 | 4.49396949e-07 1.55409105e-07 68 | 4.46881158e-07 1.50936966e-07 69 | 4.44365687e-07 1.47014907e-07 70 | 4.41900578e-07 1.43607858e-07 71 | 4.39529991e-07 1.40678162e-07 72 | 4.37292381e-07 1.38186156e-07 73 | 4.35220668e-07 1.36090676e-07 74 | 4.33342408e-07 1.34349521e-07 75 | 4.31679943e-07 1.32919873e-07 76 | 4.30250545e-07 1.31758695e-07 77 | 4.29066555e-07 1.30823129e-07 78 | 4.28135520e-07 1.30070877e-07 79 | 4.27460345e-07 1.29460605e-07 80 | 4.27039472e-07 1.28952332e-07 81 | 4.26867074e-07 1.28507841e-07 82 | 4.26933306e-07 1.28091076e-07 83 | 4.27224595e-07 1.27668541e-07 84 | 4.27723981e-07 1.27209668e-07 85 | 4.28411512e-07 1.26687164e-07 86 | 4.29264696e-07 1.26077311e-07 87 | 4.30258994e-07 1.25360208e-07 88 | 4.31368351e-07 1.24519946e-07 89 | 4.32565760e-07 1.23544709e-07 90 | 4.33823828e-07 1.22426787e-07 91 | 4.35115344e-07 1.21162502e-07 92 | 4.36413829e-07 1.19752059e-07 93 | 4.37694043e-07 1.18199299e-07 94 | 4.38932446e-07 1.16511391e-07 95 | 4.40107596e-07 1.14698464e-07 96 | 4.41200469e-07 1.12773180e-07 97 | 4.42194712e-07 1.10750287e-07 98 | 4.43076805e-07 1.08646146e-07 99 | 4.43836147e-07 1.06478259e-07 100 | 4.44465069e-07 1.04264811e-07 101 | 4.44958778e-07 1.02024229e-07 102 | -------------------------------------------------------------------------------- /test/reference/test_sphere/extinction_efie.npy: -------------------------------------------------------------------------------- 1 | 7.52546208e-15 4.14801773e-09 2 | 6.01598124e-13 1.24156478e-08 3 | 4.63143942e-12 2.07223295e-08 4 | 1.77825767e-11 2.90938056e-08 5 | 4.86013392e-11 3.75554419e-08 6 | 1.08520631e-10 4.61320676e-08 7 | 2.11893982e-10 5.48478049e-08 8 | 3.76037413e-10 6.37258859e-08 9 | 6.21278310e-10 7.27884510e-08 10 | 9.71009954e-10 8.20563271e-08 11 | 1.45174964e-09 9.15487769e-08 12 | 2.09319734e-09 1.01283218e-07 13 | 2.92829059e-09 1.11274903e-07 14 | 3.99324976e-09 1.21536554e-07 15 | 5.32760581e-09 1.32077948e-07 16 | 6.97420057e-09 1.42905445e-07 17 | 8.97914655e-09 1.54021454e-07 18 | 1.13917306e-08 1.65423829e-07 19 | 1.42642421e-08 1.77105201e-07 20 | 1.76517028e-08 1.89052240e-07 21 | 2.16114725e-08 2.01244854e-07 22 | 2.62027010e-08 2.13655342e-07 23 | 3.14855937e-08 2.26247508e-07 24 | 3.75204600e-08 2.38975784e-07 25 | 4.43665116e-08 2.51784383e-07 26 | 5.20803838e-08 2.64606553e-07 27 | 6.07143629e-08 2.77363988e-07 28 | 7.03143138e-08 2.89966494e-07 29 | 8.09173251e-08 3.02311986e-07 30 | 9.25491139e-08 3.14286939e-07 31 | 1.05221268e-07 3.25767396e-07 32 | 1.18928438e-07 3.36620622e-07 33 | 1.33645643e-07 3.46707495e-07 34 | 1.49325875e-07 3.55885665e-07 35 | 1.65898237e-07 3.64013466e-07 36 | 1.83266851e-07 3.70954511e-07 37 | 2.01310773e-07 3.76582780e-07 38 | 2.19885097e-07 3.80787976e-07 39 | 2.38823396e-07 3.83480807e-07 40 | 2.57941496e-07 3.84597832e-07 41 | 2.77042510e-07 3.84105465e-07 42 | 2.95922889e-07 3.82002784e-07 43 | 3.14379149e-07 3.78322842e-07 44 | 3.32214844e-07 3.73132323e-07 45 | 3.49247292e-07 3.66529504e-07 46 | 3.65313605e-07 3.58640660e-07 47 | 3.80275611e-07 3.49615183e-07 48 | 3.94023389e-07 3.39619792e-07 49 | 4.06477264e-07 3.28832263e-07 50 | 4.17588254e-07 3.17435133e-07 51 | 4.27337104e-07 3.05609775e-07 52 | 4.35732134e-07 2.93531146e-07 53 | 4.42806177e-07 2.81363463e-07 54 | 4.48612938e-07 2.69256875e-07 55 | 4.53223065e-07 2.57345189e-07 56 | 4.56720192e-07 2.45744558e-07 57 | 4.59197177e-07 2.34553028e-07 58 | 4.60752680e-07 2.23850788e-07 59 | 4.61488162e-07 2.13700960e-07 60 | 4.61505378e-07 2.04150779e-07 61 | 4.60904341e-07 1.95233003e-07 62 | 4.59781736e-07 1.86967461e-07 63 | 4.58229752e-07 1.79362612e-07 64 | 4.56335255e-07 1.72417067e-07 65 | 4.54179257e-07 1.66121018e-07 66 | 4.51836615e-07 1.60457544e-07 67 | 4.49375902e-07 1.55403774e-07 68 | 4.46859409e-07 1.50931919e-07 69 | 4.44343235e-07 1.47010163e-07 70 | 4.41877423e-07 1.43603437e-07 71 | 4.39506135e-07 1.40674085e-07 72 | 4.37267824e-07 1.38182444e-07 73 | 4.35195414e-07 1.36087353e-07 74 | 4.33316463e-07 1.34346612e-07 75 | 4.31653314e-07 1.32917401e-07 76 | 4.30223244e-07 1.31756686e-07 77 | 4.29038593e-07 1.30821606e-07 78 | 4.28106913e-07 1.30069866e-07 79 | 4.27431114e-07 1.29460128e-07 80 | 4.27009636e-07 1.28952412e-07 81 | 4.26836660e-07 1.28508496e-07 82 | 4.26902341e-07 1.28092323e-07 83 | 4.27193109e-07 1.27670394e-07 84 | 4.27692005e-07 1.27212136e-07 85 | 4.28379080e-07 1.26690254e-07 86 | 4.29231842e-07 1.26081024e-07 87 | 4.30225752e-07 1.25364540e-07 88 | 4.31334755e-07 1.24524890e-07 89 | 4.32531843e-07 1.23550252e-07 90 | 4.33789620e-07 1.22432911e-07 91 | 4.35080874e-07 1.21169187e-07 92 | 4.36379120e-07 1.19759278e-07 93 | 4.37659117e-07 1.18207023e-07 94 | 4.38897319e-07 1.16519590e-07 95 | 4.40072278e-07 1.14707102e-07 96 | 4.41164968e-07 1.12782224e-07 97 | 4.42159030e-07 1.10759700e-07 98 | 4.43040936e-07 1.08655892e-07 99 | 4.43800083e-07 1.06488304e-07 100 | 4.44428796e-07 1.04275120e-07 101 | 4.44922277e-07 1.02034772e-07 102 | -------------------------------------------------------------------------------- /test/reference/test_sphere/extinction_mfie.npy: -------------------------------------------------------------------------------- 1 | 2.02322009e-10 4.21058649e-09 2 | 2.03175918e-10 1.26028392e-08 3 | 2.07756021e-10 2.10344749e-08 4 | 2.21810706e-10 2.95314563e-08 5 | 2.53981766e-10 3.81193678e-08 6 | 3.15831618e-10 4.68232555e-08 7 | 4.21879276e-10 5.56674555e-08 8 | 5.89644585e-10 6.46754091e-08 9 | 8.39699913e-10 7.38694594e-08 10 | 1.19572789e-09 8.32706266e-08 11 | 1.68458308e-09 9.28983540e-08 12 | 2.33635446e-09 1.02770222e-07 13 | 3.18442431e-09 1.12901623e-07 14 | 4.26551740e-09 1.23305389e-07 15 | 5.61973257e-09 1.33991367e-07 16 | 7.29054617e-09 1.44965937e-07 17 | 9.32477432e-09 1.56231466e-07 18 | 1.17724775e-08 1.67785690e-07 19 | 1.46867877e-08 1.79621026e-07 20 | 1.81236351e-08 1.91723817e-07 21 | 2.21413461e-08 2.04073511e-07 22 | 2.68000835e-08 2.16641784e-07 23 | 3.21610953e-08 2.29391634e-07 24 | 3.82857386e-08 2.42276480e-07 25 | 4.52342457e-08 2.55239286e-07 26 | 5.30642051e-08 2.68211796e-07 27 | 6.18287377e-08 2.81113931e-07 28 | 7.15743650e-08 2.93853444e-07 29 | 8.23385853e-08 3.06325922e-07 30 | 9.41472025e-08 3.18415263e-07 31 | 1.07011490e-07 3.29994713e-07 32 | 1.20925305e-07 3.40928596e-07 33 | 1.35862325e-07 3.51074782e-07 34 | 1.51773599e-07 3.60287963e-07 35 | 1.68585659e-07 3.68423705e-07 36 | 1.86199430e-07 3.75343185e-07 37 | 2.04490197e-07 3.80918445e-07 38 | 2.23308802e-07 3.85037900e-07 39 | 2.42484216e-07 3.87611751e-07 40 | 2.61827504e-07 3.88576932e-07 41 | 2.81137064e-07 3.87901159e-07 42 | 3.00204899e-07 3.85585726e-07 43 | 3.18823567e-07 3.81666746e-07 44 | 3.36793339e-07 3.76214668e-07 45 | 3.53929084e-07 3.69332053e-07 46 | 3.70066378e-07 3.61149769e-07 47 | 3.85066459e-07 3.51821871e-07 48 | 3.98819720e-07 3.41519590e-07 49 | 4.11247607e-07 3.30424873e-07 50 | 4.22302936e-07 3.18723928e-07 51 | 4.31968756e-07 3.06601187e-07 52 | 4.40256024e-07 2.94234014e-07 53 | 4.47200373e-07 2.81788353e-07 54 | 4.52858318e-07 2.69415451e-07 55 | 4.57303200e-07 2.57249637e-07 56 | 4.60621126e-07 2.45407094e-07 57 | 4.62907140e-07 2.33985506e-07 58 | 4.64261753e-07 2.23064402e-07 59 | 4.64787932e-07 2.12706042e-07 60 | 4.64588586e-07 2.02956679e-07 61 | 4.63764548e-07 1.93848053e-07 62 | 4.62413022e-07 1.85398993e-07 63 | 4.60626441e-07 1.77617037e-07 64 | 4.58491689e-07 1.70499992e-07 65 | 4.56089605e-07 1.64037395e-07 66 | 4.53494723e-07 1.58211839e-07 67 | 4.50775193e-07 1.53000159e-07 68 | 4.47992819e-07 1.48374462e-07 69 | 4.45203187e-07 1.44303029e-07 70 | 4.42455848e-07 1.40751080e-07 71 | 4.39794518e-07 1.37681437e-07 72 | 4.37257298e-07 1.35055087e-07 73 | 4.34876877e-07 1.32831674e-07 74 | 4.32680729e-07 1.30969937e-07 75 | 4.30691295e-07 1.29428102e-07 76 | 4.28926147e-07 1.28164252e-07 77 | 4.27398145e-07 1.27136681e-07 78 | 4.26115588e-07 1.26304243e-07 79 | 4.25082375e-07 1.25626704e-07 80 | 4.24298169e-07 1.25065093e-07 81 | 4.23758597e-07 1.24582060e-07 82 | 4.23455467e-07 1.24142232e-07 83 | 4.23377034e-07 1.23712569e-07 84 | 4.23508306e-07 1.23262694e-07 85 | 4.23831387e-07 1.22765212e-07 86 | 4.24325884e-07 1.22195987e-07 87 | 4.24969336e-07 1.21534374e-07 88 | 4.25737694e-07 1.20763396e-07 89 | 4.26605818e-07 1.19869853e-07 90 | 4.27547996e-07 1.18844363e-07 91 | 4.28538459e-07 1.17681326e-07 92 | 4.29551885e-07 1.16378806e-07 93 | 4.30563880e-07 1.14938350e-07 94 | 4.31551414e-07 1.13364728e-07 95 | 4.32493208e-07 1.11665622e-07 96 | 4.33370059e-07 1.09851261e-07 97 | 4.34165099e-07 1.07934027e-07 98 | 4.34863980e-07 1.05928036e-07 99 | 4.35454994e-07 1.03848704e-07 100 | 4.35929116e-07 1.01712334e-07 101 | 4.36279989e-07 9.95356972e-08 102 | -------------------------------------------------------------------------------- /test/run_geometry.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | #----------------------------------------------------------------------------- 3 | # OpenModes - An eigenmode solver for open electromagnetic resonantors 4 | # Copyright (C) 2013 David Powell 5 | # 6 | # This program is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation, either version 3 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # This program is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with this program. If not, see . 18 | #----------------------------------------------------------------------------- 19 | 20 | 21 | import os.path as osp 22 | 23 | import numpy as np 24 | import matplotlib.pyplot as plt 25 | import scipy.linalg as la 26 | 27 | import logging 28 | logging.getLogger().setLevel(logging.INFO) 29 | 30 | import openmodes 31 | import openmodes.basis 32 | from openmodes.constants import c 33 | from openmodes.model import ScalarModelLeastSq 34 | from openmodes.sources import PlaneWaveSource 35 | 36 | 37 | def geometry_extinction_modes(name, freqs, num_modes, mesh_tol, 38 | plot_currents=False, plot_admittance=True, 39 | parameters={}, model_class=ScalarModelLeastSq): 40 | """Load a geometry file, calculate its modes by searching for 41 | singularities, and plot them in 3D. Then use the modes to calculate 42 | extinction, and compare with exact calculation 43 | """ 44 | 45 | sim = openmodes.Simulation(name=name, 46 | basis_class=openmodes.basis.LoopStarBasis) 47 | mesh = sim.load_mesh(osp.join(openmodes.geometry_dir, name+'.geo'), 48 | mesh_tol=mesh_tol, parameters=parameters) 49 | part = sim.place_part(mesh) 50 | 51 | s_start = 2j*np.pi*0.5*(freqs[0]+freqs[-1]) 52 | 53 | mode_s, mode_j = sim.singularities(s_start, num_modes, part) 54 | 55 | if plot_currents: 56 | for mode in range(num_modes): 57 | current = sim.empty_vector() 58 | current[:] = mode_j[:, mode] 59 | sim.plot_3d(solution=current, output_format='mayavi', 60 | compress_scalars=1) 61 | 62 | if not plot_admittance: 63 | return 64 | 65 | models = sim.construct_models(mode_s, mode_j, part, 66 | model_class=model_class) 67 | 68 | num_freqs = len(freqs) 69 | 70 | extinction = np.empty(num_freqs, np.complex128) 71 | extinction_sem = np.empty((num_freqs, num_modes), np.complex128) 72 | extinction_eem = np.empty((num_freqs, num_modes), np.complex128) 73 | 74 | e_inc = np.array([1, 1, 0], dtype=np.complex128)/np.sqrt(2) 75 | k_hat = np.array([0, 0, 1], dtype=np.complex128) 76 | pw = PlaneWaveSource(e_inc, k_hat) 77 | 78 | z_sem = np.empty((num_freqs, num_modes), np.complex128) 79 | z_eem = np.empty((num_freqs, num_modes), np.complex128) 80 | # z_eem_direct = np.empty((num_freqs, num_modes), np.complex128) 81 | 82 | for freq_count, s in sim.iter_freqs(freqs): 83 | Z = sim.impedance(s) 84 | V = sim.source_vector(pw, s) 85 | I = Z.solve(V) 86 | extinction[freq_count] = np.vdot(V[part], I) 87 | 88 | z_sem[freq_count] = [model.scalar_impedance(s) for model in models] 89 | extinction_sem[freq_count] = [np.vdot(V, model.solve(s, V)) 90 | for model in models] 91 | 92 | # z_eem_direct[freq_count], _ = Z.eigenmodes(num_modes, use_gram=False) 93 | z_eem[freq_count], j_eem = Z.eigenmodes(start_j=mode_j, use_gram=True) 94 | extinction_eem[freq_count] = [np.vdot(V, j_eem[:, mode]) * 95 | V.dot(j_eem[:, mode]) / z_eem[freq_count, mode] 96 | for mode in range(num_modes)] 97 | 98 | plt.figure(figsize=(10, 5)) 99 | plt.subplot(121) 100 | plt.plot(freqs*1e-9, extinction.real) 101 | plt.plot(freqs*1e-9, np.sum(extinction_sem.real, axis=1), '--') 102 | #plt.plot(freqs*1e-9, np.sum(extinction_eem.real, axis=1), '-.') 103 | plt.xlabel('f (GHz)') 104 | plt.subplot(122) 105 | plt.plot(freqs*1e-9, extinction.imag) 106 | plt.plot(freqs*1e-9, np.sum(extinction_sem.imag, axis=1), '--') 107 | #plt.plot(freqs*1e-9, np.sum(extinction_eem.imag, axis=1), '-.') 108 | plt.suptitle("Extinction") 109 | plt.show() 110 | 111 | # plt.figure(figsize=(10,5)) 112 | # plt.subplot(121) 113 | # plt.plot(freqs*1e-9, z_eem_direct.real) 114 | # #plt.ylim(0, 80) 115 | # plt.xlabel('f (GHz)') 116 | # plt.subplot(122) 117 | # plt.plot(freqs*1e-9, z_eem_direct.imag) 118 | # plt.plot([freqs[0]*1e-9, freqs[-1]*1e-9], [0, 0], 'k') 119 | # plt.suptitle("EEM impedance without Gram matrix") 120 | # plt.show() 121 | 122 | y_sem = 1/z_sem 123 | y_eem = 1/z_eem 124 | 125 | z_sem *= 2j*np.pi*freqs[:, None] 126 | z_eem *= 2j*np.pi*freqs[:, None] 127 | 128 | plt.figure(figsize=(10, 5)) 129 | plt.subplot(121) 130 | plt.plot(freqs*1e-9, z_eem.real) 131 | plt.plot(freqs*1e-9, z_sem.real, '--') 132 | plt.xlabel('f (GHz)') 133 | plt.subplot(122) 134 | plt.plot(freqs*1e-9, z_eem.imag) 135 | plt.plot(freqs*1e-9, z_sem.imag, '--') 136 | plt.suptitle("SEM and EEM impedance") 137 | plt.show() 138 | 139 | plt.figure(figsize=(10, 5)) 140 | plt.subplot(121) 141 | plt.plot(freqs*1e-9, y_eem.real) 142 | plt.plot(freqs*1e-9, y_sem.real, '--') 143 | plt.xlabel('f (GHz)') 144 | plt.subplot(122) 145 | plt.plot(freqs*1e-9, y_eem.imag) 146 | plt.plot(freqs*1e-9, y_sem.imag, '--') 147 | plt.xlabel('f (GHz)') 148 | plt.suptitle("SEM and EEM admittance") 149 | plt.show() 150 | 151 | 152 | geometry_extinction_modes('horseshoe_rect', np.linspace(1e8, 20e9, 101), 153 | 3, 1.5e-3, plot_currents=True) 154 | #geometry_extinction_modes('sphere', np.linspace(0.2e7, 8e7, 101), 8, 0.2) 155 | #geometry_extinction_modes('canonical_spiral', np.linspace(1e8, 15e9, 101), 156 | # 3, 1e-3, parameters={'arm_length': 12e-3, 157 | # 'inner_radius': 2e-3}, 158 | # plot_currents=False, plot_admittance=True) 159 | #geometry_extinction_modes('v_antenna', np.linspace(1e8, 15e9, 101), 160 | # 6, 1.5e-3, model_class=ScalarModelLeastSq) 161 | 162 | #geometry_extinction_modes('SRR', np.linspace(1e8, 20e9, 101), 4, 1e-3, 163 | # plot_currents=False, plot_admittance=True, 164 | # model_class=ScalarModelLeastSq) 165 | 166 | #geometry_extinction_modes('cross', np.linspace(1e8, 20e9, 101), 2, 1e-3, 167 | # plot_currents=False, plot_admittance=True) 168 | 169 | #geometry_extinction_modes('closed_ring', np.linspace(1e8, 15e9, 101), 170 | # 2, 1e-3, model_class=ScalarModelLeastSq, 171 | # plot_currents=True, plot_admittance=False, 172 | # parameters={'inner_radius': 3e-3, 173 | # 'outer_radius': 6e-3}) 174 | -------------------------------------------------------------------------------- /test/test_array.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # ----------------------------------------------------------------------------- 3 | # OpenModes - An eigenmode solver for open electromagnetic resonantors 4 | # Copyright (C) 2013-2015 David Powell 5 | # 6 | # This program is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation, either version 3 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # This program is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with this program. If not, see . 18 | # ----------------------------------------------------------------------------- 19 | 20 | import os.path as osp 21 | from openmodes.array import LookupArray 22 | import openmodes 23 | import numpy as np 24 | import pytest 25 | 26 | 27 | def test_indexing(): 28 | "Basic test for array indexing" 29 | 30 | sim = openmodes.Simulation() 31 | mesh = sim.load_mesh(osp.join(openmodes.geometry_dir, "SRR.geo")) 32 | 33 | group1 = sim.place_part() 34 | group2 = sim.place_part() 35 | 36 | srr1 = sim.place_part(mesh, parent=group1) 37 | srr2 = sim.place_part(mesh, parent=group1) 38 | srr3 = sim.place_part(mesh, parent=group2) 39 | 40 | basis = sim.basis_container[srr1] 41 | basis_len = len(basis) 42 | 43 | A = LookupArray(((group1, sim.basis_container), 44 | (group2, sim.basis_container), 5, 3)) 45 | 46 | assert(A.shape == (2*basis_len, basis_len, 5, 3)) 47 | 48 | A[group1, srr3] = 22.5 49 | assert(np.all(A[srr1, :] == 22.5)) 50 | assert(np.all(A[srr2] == 22.5)) 51 | 52 | V = LookupArray((("E", "H"), (sim.parts, sim.basis_container)), 53 | dtype=np.complex128) 54 | 55 | V["E", group1] = -4.7+22j 56 | V["H", srr1] = 5.2 57 | V["H", srr2] = 6.7 58 | 59 | assert(np.all(V["E", group1] == V["E"][group1])) 60 | assert(np.all(V["E", srr1] == -4.7+22j)) 61 | assert(np.all(V["E", srr2].imag == 22)) 62 | 63 | 64 | def test_list_indexing(): 65 | "List mucks up LookupArrays, which is now warned about" 66 | 67 | sim = openmodes.Simulation() 68 | mesh = sim.load_mesh(osp.join(openmodes.geometry_dir, "SRR.geo")) 69 | srr1 = sim.place_part(mesh) 70 | srr2 = sim.place_part(mesh) 71 | 72 | res = LookupArray((sim.operator.unknowns, (sim.parts, sim.basis_container), 73 | ('modes',), (srr2, sim.basis_container)), 74 | dtype=np.complex128) 75 | 76 | with pytest.warns(UserWarning): 77 | res[0, :, 0, [2]] 78 | 79 | 80 | if __name__ == "__main__": 81 | test_indexing() 82 | -------------------------------------------------------------------------------- /test/test_basis.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | #----------------------------------------------------------------------------- 3 | # OpenModes - An eigenmode solver for open electromagnetic resonantors 4 | # Copyright (C) 2013 David Powell 5 | # 6 | # This program is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation, either version 3 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # This program is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with this program. If not, see . 18 | #----------------------------------------------------------------------------- 19 | 20 | 21 | import numpy as np 22 | import matplotlib.pyplot as plt 23 | import os.path as osp 24 | 25 | import openmodes 26 | from openmodes.basis import DivRwgBasis, LoopStarBasis 27 | from openmodes.integration import DunavantRule 28 | from openmodes import Simulation 29 | from openmodes.visualise import write_vtk 30 | 31 | from helpers import read_2d_real, write_2d_real 32 | 33 | from numpy.testing import assert_allclose 34 | 35 | tests_location = osp.split(__file__)[0] 36 | mesh_dir = osp.join(tests_location, 'input', 'test_basis') 37 | reference_dir = osp.join(tests_location, 'reference', 'test_basis') 38 | 39 | 40 | def test_interpolate_rwg(plot=False, write_reference=False, skip_asserts=False): 41 | "Interpolate an RWG basis function over a triangle" 42 | 43 | sim = Simulation() 44 | srr = sim.load_mesh(osp.join(mesh_dir, 'SRR.msh')) 45 | 46 | basis = DivRwgBasis(srr) 47 | 48 | rwg_function = np.zeros(len(basis), np.float64) 49 | rwg_function[20] = 1 50 | 51 | rule = DunavantRule(10) 52 | 53 | r, basis_func = basis.interpolate_function(rwg_function, rule) 54 | 55 | if write_reference: 56 | # save reference data 57 | write_2d_real(osp.join(reference_dir, 'rwg_r.txt'), r) 58 | write_2d_real(osp.join(reference_dir, 'rwg_basis_func.txt'), 59 | basis_func) 60 | 61 | r_ref = read_2d_real(osp.join(reference_dir, 'rwg_r.txt')) 62 | basis_func_ref = read_2d_real(osp.join(reference_dir, 63 | 'rwg_basis_func.txt')) 64 | 65 | if not skip_asserts: 66 | assert_allclose(r, r_ref, rtol=1e-6) 67 | assert_allclose(basis_func, basis_func_ref, rtol=1e-6) 68 | 69 | if plot: 70 | plt.figure(figsize=(6, 6)) 71 | plt.quiver(r[:, 0], r[:, 1], basis_func[:, 0], basis_func[:, 1], 72 | scale=5e4) 73 | plt.show() 74 | 75 | 76 | def test_interpolate_loop_star(plot=False, write_reference=False, 77 | skip_asserts=False): 78 | "Interpolate loop and star basis functions" 79 | 80 | sim = Simulation() 81 | mesh = sim.load_mesh(osp.join(tests_location, 'input', 'test_basis', 82 | 'rectangle.msh')) 83 | 84 | basis = LoopStarBasis(mesh) 85 | 86 | ls_function = np.zeros(len(basis), np.float64) 87 | # chose one loop and one star 88 | star_basis = 28 89 | loop_basis = 4 90 | 91 | ls_function[star_basis] = 1 92 | ls_function[loop_basis] = 1 93 | 94 | rule = DunavantRule(10) 95 | r, basis_func = basis.interpolate_function(ls_function, rule) 96 | 97 | the_basis = basis[star_basis] 98 | 99 | plus_nodes = mesh.nodes[basis.mesh.polygons[the_basis.tri_p, 100 | the_basis.node_p]] 101 | minus_nodes = mesh.nodes[basis.mesh.polygons[the_basis.tri_m, 102 | the_basis.node_m]] 103 | 104 | if write_reference: 105 | # save reference data 106 | write_2d_real(osp.join(reference_dir, 'loop_star_r.txt'), r) 107 | write_2d_real(osp.join(reference_dir, 'loop_star_basis_func.txt'), 108 | basis_func) 109 | 110 | r_ref = read_2d_real(osp.join(reference_dir, 'loop_star_r.txt')) 111 | basis_func_ref = read_2d_real(osp.join(reference_dir, 112 | 'loop_star_basis_func.txt')) 113 | 114 | if not skip_asserts: 115 | assert_allclose(r, r_ref, rtol=1e-6) 116 | assert_allclose(basis_func, basis_func_ref, rtol=1e-6) 117 | 118 | if plot: 119 | plt.figure(figsize=(6, 6)) 120 | plt.quiver(r[:, 0], r[:, 1], basis_func[:, 0], basis_func[:, 1], 121 | pivot='middle') 122 | plt.plot(plus_nodes[:, 0], plus_nodes[:, 1], 'x') 123 | plt.plot(minus_nodes[:, 0], minus_nodes[:, 1], '+') 124 | plt.show() 125 | 126 | 127 | if __name__ == "__main__": 128 | test_interpolate_rwg(plot=True)#, skip_asserts=True) 129 | test_interpolate_loop_star(plot=True) #, skip_asserts=True) 130 | -------------------------------------------------------------------------------- /test/test_helpers.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | #----------------------------------------------------------------------------- 3 | # OpenModes - An eigenmode solver for open electromagnetic resonantors 4 | # Copyright (C) 2013 David Powell 5 | # 6 | # This program is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation, either version 3 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # This program is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with this program. If not, see . 18 | #----------------------------------------------------------------------------- 19 | 20 | from __future__ import print_function 21 | 22 | from openmodes.helpers import equivalence 23 | 24 | def test_equivalence(): 25 | "Tests for equivalence class code" 26 | 27 | relations1 = ((0, 1), (1, 2), (2, 3), (3, 0), 28 | (7, 12), (12, 9), (9, 4)) 29 | equiv1 = equivalence(relations1) 30 | print("Relationships are", relations1) 31 | print("Equivalence classes are", equiv1, "\n") 32 | 33 | equiv1_set = set(frozenset(x) for x in equiv1) 34 | assert(equiv1_set == set([frozenset([0, 1, 2, 3]), frozenset([9, 4, 12, 7])])) 35 | 36 | relations2 = ((1, 2), (0, 1), (2, 3), (3, 3), 37 | (7, 12), (12, 9), (9, 4), 38 | ("f", 14), ("h", "f"), (14, 15)) 39 | 40 | equiv2 = equivalence(relations2) 41 | print("Relationships are", relations2) 42 | print("Equivalence classes are", equiv2, "\n") 43 | 44 | equiv2_set = set(frozenset(x) for x in equiv2) 45 | assert(equiv2_set == set([frozenset([0, 1, 2, 3]), frozenset([9, 4, 12, 7]), frozenset(['h', 15, 14, 'f'])])) 46 | 47 | if __name__ == "__main__": 48 | test_equivalence() 49 | -------------------------------------------------------------------------------- /test/test_mesh.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | #----------------------------------------------------------------------------- 3 | # OpenModes - An eigenmode solver for open electromagnetic resonantors 4 | # Copyright (C) 2013 David Powell 5 | # 6 | # This program is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation, either version 3 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # This program is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with this program. If not, see . 18 | #----------------------------------------------------------------------------- 19 | 20 | import openmodes 21 | import os.path as osp 22 | 23 | 24 | def test_closed(): 25 | "Check whether the meshes are closed" 26 | 27 | # For each file, list whether each of the meshes it contains is closed. 28 | # There may be multiple meshes per file. 29 | # TODO: get all geometries working 30 | mesh_values = [#('asymmetric_ring.geo', (False, False)), 31 | # ('canonical_spiral.geo', (False,)), 32 | ('circle.geo', (False,)), 33 | ('circled_cross.geo', (False,)), 34 | ('closed_ring.geo', (False,)), 35 | ('cross.geo', (False,)), 36 | ('ellipsoid.geo', (True,)), 37 | ('horseshoe_rect.geo', (True,)), 38 | ('isosceles.geo', (False,)), 39 | ('rectangle.geo', (False,)), 40 | ('single.geo', (False,)), 41 | ('sphere.geo', (True,)), 42 | ('SRR.geo', (False,)), 43 | # ('torus.geo', (True,)), 44 | # ('v_antenna.geo', (False,)), 45 | ] 46 | 47 | sim = openmodes.Simulation() 48 | 49 | for filename, closed in mesh_values: 50 | mesh = sim.load_mesh(osp.join(openmodes.geometry_dir, filename), 51 | force_tuple=True) 52 | assert all(m.closed_surface == closed_val for (m, closed_val) in 53 | zip(mesh, closed)), \ 54 | ("%s closed_surface is not %s" % (filename, closed)) 55 | 56 | if __name__ == "__main__": 57 | test_closed() 58 | -------------------------------------------------------------------------------- /test/test_multipoles.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Fri Mar 18 16:59:21 2016 4 | 5 | @author: dap124 6 | """ 7 | 8 | import numpy as np 9 | import os.path as osp 10 | import matplotlib.pyplot as plt 11 | 12 | import openmodes 13 | from openmodes.mesh import gmsh 14 | from openmodes.constants import c 15 | from openmodes.sources import PlaneWaveSource 16 | 17 | import helpers 18 | 19 | tests_filename = __file__ 20 | input_dir = helpers.get_input_dir(tests_filename) 21 | meshfile = osp.join(input_dir, 'sphere.msh') 22 | 23 | 24 | def generate_mesh(): 25 | "Generate a fixed mesh file to ensure consistent results of tests" 26 | meshed_name = gmsh.mesh_geometry(osp.join(openmodes.geometry_dir, 'sphere.geo'), 27 | input_dir, parameters={'radius': 1, 'mesh_tol': 0.3}) 28 | assert(meshed_name == meshfile) 29 | 30 | 31 | def pec_sphere_multipoles(plot=False): 32 | "Multipole expansion of a PEC sphere" 33 | sim = openmodes.Simulation(name='pec_sphere_multipoles') 34 | mesh = sim.load_mesh(meshfile) 35 | sim.place_part(mesh) 36 | 37 | k0r = np.linspace(0.1, 3, 50) 38 | freqs = k0r*c/(2*np.pi) 39 | pw = PlaneWaveSource([1, 0, 0], [0, 0, 1], p_inc=1.0) 40 | 41 | multipole_order = 4 42 | extinction = np.empty(len(freqs), dtype=np.complex128) 43 | 44 | a_e = {} 45 | a_m = {} 46 | for l in range(multipole_order+1): 47 | for m in range(-l, l+1): 48 | a_e[l, m] = np.empty(len(freqs), dtype=np.complex128) 49 | a_m[l, m] = np.empty(len(freqs), dtype=np.complex128) 50 | 51 | for freq_count, s in sim.iter_freqs(freqs): 52 | Z = sim.impedance(s) 53 | V = sim.source_vector(pw, s) 54 | V_E = sim.source_vector(pw, s, extinction_field=True) 55 | I = Z.solve(V) 56 | extinction[freq_count] = np.vdot(V_E, I) 57 | 58 | a_en, a_mn = sim.multipole_decomposition(I, multipole_order, s) 59 | for l in range(multipole_order+1): 60 | for m in range(-l, l+1): 61 | a_e[l, m][freq_count] = a_en[l, m] 62 | a_m[l, m][freq_count] = a_mn[l, m] 63 | 64 | if plot: 65 | plt.figure() 66 | plt.plot(k0r, extinction.real) 67 | plt.plot(k0r, np.pi/k0r**2*sum(sum((np.abs(a_e[l, m])**2+np.abs(a_m[l, m])**2) for m in range(-l, l+1)) 68 | for l in range(1, multipole_order+1)), 'x') 69 | plt.plot(k0r, np.pi/k0r**2*sum(sum(np.sqrt(2*l+1)*(-m*a_e[l, m].real-a_m[l, m].real) for m in range(-l, l+1)) 70 | for l in range(1, multipole_order+1)), '+') 71 | plt.xlabel('$k_{0}r$') 72 | plt.title("Total extinction vs multipole extinction and scattering") 73 | plt.show() 74 | 75 | plt.figure() 76 | for l in range(1, multipole_order+1): 77 | for m in range(-l, l+1): 78 | plt.plot(k0r, np.pi/k0r**2*np.abs(a_e[l, m])**2) 79 | plt.plot(k0r, np.pi/k0r**2*np.abs(a_m[l, m])**2, '--') 80 | plt.title("Multipole contributions to scattering") 81 | plt.xlabel('$k_{0}r$') 82 | plt.show() 83 | 84 | plt.figure() 85 | for l in range(1, multipole_order+1): 86 | for m in (-1, 1): 87 | plt.plot(k0r, -np.pi/k0r**2*np.sqrt(2*l+1)*m*a_e[l, m].real) 88 | plt.plot(k0r, -np.pi/k0r**2*np.sqrt(2*l+1)*a_m[l, m].real, '--') 89 | plt.title("Multipole contributions to extinction") 90 | plt.xlabel('$k_{0}r$') 91 | plt.show() 92 | 93 | else: 94 | return {'name': 'pec_sphere_multipoles', 95 | 'results': {'k0r': k0r, 'extinction': extinction, 96 | 'a_e': a_e, 'a_m': a_m}, 97 | 'rtol': {'a_e': 1e-6, 'a_m': 1e-6}} 98 | 99 | 100 | # The following boilerplate code is needed to generate an actual test from 101 | # the function 102 | def test_pec_sphere_multipoles(): 103 | helpers.run_test(pec_sphere_multipoles, tests_filename) 104 | test_pec_sphere_multipoles.__doc__ = pec_sphere_multipoles.__doc__ 105 | 106 | if __name__ == "__main__": 107 | # Uncomment the following lines to update reference solutions 108 | # generate_mesh() 109 | # helpers.create_reference(pec_sphere_multipoles, tests_filename) 110 | 111 | # Run the tested functions to produce plots, without any checks 112 | pec_sphere_multipoles(plot=True) 113 | -------------------------------------------------------------------------------- /test/test_nonlinear_eig.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | #----------------------------------------------------------------------------- 3 | # OpenModes - An eigenmode solver for open electromagnetic resonantors 4 | # Copyright (C) 2013 David Powell 5 | # 6 | # This program is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation, either version 3 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # This program is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with this program. If not, see . 18 | #----------------------------------------------------------------------------- 19 | 20 | from __future__ import print_function 21 | 22 | import numpy as np 23 | from numpy.testing import assert_allclose 24 | import scipy.linalg as la 25 | 26 | from openmodes.eig import eig_newton, poles_cauchy 27 | from openmodes.integration import RectangularContour 28 | 29 | def test_nonlinear_eig(): 30 | """Test routines for nonlinear eigenvalues problems 31 | by giving them linear eigenvalue problems""" 32 | 33 | # Construct matrix from eigenform 34 | exact_s = np.array([-1, -0.5+3.3j, 0.5+7j, 4.6j]) 35 | exact_vr = np.array([[1, 0.7-.4j, 4, 2], 36 | [6, -1, -7j, 0.5j], 37 | [-2+1j, 3j, 0.5, 1], 38 | [0.87, 4.2j, -5+1j, -6]]).T 39 | exact_vl = la.inv(exact_vr) 40 | full_matrix = np.dot(exact_vr, np.dot(np.diag(exact_s), exact_vl)) 41 | exact_order = np.argsort(exact_s) 42 | 43 | # Compare with direct eigenvalue decomposition 44 | eig_s, eig_vl, eig_vr = la.eig(full_matrix, left=True) 45 | eig_order = np.argsort(eig_s) 46 | 47 | for exact_n, eig_n in zip(exact_order, eig_order): 48 | assert(np.abs((exact_s[exact_n]-eig_s[eig_n])/exact_s[exact_n]) < 1e-10) 49 | 50 | exact_vr_n = exact_vr[:, exact_n] 51 | eig_vr_n = eig_vr[:, eig_n] 52 | vr_ratio = np.abs(exact_vr_n/eig_vr_n) 53 | assert_allclose(vr_ratio, np.average(vr_ratio)) 54 | 55 | exact_vl_n = exact_vl[exact_n, :] 56 | eig_vl_n = eig_vl[:, eig_n] 57 | vl_ratio = np.abs(exact_vl_n/eig_vl_n) 58 | assert_allclose(vl_ratio, np.average(vl_ratio)) 59 | 60 | # Perform Cauchy line integral to estimate eigendecomposition 61 | def Z_func(s): 62 | return np.dot(exact_vr, np.dot(np.diag(s - exact_s), exact_vl)) 63 | 64 | contour = RectangularContour(-3-1j, 7+9j) 65 | estimates = poles_cauchy(Z_func, contour) 66 | 67 | estimates_order = np.argsort(estimates['s']) 68 | 69 | for exact_n, estimate_n in zip(exact_order, estimates_order): 70 | assert(np.abs((exact_s[exact_n]-estimates['s'][estimate_n])/exact_s[exact_n]) < 1e-10) 71 | 72 | exact_vr_n = exact_vr[:, exact_n] 73 | estimate_vr_n = estimates['vr'][:, estimate_n] 74 | vr_ratio = np.abs(exact_vr_n/estimate_vr_n) 75 | assert_allclose(vr_ratio, np.average(vr_ratio)) 76 | 77 | exact_vl_n = exact_vl[exact_n, :] 78 | estimate_vl_n = estimates['vl'][estimate_n, :] 79 | vl_ratio = np.abs(exact_vl_n/estimate_vl_n) 80 | 81 | assert_allclose(vl_ratio, np.average(vl_ratio)) 82 | 83 | # print(np.abs(exact_vr[:, exact_n]/estimates['vr'][:, estimate_n])) 84 | 85 | for estimate_n in estimates_order: 86 | magnitude = np.dot(estimates['vr'][:, estimate_n], estimates['vl'][estimate_n, :]) 87 | estimates['vr'][:, estimate_n] /= magnitude 88 | 89 | estimate_matrix = estimates['vr'].dot(np.diag(estimates['s']).dot(estimates['vl'])) 90 | assert(np.all(np.abs((estimate_matrix-full_matrix)/full_matrix) < 1e-10)) 91 | 92 | if __name__ == "__main__": 93 | test_nonlinear_eig() 94 | -------------------------------------------------------------------------------- /test/test_parts.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Tue Jun 24 17:18:32 2014 4 | 5 | @author: dap124 6 | """ 7 | 8 | from __future__ import print_function 9 | 10 | import openmodes 11 | import os.path as osp 12 | import weakref 13 | 14 | 15 | def test_part_references(): 16 | """Check that weak and strong references to Parts work as expected""" 17 | 18 | def keep_ref(): 19 | sim = openmodes.Simulation(name=name) 20 | mesh = sim.load_mesh(osp.join(openmodes.geometry_dir, name+'.geo'), 21 | mesh_tol=mesh_tol) 22 | part = sim.place_part(mesh) 23 | return weakref.ref(part), part 24 | 25 | def discard_ref(): 26 | sim = openmodes.Simulation(name=name) 27 | mesh = sim.load_mesh(osp.join(openmodes.geometry_dir, name+'.geo'), 28 | mesh_tol=mesh_tol) 29 | part = sim.place_part(mesh) 30 | return weakref.ref(part) 31 | 32 | name = "SRR" 33 | mesh_tol = 1e-3 34 | 35 | # create a reference to a part object which stays in memory 36 | weakref1, strongref1 = keep_ref() 37 | print(weakref1, strongref1) 38 | assert('SinglePart' in repr(weakref1)) 39 | assert('SinglePart' in repr(strongref1)) 40 | 41 | # create a reference to a part object which is already discarded 42 | weakref2 = discard_ref() 43 | print(weakref2) 44 | assert('dead' in repr(weakref2)) 45 | 46 | # delete the reference to the original part object 47 | del strongref1 48 | print(weakref2) 49 | assert('dead' in repr(weakref1)) 50 | 51 | if __name__ == "__main__": 52 | test_part_references() 53 | -------------------------------------------------------------------------------- /test/test_poles.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Tue Jun 16 15:50:37 2015 4 | 5 | @author: dap124 6 | """ 7 | 8 | from __future__ import print_function 9 | 10 | import os.path as osp 11 | 12 | import numpy as np 13 | 14 | import matplotlib.pyplot as plt 15 | 16 | import openmodes 17 | from openmodes.basis import LoopStarBasis 18 | from openmodes.operator import EfieOperator 19 | from openmodes.integration import ExternalModeContour 20 | from openmodes.mesh import gmsh 21 | 22 | import helpers 23 | 24 | import logging 25 | logging.getLogger().setLevel(logging.INFO) 26 | 27 | tests_filename = __file__ 28 | input_dir = helpers.get_input_dir(tests_filename) 29 | meshfile = osp.join(input_dir, 'srr.msh') 30 | 31 | parameters = {'inner_radius': 2.5e-3, 32 | 'outer_radius': 4e-3} 33 | 34 | 35 | def generate_mesh(): 36 | "Generate a fixed mesh file to ensure consistent results of tests" 37 | meshed_name = gmsh.mesh_geometry(osp.join(openmodes.geometry_dir, 'srr.geo'), 38 | input_dir, parameters=parameters) 39 | assert(meshed_name == meshfile) 40 | 41 | 42 | 43 | def srr_pair_combined_poles(plot=False): 44 | "Cauchy integral for poles of an SRR pair considered as a single part" 45 | 46 | sim = openmodes.Simulation(basis_class=LoopStarBasis, 47 | operator_class=EfieOperator) 48 | 49 | mesh = sim.load_mesh(meshfile) 50 | srr1 = sim.place_part(mesh) 51 | srr2 = sim.place_part(mesh, location=[0, 0, 1e-3]) 52 | 53 | # calculate modes of the SRR pair 54 | contour = ExternalModeContour(-0.5e11+1.2e11j, overlap_axes=0.2e6) 55 | result = sim.estimate_poles(contour) 56 | refined = sim.refine_poles(result) 57 | 58 | s_estimate = result.s 59 | s_refined = refined.s 60 | 61 | if plot: 62 | plt.figure(figsize=(6, 4)) 63 | plt.plot(s_estimate.imag, s_estimate.real, 'x') 64 | plt.plot(s_refined.imag, s_refined.real, '+') 65 | contour_points = np.array([s for s, w in contour]) 66 | plt.plot(contour_points.imag, contour_points.real) 67 | plt.show() 68 | 69 | return {'name': 'srr_pair_combined_poles', 70 | 'results' : {'s': s_refined.simple_view()}, 71 | 'rtol': {'s': 1e-5}} 72 | 73 | 74 | def test_srr_pair_combined_poles(): 75 | helpers.run_test(srr_pair_combined_poles, tests_filename) 76 | test_srr_pair_combined_poles.__doc__ = srr_pair_combined_poles.__doc__ 77 | 78 | 79 | def srr_pair_separate_poles(plot=False): 80 | "Cauchy integral for poles of an SRR pair considered as a single part" 81 | 82 | sim = openmodes.Simulation(basis_class=LoopStarBasis, 83 | operator_class=EfieOperator) 84 | 85 | mesh = sim.load_mesh(meshfile) 86 | srr1 = sim.place_part(mesh) 87 | srr2 = sim.place_part(mesh, location=[0, 0, 1e-3]) 88 | 89 | # calculate modes of each SRR 90 | contour = ExternalModeContour(-0.5e11+1.2e11j, overlap_axes=0.2e9) 91 | estimate = sim.estimate_poles(contour, parts=[srr1, srr2]) 92 | refined = sim.refine_poles(estimate) 93 | 94 | s_estimate = estimate[srr1].s 95 | s_refined = refined[srr1].s 96 | 97 | if plot: 98 | plt.figure(figsize=(6, 4)) 99 | plt.plot(s_estimate.imag, s_estimate.real, 'x') 100 | plt.plot() 101 | plt.plot(s_refined.imag, s_refined.real, '+') 102 | contour_points = np.array([s for s, w in contour]) 103 | plt.plot(contour_points.imag, contour_points.real) 104 | plt.show() 105 | 106 | return {'name': 'srr_pair_separate_poles', 107 | 'results' : {'s_srr1': s_refined[0].simple_view()}, 108 | 'rtol': {'s_srr1': 1e-5}} 109 | 110 | def test_srr_pair_separate_poles(): 111 | helpers.run_test(srr_pair_separate_poles, tests_filename) 112 | test_srr_pair_separate_poles.__doc__ = srr_pair_separate_poles.__doc__ 113 | 114 | 115 | if __name__ == "__main__": 116 | # Uncomment the following lines to update reference solutions 117 | # generate_mesh() 118 | # helpers.create_reference(srr_pair_combined_poles, tests_filename) 119 | # helpers.create_reference(srr_pair_separate_poles, tests_filename) 120 | 121 | srr_pair_combined_poles(plot=True) 122 | srr_pair_separate_poles(plot=True) 123 | -------------------------------------------------------------------------------- /test/test_sphere.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Thu Feb 26 09:36:46 2015 4 | 5 | @author: dap124 6 | """ 7 | 8 | from __future__ import print_function 9 | 10 | import os.path as osp 11 | 12 | import numpy as np 13 | from numpy.testing import assert_allclose 14 | import matplotlib.pyplot as plt 15 | 16 | import openmodes 17 | from openmodes.basis import DivRwgBasis 18 | from openmodes.sources import PlaneWaveSource 19 | from openmodes.constants import c, eta_0 20 | from openmodes.operator import MfieOperator, EfieOperator, CfieOperator 21 | 22 | from helpers import read_1d_complex, write_1d_complex 23 | 24 | tests_location = osp.split(__file__)[0] 25 | mesh_dir = osp.join(tests_location, 'input', 'test_sphere') 26 | reference_dir = osp.join(tests_location, 'reference', 'test_sphere') 27 | 28 | 29 | def sphere_extinction_analytical(freqs, r): 30 | """Analytical expressions for a PEC sphere's extinction for plane wave 31 | with E = 1V/m 32 | 33 | Parameters 34 | ---------- 35 | freqs : ndarray 36 | Frequencies at which to calculate 37 | r : real 38 | Radius of sphere 39 | """ 40 | from scipy.special import sph_jnyn 41 | N = 40 42 | 43 | k0r = freqs*2*np.pi/c*r 44 | #scs = np.zeros(len(k0r)) 45 | #scs_modal = np.zeros((len(k0r), N)) 46 | ecs_kerker = np.zeros(len(k0r)) 47 | 48 | for count, x in enumerate(k0r): 49 | jn, jnp, yn, ynp = sph_jnyn(N, x) 50 | h2n = jn - 1j*yn 51 | h2np = jnp - 1j*ynp 52 | a_n = ((x*jnp + jn)/(x*h2np + h2n))[1:] 53 | b_n = (jn/h2n)[1:] 54 | #scs[count] = 2*np.pi*sum((2*np.arange(1, N+1)+1)*(abs(a_n)**2 + abs(b_n)**2))/x**2 # 55 | #scs_modal[count] = 2*np.pi*(2*np.arange(1, N+1)+1)*(abs(a_n)**2 + abs(b_n)**2)/x**2 # 56 | ecs_kerker[count] = 2*np.pi*np.real(np.sum((2*np.arange(1, N+1)+1)*(a_n + b_n)))/x**2 57 | 58 | return ecs_kerker*r**2/eta_0 59 | 60 | 61 | def test_extinction_all(plot_extinction=False, skip_asserts=False, 62 | write_reference=False): 63 | "Extinction of a PEC sphere with EFIE, MFIE, CFIE" 64 | 65 | tests = (("EFIE", EfieOperator, 'extinction_efie.npy'), 66 | ("MFIE", MfieOperator, 'extinction_mfie.npy'), 67 | ("CFIE", CfieOperator, 'extinction_cfie.npy')) 68 | 69 | for operator_name, operator_class, reference_filename in tests: 70 | 71 | sim = openmodes.Simulation(name='horseshoe_extinction', 72 | basis_class=DivRwgBasis, 73 | operator_class=operator_class) 74 | 75 | radius = 5e-3 76 | sphere = sim.load_mesh(osp.join(mesh_dir, 'sphere.msh')) 77 | sim.place_part(sphere) 78 | 79 | num_freqs = 101 80 | freqs = np.linspace(1e8, 20e9, num_freqs) 81 | 82 | extinction = np.empty(num_freqs, np.complex128) 83 | 84 | e_inc = np.array([1, 0, 0], dtype=np.complex128) 85 | k_hat = np.array([0, 0, 1], dtype=np.complex128) 86 | pw = PlaneWaveSource(e_inc, k_hat) 87 | 88 | for freq_count, s in sim.iter_freqs(freqs): 89 | Z = sim.impedance(s) 90 | V = sim.source_vector(pw, s) 91 | V_E = sim.source_vector(pw, s, extinction_field=True) 92 | extinction[freq_count] = np.vdot(V_E, Z.solve(V)) 93 | 94 | extinction_filename = osp.join(reference_dir, reference_filename) 95 | 96 | if write_reference: 97 | # generate the reference extinction solution 98 | write_1d_complex(extinction_filename, extinction) 99 | 100 | extinction_ref = read_1d_complex(extinction_filename) 101 | 102 | if not skip_asserts: 103 | assert_allclose(extinction, extinction_ref, rtol=1e-3) 104 | 105 | if plot_extinction: 106 | # to plot the generated and reference solutions 107 | 108 | # calculate analytically 109 | extinction_analytical = sphere_extinction_analytical(freqs, radius) 110 | plt.figure(figsize=(8, 6)) 111 | plt.plot(freqs*1e-9, extinction.real) 112 | plt.plot(freqs*1e-9, extinction_ref.real, '--') 113 | plt.plot(freqs*1e-9, extinction_analytical, 'x') 114 | plt.plot(freqs*1e-9, extinction.imag) 115 | plt.plot(freqs*1e-9, extinction_ref.imag, '--') 116 | plt.xlabel('f (GHz)') 117 | plt.legend(('Calculated (Re)', 'Reference (Re)', 'Analytical (Re)', 118 | 'Calculated (Im)', 'Reference (Im)'), loc='right') 119 | plt.title('Extinction with operator %s' % operator_name) 120 | plt.ylim(ymin=0) 121 | plt.show() 122 | 123 | if __name__ == "__main__": 124 | test_extinction_all(plot_extinction=True, skip_asserts=True) 125 | -------------------------------------------------------------------------------- /test/test_vector.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Wed Jun 18 16:12:15 2014 4 | 5 | @author: dap124 6 | """ 7 | from __future__ import print_function 8 | 9 | import openmodes 10 | from openmodes.sources import PlaneWaveSource 11 | 12 | import os 13 | import os.path as osp 14 | import dill as pickle 15 | import tempfile 16 | import numpy as np 17 | 18 | 19 | def test_pickling_references(): 20 | """Test that references to parent objects survive the pickling and 21 | upickling process""" 22 | 23 | def save(): 24 | name = "SRR" 25 | mesh_tol = 1e-3 26 | 27 | sim = openmodes.Simulation(name=name) 28 | mesh = sim.load_mesh(osp.join(openmodes.geometry_dir, name+'.geo'), 29 | mesh_tol=mesh_tol) 30 | parent_part = sim.place_part() 31 | sim.place_part(mesh, parent=parent_part) 32 | sim.place_part(mesh, parent=parent_part, location=[10, 10, 10]) 33 | sim.place_part(mesh, location=[10, 10, 10]) 34 | 35 | parents_dict = {} 36 | for part in sim.parts.iter_all(): 37 | print("Original part", part) 38 | if part.parent_ref is None: 39 | parents_dict[str(part.id)] = 'None' 40 | else: 41 | parents_dict[str(part.id)] = str(part.parent_ref().id) 42 | 43 | pw = PlaneWaveSource([0, 1, 0], [0, 0, 1]) 44 | V = sim.source_vector(pw, 0) 45 | 46 | with tempfile.NamedTemporaryFile(delete=False) as output_file: 47 | file_name = output_file.name 48 | pickle.dump(V, output_file, protocol=0) 49 | 50 | return file_name, parents_dict 51 | 52 | def load(file_name): 53 | with open(file_name, "rb") as infile: 54 | V = pickle.load(infile) 55 | 56 | parents_dict = {} 57 | for part in V.lookup[1][0].keys(): 58 | print("Unpickled part", part) 59 | if part.parent_ref is None: 60 | parents_dict[str(part.id)] = 'None' 61 | else: 62 | parents_dict[str(part.id)] = str(part.parent_ref().id) 63 | 64 | return parents_dict 65 | 66 | file_name, original_parents_dict = save() 67 | loaded_parents_dict = load(file_name) 68 | 69 | # direct comparison of dictionaries seems to work 70 | assert(original_parents_dict == loaded_parents_dict) 71 | 72 | print("original parent references", original_parents_dict) 73 | print("loaded parent references", loaded_parents_dict) 74 | os.remove(file_name) 75 | 76 | 77 | def test_empty_array(): 78 | "Confirm that an empty array has the expected dimensions and lookup" 79 | sim = openmodes.Simulation() 80 | name = "SRR" 81 | mesh_tol = 1e-3 82 | 83 | sim = openmodes.Simulation(name=name) 84 | mesh = sim.load_mesh(osp.join(openmodes.geometry_dir, name+'.geo'), 85 | mesh_tol=mesh_tol) 86 | part1 = sim.place_part(mesh) 87 | part2 = sim.place_part(mesh, location=[0, 0, 5e-3]) 88 | 89 | vec = sim.empty_array() 90 | 91 | s = 2j*np.pi*1e9 92 | Z = sim.impedance(s) 93 | pw = PlaneWaveSource([0, 1, 0], [0, 0, 1]) 94 | V = sim.source_vector(pw, 0) 95 | I = Z.solve(V) 96 | 97 | assert(vec.shape == I.shape) 98 | assert(vec.lookup == I.lookup) 99 | 100 | vec2 = sim.empty_array(part2) 101 | assert(vec2.shape == I[:, part2].shape) 102 | assert(vec2.lookup == I[:, part2].lookup) 103 | 104 | if __name__ == "__main__": 105 | test_pickling_references() 106 | test_empty_array() 107 | -------------------------------------------------------------------------------- /test/time_sphere.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Thu Feb 26 09:36:46 2015 4 | 5 | @author: dap124 6 | """ 7 | 8 | from __future__ import print_function 9 | 10 | import os.path as osp 11 | 12 | import numpy as np 13 | 14 | import openmodes 15 | from openmodes.basis import DivRwgBasis 16 | from openmodes.operator import MfieOperator, EfieOperator 17 | 18 | import logging 19 | logging.getLogger().setLevel(logging.INFO) 20 | 21 | tests_location = osp.split(__file__)[0] 22 | mesh_dir = osp.join(tests_location, 'input', 'test_sphere') 23 | reference_dir = osp.join(tests_location, 'reference', 'test_sphere') 24 | 25 | def test_extinction_all(plot_extinction=False, skip_asserts=False, 26 | write_reference=False): 27 | "Extinction of a PEC sphere with EFIE, MFIE, CFIE" 28 | 29 | tests = (("EFIE", EfieOperator, 'extinction_efie.npy'), 30 | ("MFIE", MfieOperator, 'extinction_mfie.npy'), 31 | ) 32 | 33 | for operator_name, operator_class, reference_filename in tests: 34 | 35 | sim = openmodes.Simulation(name='horseshoe_extinction', 36 | basis_class=DivRwgBasis, 37 | operator_class=operator_class) 38 | 39 | sphere = sim.load_mesh(osp.join(mesh_dir, 'sphere.msh')) 40 | sim.place_part(sphere) 41 | 42 | s = 2j*np.pi*2e9 43 | Z = sim.impedance(s) 44 | 45 | if __name__ == "__main__": 46 | test_extinction_all(plot_extinction=True, skip_asserts=True) 47 | --------------------------------------------------------------------------------