diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml
new file mode 100644
index 00000000..dcedc841
--- /dev/null
+++ b/.github/FUNDING.yml
@@ -0,0 +1,3 @@
+# These are supported funding model platforms
+
+custom: ['https://crowdfundraising.ubc.ca/projects/ubc-thunderbots/']
diff --git a/.gitignore b/.gitignore
index 0c328630..89bc960d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -68,3 +68,229 @@ vhdl/thunderbots_vhdl.prj
vhdl/work-obj93.cf
vhdl/xlnx_auto_0_xdb
vhdl/xst
+
+
+# Created by https://www.gitignore.io/api/venv,pycharm
+# Edit at https://www.gitignore.io/?templates=venv,pycharm
+
+### PyCharm ###
+# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm
+# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
+
+# User-specific stuff
+.idea/**/workspace.xml
+.idea/**/tasks.xml
+.idea/**/usage.statistics.xml
+.idea/**/dictionaries
+.idea/**/shelf
+
+# Generated files
+.idea/**/contentModel.xml
+
+# Sensitive or high-churn files
+.idea/**/dataSources/
+.idea/**/dataSources.ids
+.idea/**/dataSources.local.xml
+.idea/**/sqlDataSources.xml
+.idea/**/dynamic.xml
+.idea/**/uiDesigner.xml
+.idea/**/dbnavigator.xml
+
+# Gradle
+.idea/**/gradle.xml
+.idea/**/libraries
+
+# Gradle and Maven with auto-import
+# When using Gradle or Maven with auto-import, you should exclude module files,
+# since they will be recreated, and may cause churn. Uncomment if using
+# auto-import.
+# .idea/modules.xml
+# .idea/*.iml
+# .idea/modules
+# *.iml
+# *.ipr
+
+# CMake
+cmake-build-*/
+
+# Mongo Explorer plugin
+.idea/**/mongoSettings.xml
+
+# File-based project format
+*.iws
+
+# IntelliJ
+out/
+
+# mpeltonen/sbt-idea plugin
+.idea_modules/
+
+# JIRA plugin
+atlassian-ide-plugin.xml
+
+# Cursive Clojure plugin
+.idea/replstate.xml
+
+# Crashlytics plugin (for Android Studio and IntelliJ)
+com_crashlytics_export_strings.xml
+crashlytics.properties
+crashlytics-build.properties
+fabric.properties
+
+# Editor-based Rest Client
+.idea/httpRequests
+
+# Android studio 3.1+ serialized cache file
+.idea/caches/build_file_checksums.ser
+
+### PyCharm Patch ###
+# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721
+
+# *.iml
+# modules.xml
+# .idea/misc.xml
+# *.ipr
+
+# Sonarlint plugin
+.idea/**/sonarlint/
+
+# SonarQube Plugin
+.idea/**/sonarIssues.xml
+
+# Markdown Navigator plugin
+.idea/**/markdown-navigator.xml
+.idea/**/markdown-navigator/
+
+
+# Created by https://www.gitignore.io/api/python
+# Edit at https://www.gitignore.io/?templates=python
+
+### Python ###
+# Byte-compiled / optimized / DLL files
+__pycache__/
+*.py[cod]
+*$py.class
+
+# C extensions
+*.so
+
+# Distribution / packaging
+.Python
+build/
+develop-eggs/
+dist/
+downloads/
+eggs/
+.eggs/
+lib/
+lib64/
+parts/
+sdist/
+var/
+wheels/
+pip-wheel-metadata/
+share/python-wheels/
+*.egg-info/
+.installed.cfg
+*.egg
+MANIFEST
+
+# PyInstaller
+# Usually these files are written by a python script from a template
+# before PyInstaller builds the exe, so as to inject date/other infos into it.
+*.manifest
+*.spec
+
+# Installer logs
+pip-log.txt
+pip-delete-this-directory.txt
+
+# Unit test / coverage reports
+htmlcov/
+.tox/
+.nox/
+.coverage
+.coverage.*
+.cache
+nosetests.xml
+coverage.xml
+*.cover
+.hypothesis/
+.pytest_cache/
+
+# Translations
+*.mo
+*.pot
+
+# Scrapy stuff:
+.scrapy
+
+# Sphinx documentation
+docs/_build/
+
+# PyBuilder
+target/
+
+# pyenv
+.python-version
+
+# pipenv
+# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
+# However, in case of collaboration, if having platform-specific dependencies or dependencies
+# having no cross-platform support, pipenv may install dependencies that don't work, or not
+# install all needed dependencies.
+#Pipfile.lock
+
+# celery beat schedule file
+celerybeat-schedule
+
+# SageMath parsed files
+*.sage.py
+
+# Spyder project settings
+.spyderproject
+.spyproject
+
+# Rope project settings
+.ropeproject
+
+# Mr Developer
+.mr.developer.cfg
+.project
+.pydevproject
+
+# mkdocs documentation
+/site
+
+# mypy
+.mypy_cache/
+.dmypy.json
+dmypy.json
+
+# Pyre type checker
+.pyre/
+
+### venv ###
+# Virtualenv
+# http://iamzed.com/2009/05/07/a-primer-on-virtualenv/
+.Python
+[Bb]in
+[Ii]nclude
+[Ll]ib
+[Ll]ib64
+[Ll]ocal
+[Ss]cripts
+pyvenv.cfg
+.env
+.venv
+env/
+venv/
+ENV/
+env.bak/
+venv.bak/
+pip-selfcheck.json
+
+# End of https://www.gitignore.io/api/venv,pycharm
+
+# Mergetool files
+*.orig
diff --git a/.idea/Electrical.iml b/.idea/Electrical.iml
new file mode 100644
index 00000000..c98d1518
--- /dev/null
+++ b/.idea/Electrical.iml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml
new file mode 100644
index 00000000..105ce2da
--- /dev/null
+++ b/.idea/inspectionProfiles/profiles_settings.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
new file mode 100644
index 00000000..eb9a4e16
--- /dev/null
+++ b/.idea/misc.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
new file mode 100644
index 00000000..f226b1f2
--- /dev/null
+++ b/.idea/modules.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 00000000..94a25f7f
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/models/MagWire/.idea/MagWire.iml b/models/MagWire/.idea/MagWire.iml
new file mode 100644
index 00000000..85c7612b
--- /dev/null
+++ b/models/MagWire/.idea/MagWire.iml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/models/MagWire/.idea/inspectionProfiles/profiles_settings.xml b/models/MagWire/.idea/inspectionProfiles/profiles_settings.xml
new file mode 100644
index 00000000..105ce2da
--- /dev/null
+++ b/models/MagWire/.idea/inspectionProfiles/profiles_settings.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/models/MagWire/.idea/misc.xml b/models/MagWire/.idea/misc.xml
new file mode 100644
index 00000000..3d64936d
--- /dev/null
+++ b/models/MagWire/.idea/misc.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/models/MagWire/.idea/modules.xml b/models/MagWire/.idea/modules.xml
new file mode 100644
index 00000000..194e2f29
--- /dev/null
+++ b/models/MagWire/.idea/modules.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/models/MagWire/.idea/vcs.xml b/models/MagWire/.idea/vcs.xml
new file mode 100644
index 00000000..b2bdec2d
--- /dev/null
+++ b/models/MagWire/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/models/MagWire/.idea/workspace.xml b/models/MagWire/.idea/workspace.xml
new file mode 100644
index 00000000..a12dc72b
--- /dev/null
+++ b/models/MagWire/.idea/workspace.xml
@@ -0,0 +1,114 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 1581798536045
+
+
+ 1581798536045
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/models/MagWire/biotsavart.py b/models/MagWire/biotsavart.py
new file mode 100644
index 00000000..561be496
--- /dev/null
+++ b/models/MagWire/biotsavart.py
@@ -0,0 +1,110 @@
+__author__ = 'wack'
+
+# part of the magwire package
+
+# calculate magnetic fields arising from electrical current through wires of arbitrary shape
+# with the law of Biot-Savart
+
+# written by Michael Wack
+# wack@geophysik.uni-muenchen.de
+
+# tested with python 3.4.3
+
+
+import numpy as np
+import time
+#import multiprocessing as mp
+from matplotlib import pyplot as plt
+import mpl_toolkits.mplot3d.axes3d as ax3d
+
+class BiotSavart:
+ '''
+ calculates the magnetic field generated by currents flowing through wires
+ '''
+
+ def __init__(self, wire=None):
+ self.wires = []
+ if wire is not None:
+ self.wires.append(wire)
+
+ def AddWire(self, wire):
+ self.wires.append(wire)
+
+ def CalculateB(self, points):
+ """
+ calculate magnetic field B at given points
+ :param points: numpy array of n points (xyz)
+ :return: numpy array of n vectors representing the B field at given points
+ """
+
+ print("found {} wire(s).".format(len(self.wires)))
+ c = 0
+ # generate list of IdL and r1 vectors from all wires
+ for w in self.wires:
+ c += 1
+ _IdL, _r1 = w.IdL_r1
+ print("wire {} has {} segments".format(c, len(_IdL)))
+ if c == 1:
+ IdL = _IdL
+ r1 = _r1
+ else:
+ IdL = np.vstack((IdL, _IdL))
+ r1 = np.vstack((r1, _r1))
+ print("total number of segments: {}".format(len(IdL)))
+ print("number of field points: {}".format(len(points)))
+ print("total number of calculations: {}".format(len(points)*len(IdL)))
+
+ # now we have
+ # all segment vectors multiplied by the flowing current in IdL
+ # and all vectors to the central points of the segments in r1
+
+ # calculate vector B*1e7 for each point in space
+ t1 = time.process_time()
+ # simple list comprehension to calculate B at each point r
+ B = np.array([BiotSavart._CalculateB1(r, IdL, r1) * 1e-7 for r in points])
+
+ # multi processing
+ # slower than single processing?
+ #pool = mp.Pool(processes=16)
+ #B = np.array([pool.apply(_CalculateB1, args=(r, IdL, r1)) for r in points])
+
+ t2 = time.process_time()
+ print("time needed for calculation: {} s".format(t2-t1))
+
+ return B
+
+ def vv_PlotWires(self):
+ for w in self.wires:
+ w.vv_plot_path()
+
+ def mpl3d_PlotWires(self, ax):
+ for w in self.wires:
+ w.mpl3d_plot_path(show=False, ax=ax)
+
+
+
+
+ @staticmethod
+ def _CalculateB1(r, IdL, r1):
+ '''
+ calculate magnetic field B for one point r in space
+ :param r: 3 component numpy array representing the location where B will be calculated
+ :param IdL: all segment vectors multiplied by the flowing current
+ :param r1: all vectors to the central points of the segments
+ :return: numpy array of 3 component vector of B multiplied by 1e7
+ '''
+
+ # calculate law of biot savart for all current elements at given point r
+ r2 = r - r1
+ r25 = np.linalg.norm(r2, axis=1)**3
+ r3 = r2 / r25[:, np.newaxis]
+
+ cr = np.cross(IdL, r3)
+
+ # claculate sum of contributions from all current elements
+ s = np.sum(cr, axis=0)
+
+ return s
+
+
+
diff --git a/models/MagWire/magwire.py b/models/MagWire/magwire.py
new file mode 100644
index 00000000..8d78b4d3
--- /dev/null
+++ b/models/MagWire/magwire.py
@@ -0,0 +1,63 @@
+__author__ = 'wack'
+
+# part of the magwire package
+
+# calculate magnetic fields arising from electrical current through wires of arbitrary shape
+# with the law of Biot-Savart
+
+# written by Michael Wack 2015
+# wack@geophysik.uni-muenchen.de
+
+# tested with python 3.4.3
+
+# some basic calculations for testing
+
+
+import numpy as np
+
+import visvis as vv
+
+import wire
+import biotsavart
+
+
+# simple solenoid
+w = wire.Wire(path=wire.Wire.SolenoidPath(), discretization_length=0.01, current=100).Translate((0.1, 0.1, 0)).Rotate(axis=(1, 0, 0), deg=45)
+sol = biotsavart.BiotSavart(wire=w)
+
+resolution = 0.02
+volume_corner1 = (-.2, -.3, -.2)
+volume_corner2 = (.3, .3, .4)
+
+grid = np.mgrid[volume_corner1[0]:volume_corner2[0]:resolution, volume_corner1[1]:volume_corner2[1]:resolution, volume_corner1[2]:volume_corner2[2]:resolution]
+
+# create list of grid points
+points = np.vstack(map(np.ravel, grid)).T
+
+# calculate B field at given points
+B = sol.CalculateB(points=points)
+
+Babs = np.linalg.norm(B, axis=1)
+
+# draw results
+
+# prepare axes
+a = vv.gca()
+a.cameraType = '3d'
+a.daspectAuto = False
+
+vol = Babs.reshape(grid.shape[1:]).T
+vol = np.clip(vol, 0.002, 0.01)
+vol = vv.Aarray(vol, sampling=(resolution, resolution, resolution), origin=(volume_corner1[2], volume_corner1[1], volume_corner1[0]))
+
+# set labels
+vv.xlabel('x axis')
+vv.ylabel('y axis')
+vv.zlabel('z axis')
+
+sol.vv_PlotWires()
+
+t = vv.volshow2(vol, renderStyle='mip', cm=vv.CM_JET)
+vv.colorbar()
+app = vv.use()
+app.Run()
diff --git a/models/MagWire/palint_oven.py b/models/MagWire/palint_oven.py
new file mode 100644
index 00000000..5d3db58c
--- /dev/null
+++ b/models/MagWire/palint_oven.py
@@ -0,0 +1,81 @@
+__author__ = 'wack'
+
+# part of the magwire package
+
+# calculate magnetic fields arising from electrical current through wires of arbitrary shape
+# with the law of Biot-Savart
+
+# written by Michael Wack 2015
+# wack@geophysik.uni-muenchen.de
+
+# tested with python 3.4.3
+
+# calculate fields needed for an palint oven
+
+import numpy as np
+import matplotlib.pyplot as plt
+from copy import deepcopy
+import wire
+import biotsavart
+
+# two solenoids
+
+w1 = wire.Wire(path=wire.Wire.SolenoidPath(radius=0.1, pitch=0.02, turns=20), discretization_length=0.01, current=10).Rotate(axis=(0, 1, 0), deg=90)
+w2 = wire.Wire(path=wire.Wire.SolenoidPath(radius=0.1, pitch=0.02, turns=30), discretization_length=0.01, current=20).Rotate(axis=(0, 1, 0), deg=90).Translate([.45,0,0])
+
+
+sol = biotsavart.BiotSavart(wire=w1)
+sol.AddWire(w2)
+
+resolution = 0.01
+xy_corner1 = (-.2, -.09)
+xy_corner2 = (.8+1e-10, .09)
+
+# matplotlib plot 2D
+# create list of xy coordinates
+grid = np.mgrid[xy_corner1[0]:xy_corner2[0]:resolution, xy_corner1[1]:xy_corner2[1]:resolution]
+
+# create list of grid points
+points = np.vstack(map(np.ravel, grid)).T
+points = np.hstack([points, np.zeros([len(points),1])])
+
+# calculate B field at given points
+B = sol.CalculateB(points=points)
+Babs = np.linalg.norm(B, axis=1)
+
+# remove big values close to the wire
+#cutoff = 0.005
+
+#B[Babs > cutoff] = [np.nan,np.nan,np.nan]
+#Babs[Babs > cutoff] = np.nan
+
+
+
+#2d quiver
+# get 2D values from one plane with Z = 0
+
+fig = plt.figure()
+ax = fig.gca()
+ax.quiver(points[:, 0], points[:, 1], B[:, 0], B[:, 1], scale=.15)
+X = np.unique(points[:, 0])
+Y = np.unique(points[:, 1])
+cs = ax.contour(X, Y, Babs.reshape([len(X), len(Y)]).T, 10)
+ax.clabel(cs)
+plt.xlabel('x')
+plt.ylabel('y')
+plt.axis('equal')
+plt.show()
+
+
+x_vals = np.arange(xy_corner1[0],xy_corner2[0],resolution)
+points = np.array( [x_vals, x_vals*0, x_vals*0]).T
+print(points)
+B = sol.CalculateB(points=points)
+Babs = np.linalg.norm(B, axis=1)
+
+fig = plt.figure()
+plt.plot(points[:,0], Babs)
+plt.xlabel('x[m]')
+plt.ylabel('B[T]')
+plt.show()
+
diff --git a/models/MagWire/sinusoidal_loop_demag.py b/models/MagWire/sinusoidal_loop_demag.py
new file mode 100644
index 00000000..fa985b5d
--- /dev/null
+++ b/models/MagWire/sinusoidal_loop_demag.py
@@ -0,0 +1,113 @@
+__author__ = 'wack'
+
+
+# part of the magwire package
+
+# calculate magnetic fields arising from electrical current through wires of arbitrary shape
+# with the law of Biot-Savart
+
+# written by Michael Wack 2015
+# wack@geophysik.uni-muenchen.de
+
+# tested with python 3.4.3
+
+
+# calculate magnetic field caused by two sinusoidal loops with opposing current directions
+
+import numpy as np
+
+try:
+ import visvis as vv
+ visvis_avail = True
+except ImportError:
+ visvis_avail = False
+ print("visvis not found.")
+
+from matplotlib import pyplot as plt
+from mpl_toolkits.mplot3d import axes3d
+
+import wire
+import biotsavart
+
+
+npts = 200
+radius = .07
+amp = .01
+ncycle = 12.0
+
+
+# define wires
+w1 = wire.Wire(path=wire.Wire.SinusoidalCircularPath(radius=radius, amplitude=amp, frequency=ncycle, pts=npts), discretization_length=0.01, current=100.0)
+w2 = wire.Wire(path=wire.Wire.SinusoidalCircularPath(radius=radius, amplitude=amp, frequency=ncycle, pts=npts), discretization_length=0.01, current=-100.0).Rotate(axis=(0, 0, 1), deg=360.0/ncycle/2.0)
+
+# prepare data grid and calculate B in volume
+resolution = 0.005
+# volume to examine
+volume_corner1 = (0, 0, 0)
+volume_corner2 = (.12, .12, .05)
+
+grid3D = np.mgrid[volume_corner1[0]:volume_corner2[0]:resolution, volume_corner1[1]:volume_corner2[1]:resolution, volume_corner1[2]:volume_corner2[2]:resolution]
+grid2D = np.mgrid[volume_corner1[0]:volume_corner2[0]:resolution/2.0, volume_corner1[1]:volume_corner2[1]:resolution/2.0, 0:1]
+
+# create list of grid points
+points3D = np.vstack(map(np.ravel, grid3D)).T
+points2D = np.vstack(map(np.ravel, grid2D)).T
+
+# calculate B field at given points
+bs = biotsavart.BiotSavart(wire=w1)
+bs.AddWire(w2)
+
+B = bs.CalculateB(points=points3D)
+Babs = np.linalg.norm(B, axis=1)
+
+B2D = bs.CalculateB(points=points2D)
+B2Dabs = np.linalg.norm(B2D, axis=1)
+B2Dabs = B2Dabs.clip(0,0.01)
+B2Dabsgrid = B2Dabs.reshape(grid2D.shape[1:-1]).T
+
+
+# draw results
+
+# prepare axes
+a = vv.gca()
+a.cameraType = '3d'
+a.daspectAuto = False
+
+#Bgrid = B.reshape(grid3D.shape).T
+Babsgrid = Babs.reshape(grid3D.shape[1:]).T
+
+# clipping is not automatic!
+Babsgrid = np.clip(Babsgrid, 0, 0.005)
+Babsgrid = vv.Aarray(Babsgrid, sampling=(resolution, resolution, resolution),
+ origin=(volume_corner1[2], volume_corner1[1], volume_corner1[0]))
+
+
+# set labels
+vv.xlabel('x axis')
+vv.ylabel('y axis')
+vv.zlabel('z axis')
+
+bs.vv_PlotWires()
+
+t = vv.volshow2(Babsgrid, renderStyle='mip', cm=vv.CM_JET)
+vv.colorbar()
+app = vv.use()
+app.Run()
+
+
+# matplotlib plot
+
+fig = plt.figure()
+
+# 3d quiver
+
+#ax = fig.gca(projection='3d')
+#ax.quiver(points[:, 0], points[:, 1], points[:, 2], B[:, 0], B[:, 1], B[:, 2], length=0.005)
+
+#2d quiver
+ax = fig.gca()
+ax.quiver(points2D[:, 0], points2D[:, 1], B2D[:, 0], B2D[:, 1], scale=.3)
+cs = ax.contour(grid2D.reshape(grid2D.shape[:-1])[0], grid2D.reshape(grid2D.shape[:-1])[1], B2Dabsgrid)
+ax.clabel(cs)
+
+plt.show()
diff --git a/models/MagWire/solenoid_demo.py b/models/MagWire/solenoid_demo.py
new file mode 100644
index 00000000..174e819e
--- /dev/null
+++ b/models/MagWire/solenoid_demo.py
@@ -0,0 +1,91 @@
+__author__ = 'wack'
+
+# part of the magwire package
+
+# calculate magnetic fields arising from electrical current through wires of arbitrary shape
+# with the law of Biot-Savart
+
+# written by Michael Wack 2015
+# wack@geophysik.uni-muenchen.de
+
+# tested with python 3.4.3
+
+# some basic calculations for testing
+
+import numpy as np
+import matplotlib.pyplot as plt
+import wire
+import biotsavart
+
+
+# simple solenoid
+# approximated analytical solution: B = mu0 * I * n / l = 4*pi*1e-7[T*m/A] * 100[A] * 10 / 0.5[m] = 2.5mT
+
+
+w = wire.Wire(path=wire.Wire.SolenoidPath(pitch=0.05, turns=10), discretization_length=0.01, current=100).Rotate(axis=(1, 0, 0), deg=90) #.Translate((0.1, 0.1, 0)).
+sol = biotsavart.BiotSavart(wire=w)
+
+resolution = 0.04
+volume_corner1 = (-.2, -.8, -.2)
+volume_corner2 = (.2+1e-10, .3, .2)
+
+# matplotlib plot 2D
+# create list of xy coordinates
+grid = np.mgrid[volume_corner1[0]:volume_corner2[0]:resolution, volume_corner1[1]:volume_corner2[1]:resolution]
+
+# create list of grid points
+points = np.vstack(map(np.ravel, grid)).T
+points = np.hstack([points, np.zeros([len(points),1])])
+
+# calculate B field at given points
+B = sol.CalculateB(points=points)
+
+
+Babs = np.linalg.norm(B, axis=1)
+
+# remove big values close to the wire
+cutoff = 0.005
+
+B[Babs > cutoff] = [np.nan,np.nan,np.nan]
+#Babs[Babs > cutoff] = np.nan
+
+for ba in B:
+ print(ba)
+
+#2d quiver
+# get 2D values from one plane with Z = 0
+
+fig = plt.figure()
+ax = fig.gca()
+ax.quiver(points[:, 0], points[:, 1], B[:, 0], B[:, 1], scale=.15)
+X = np.unique(points[:, 0])
+Y = np.unique(points[:, 1])
+cs = ax.contour(X, Y, Babs.reshape([len(X), len(Y)]).T, 10)
+ax.clabel(cs)
+plt.xlabel('x')
+plt.ylabel('y')
+plt.axis('equal')
+plt.show()
+
+
+# matplotlib plot 3D
+
+grid = np.mgrid[volume_corner1[0]:volume_corner2[0]:resolution*2, volume_corner1[1]:volume_corner2[1]:resolution*2, volume_corner1[2]:volume_corner2[2]:resolution*2]
+
+# create list of grid points
+points = np.vstack(map(np.ravel, grid)).T
+
+# calculate B field at given points
+B = sol.CalculateB(points=points)
+
+Babs = np.linalg.norm(B, axis=1)
+
+fig = plt.figure()
+# 3d quiver
+ax = fig.gca(projection='3d')
+sol.mpl3d_PlotWires(ax)
+ax.quiver(points[:, 0], points[:, 1], points[:, 2], B[:, 0], B[:, 1], B[:, 2], length=0.04)
+plt.show()
+
+
+
diff --git a/models/MagWire/wire.py b/models/MagWire/wire.py
new file mode 100644
index 00000000..c0d6a569
--- /dev/null
+++ b/models/MagWire/wire.py
@@ -0,0 +1,205 @@
+__author__ = 'wack'
+
+# part of the magwire package
+
+# calculate magnetic fields arising from electrical current through wires of arbitrary shape
+# with the law of Biot-Savart
+
+# written by Michael Wack
+# wack@geophysik.uni-muenchen.de
+
+# tested with python 3.4.3
+
+from copy import deepcopy
+import numpy as np
+try:
+ import visvis as vv
+ visvis_avail = True
+except ImportError:
+ visvis_avail = False
+ print("visvis not found.")
+
+
+class Wire:
+ '''
+ represents an arbitrary 3D wire geometry
+ '''
+ def __init__(self, current=1, path=None, discretization_length=0.01):
+ '''
+
+ :param current: electrical current in Ampere used for field calculations
+ :param path: geometry of the wire specified as path of n 3D (x,y,z) points in a numpy array with dimension n x 3
+ length unit is meter
+ :param discretization_length: lenght of dL after discretization
+ '''
+ self.current = current
+ self.path = path
+ self.discretization_length = discretization_length
+
+
+ @property
+ def discretized_path(self):
+ '''
+ calculate end points of segments of discretized path
+ approximate discretization lenghth is given by self.discretization_length
+ elements will never be combined
+ elements longer that self.dicretization_length will be divided into pieces
+ :return: discretized path as m x 3 numpy array
+ '''
+
+ try:
+ return self.dpath
+ except AttributeError:
+ pass
+
+ self.dpath = deepcopy(self.path)
+ for c in range(len(self.dpath)-2, -1, -1):
+ # go backwards through all elements
+ # length of element
+ element = self.dpath[c+1]-self.dpath[c]
+ el_len = np.linalg.norm(element)
+ npts = int(np.ceil(el_len / self.discretization_length)) # number of parts that this element should be split up into
+ if npts > 1:
+ # element too long -> create points between
+ # length of new sub elements
+ sel = el_len / float(npts)
+ for d in range(npts-1, 0, -1):
+ self.dpath = np.insert(self.dpath, c+1, self.dpath[c] + element / el_len * sel * d, axis=0)
+
+ return self.dpath
+
+ @property
+ def IdL_r1(self):
+ '''
+ calculate discretized path elements dL and their center point r1
+ :return: numpy array with I * dL vectors, numpy array of r1 vectors (center point of element dL)
+ '''
+ npts = len(self.discretized_path)
+ if npts < 2:
+ print("discretized path must have at least two points")
+ return
+
+ IdL = np.array([self.discretized_path[c+1]-self.discretized_path[c] for c in range(npts-1)]) * self.current
+ r1 = np.array([(self.discretized_path[c+1]+self.discretized_path[c])*0.5 for c in range(npts-1)])
+
+ return IdL, r1
+
+
+ def vv_plot_path(self, discretized=True, color='r'):
+ if not visvis_avail:
+ print("plot path works only with visvis module")
+ return
+
+ if discretized:
+ p = self.discretized_path
+ else:
+ p = self.path
+
+ vv.plot(p, ms='x', mc=color, mw='2', ls='-', mew=0)
+
+
+ def mpl3d_plot_path(self, discretized=True, show=True, ax=None, plt_style='-r'):
+
+ if ax is None:
+ fig = plt.figure(None)
+ ax = ax3d.Axes3D(fig)
+
+ if discretized:
+ p = self.discretized_path
+ else:
+ p = self.path
+
+ ax.plot(p[:, 0], p[:, 1], p[:, 2], plt_style)
+ ax.set_xlabel('X')
+ ax.set_ylabel('Y')
+ ax.set_zlabel('Z')
+
+ # make all axes the same
+ #max_a = np.array((p[:, 0], p[:, 1], p[:, 2])).max()
+
+ #ax.set_xlim3d(min(p[:, 0]), max_a)
+ #ax.set_ylim3d(min(p[:, 1]), max_a)
+ #ax.set_zlim3d(min(p[:, 2]), max_a)
+
+
+ if show:
+ plt.show()
+
+ return ax
+
+ def ExtendPath(self, path):
+ '''
+ extends existing path by another one
+ :param path: path to append
+ '''
+ if self.path is None:
+ self.path = path
+ else:
+ # check if last point is identical to avoid zero length segments
+ if self.path[-1] == path[0]:
+ self.path=np.append(self.path, path[1:], axis=1)
+ else:
+ self.path=np.append(self.path, path, axis=1)
+
+ def Translate(self, xyz):
+ '''
+ move the wire in space
+ :param xyz: 3 component vector that describes translation in x,y and z direction
+ '''
+ if self.path is not None:
+ self.path += np.array(xyz)
+
+ return self
+
+ def Rotate(self, axis=(1,0,0), deg=0):
+ '''
+ rotate wire around given axis by deg degrees
+ :param axis: axis of rotation
+ :param deg: angle
+ '''
+ if self.path is not None:
+ n = axis
+ ca = np.cos(np.radians(deg))
+ sa = np.sin(np.radians(deg))
+ R = np.array([[n[0]**2*(1-ca)+ca, n[0]*n[1]*(1-ca)-n[2]*sa, n[0]*n[2]*(1-ca)+n[1]*sa],
+ [n[1]*n[0]*(1-ca)+n[2]*sa, n[1]**2*(1-ca)+ca, n[1]*n[2]*(1-ca)-n[0]*sa],
+ [n[2]*n[0]*(1-ca)-n[1]*sa, n[2]*n[1]*(1-ca)+n[0]*sa, n[2]**2*(1-ca)+ca]])
+ self.path = np.dot(self.path, R.T)
+
+ return self
+
+
+
+ # different standard paths
+ @staticmethod
+ def LinearPath(pt1=(0, 0, 0), pt2=(0, 0, 1)):
+ return np.array([pt1, pt2]).T
+
+ @staticmethod
+ def RectangularPath(dx=0.1, dy=0.2):
+ dx2 = dx/2.0; dy2 = dy/2.0
+ return np.array([[dx2, dy2, 0], [dx2, -dy2, 0], [-dx2, -dy2, 0], [-dx2, dy2, 0], [dx2, dy2, 0]]).T
+
+ @staticmethod
+ def CircularPath(radius=0.1, pts=20):
+ return Wire.EllipticalPath(rx=radius, ry=radius, pts=pts)
+
+ @staticmethod
+ def SinusoidalCircularPath(radius=0.1, amplitude=0.01, frequency=10, pts=100):
+ t = np.linspace(0, 2 * np.pi, pts)
+ return np.array([radius * np.sin(t), radius * np.cos(t), amplitude * np.cos(frequency*t)]).T
+
+ @staticmethod
+ def EllipticalPath(rx=0.1, ry=0.2, pts=20):
+ t = np.linspace(0, 2 * np.pi, pts)
+ return np.array([rx * np.sin(t), ry * np.cos(t), 0]).T
+
+ @staticmethod
+ def SolenoidPath(radius=0.1, pitch=0.01, turns=30, pts_per_turn=20):
+ return Wire.EllipticalSolenoidPath(rx=radius, ry=radius, pitch=pitch, turns=turns, pts_per_turn=pts_per_turn)
+
+ @staticmethod
+ def EllipticalSolenoidPath(rx=0.1, ry=0.2, pitch=0.01, turns=30, pts_per_turn=20):
+ t = np.linspace(0, 2 * np.pi * turns, pts_per_turn * turns)
+ return np.array([rx * np.sin(t), ry * np.cos(t), t / (2 * np.pi) * pitch]).T
+
diff --git a/models/solenoids/.gitignore b/models/solenoids/.gitignore
new file mode 100644
index 00000000..82b191b2
--- /dev/null
+++ b/models/solenoids/.gitignore
@@ -0,0 +1,198 @@
+
+# Created by https://www.gitignore.io/api/python,pycharm
+# Edit at https://www.gitignore.io/?templates=python,pycharm
+
+### PyCharm ###
+# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm
+# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
+
+# User-specific stuff
+.idea/**/workspace.xml
+.idea/**/tasks.xml
+.idea/**/usage.statistics.xml
+.idea/**/dictionaries
+.idea/**/shelf
+
+# Generated files
+.idea/**/contentModel.xml
+
+# Sensitive or high-churn files
+.idea/**/dataSources/
+.idea/**/dataSources.ids
+.idea/**/dataSources.local.xml
+.idea/**/sqlDataSources.xml
+.idea/**/dynamic.xml
+.idea/**/uiDesigner.xml
+.idea/**/dbnavigator.xml
+
+# Gradle
+.idea/**/gradle.xml
+.idea/**/libraries
+
+# Gradle and Maven with auto-import
+# When using Gradle or Maven with auto-import, you should exclude module files,
+# since they will be recreated, and may cause churn. Uncomment if using
+# auto-import.
+# .idea/modules.xml
+# .idea/*.iml
+# .idea/modules
+# *.iml
+# *.ipr
+
+# CMake
+cmake-build-*/
+
+# Mongo Explorer plugin
+.idea/**/mongoSettings.xml
+
+# File-based project format
+*.iws
+
+# IntelliJ
+out/
+
+# mpeltonen/sbt-idea plugin
+.idea_modules/
+
+# JIRA plugin
+atlassian-ide-plugin.xml
+
+# Cursive Clojure plugin
+.idea/replstate.xml
+
+# Crashlytics plugin (for Android Studio and IntelliJ)
+com_crashlytics_export_strings.xml
+crashlytics.properties
+crashlytics-build.properties
+fabric.properties
+
+# Editor-based Rest Client
+.idea/httpRequests
+
+# Android studio 3.1+ serialized cache file
+.idea/caches/build_file_checksums.ser
+
+### PyCharm Patch ###
+# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721
+
+# *.iml
+# modules.xml
+# .idea/misc.xml
+# *.ipr
+
+# Sonarlint plugin
+.idea/**/sonarlint/
+
+# SonarQube Plugin
+.idea/**/sonarIssues.xml
+
+# Markdown Navigator plugin
+.idea/**/markdown-navigator.xml
+.idea/**/markdown-navigator/
+
+### Python ###
+# Byte-compiled / optimized / DLL files
+__pycache__/
+*.py[cod]
+*$py.class
+
+# C extensions
+*.so
+
+# Distribution / packaging
+.Python
+build/
+develop-eggs/
+dist/
+downloads/
+eggs/
+.eggs/
+lib/
+lib64/
+parts/
+sdist/
+var/
+wheels/
+pip-wheel-metadata/
+share/python-wheels/
+*.egg-info/
+.installed.cfg
+*.egg
+MANIFEST
+
+# PyInstaller
+# Usually these files are written by a python script from a template
+# before PyInstaller builds the exe, so as to inject date/other infos into it.
+*.manifest
+*.spec
+
+# Installer logs
+pip-log.txt
+pip-delete-this-directory.txt
+
+# Unit test / coverage reports
+htmlcov/
+.tox/
+.nox/
+.coverage
+.coverage.*
+.cache
+nosetests.xml
+coverage.xml
+*.cover
+.hypothesis/
+.pytest_cache/
+
+# Translations
+*.mo
+*.pot
+
+# Scrapy stuff:
+.scrapy
+
+# Sphinx documentation
+docs/_build/
+
+# PyBuilder
+target/
+
+# pyenv
+.python-version
+
+# pipenv
+# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
+# However, in case of collaboration, if having platform-specific dependencies or dependencies
+# having no cross-platform support, pipenv may install dependencies that don't work, or not
+# install all needed dependencies.
+#Pipfile.lock
+
+# celery beat schedule file
+celerybeat-schedule
+
+# SageMath parsed files
+*.sage.py
+
+# Spyder project settings
+.spyderproject
+.spyproject
+
+# Rope project settings
+.ropeproject
+
+# Mr Developer
+.mr.developer.cfg
+.project
+.pydevproject
+
+# mkdocs documentation
+/site
+
+# mypy
+.mypy_cache/
+.dmypy.json
+dmypy.json
+
+# Pyre type checker
+.pyre/
+
+# End of https://www.gitignore.io/api/python,pycharm
\ No newline at end of file
diff --git a/models/solenoids/.idea/.idea/.idea.iml b/models/solenoids/.idea/.idea/.idea.iml
new file mode 100644
index 00000000..d0876a78
--- /dev/null
+++ b/models/solenoids/.idea/.idea/.idea.iml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/models/solenoids/.idea/.idea/inspectionProfiles/profiles_settings.xml b/models/solenoids/.idea/.idea/inspectionProfiles/profiles_settings.xml
new file mode 100644
index 00000000..105ce2da
--- /dev/null
+++ b/models/solenoids/.idea/.idea/inspectionProfiles/profiles_settings.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/models/solenoids/.idea/.idea/misc.xml b/models/solenoids/.idea/.idea/misc.xml
new file mode 100644
index 00000000..a2e120dc
--- /dev/null
+++ b/models/solenoids/.idea/.idea/misc.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/models/solenoids/.idea/.idea/modules.xml b/models/solenoids/.idea/.idea/modules.xml
new file mode 100644
index 00000000..08f54a6d
--- /dev/null
+++ b/models/solenoids/.idea/.idea/modules.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/models/solenoids/.idea/.idea/workspace.xml b/models/solenoids/.idea/.idea/workspace.xml
new file mode 100644
index 00000000..6b59c0d1
--- /dev/null
+++ b/models/solenoids/.idea/.idea/workspace.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/models/solenoids/.idea/inspectionProfiles/profiles_settings.xml b/models/solenoids/.idea/inspectionProfiles/profiles_settings.xml
new file mode 100644
index 00000000..105ce2da
--- /dev/null
+++ b/models/solenoids/.idea/inspectionProfiles/profiles_settings.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/models/solenoids/.idea/misc.xml b/models/solenoids/.idea/misc.xml
new file mode 100644
index 00000000..a2e120dc
--- /dev/null
+++ b/models/solenoids/.idea/misc.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/models/solenoids/.idea/modules.xml b/models/solenoids/.idea/modules.xml
new file mode 100644
index 00000000..aab61eee
--- /dev/null
+++ b/models/solenoids/.idea/modules.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/models/solenoids/.idea/solenoids.iml b/models/solenoids/.idea/solenoids.iml
new file mode 100644
index 00000000..67116063
--- /dev/null
+++ b/models/solenoids/.idea/solenoids.iml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/models/solenoids/.idea/vcs.xml b/models/solenoids/.idea/vcs.xml
new file mode 100644
index 00000000..b2bdec2d
--- /dev/null
+++ b/models/solenoids/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/models/solenoids/GUIscratch.py b/models/solenoids/GUIscratch.py
new file mode 100644
index 00000000..e5c205f1
--- /dev/null
+++ b/models/solenoids/GUIscratch.py
@@ -0,0 +1,117 @@
+from tkinter import *
+from tkinter import ttk
+from em_force_model import *
+
+class solenoidGUI:
+
+ def __init__(self, master):
+ self.master = master
+ master.title("window title")
+ # geometry: width x height
+ master.geometry('1080x720')
+ self.description_label = Label(master, text="Solenoid Model", font=("Arial Bold", 40)).grid(
+ column=0, row=0)
+
+ # Changeable Parameters
+ self.description_label = Label(master, text="Changeable parameters: geometry", font=("Arial Bold", 20)).grid(
+ column=0, row=1)
+ frame = Frame(master)
+ frame.grid(column=0, row=2)
+
+ self.description_length_solenoid = Label(frame, text="Solenoid Length in mm").grid(column=0, row=1)
+ self.length_solenoid = Entry(frame, width=10)
+ self.length_solenoid.grid(column=1, row=1)
+
+ self.description_gauge_thickness = Label(frame, text="Gauge Thickness in mm").grid(column=0, row=2)
+
+ self.gauge_thickness = ttk.Combobox(frame, values=["12", "13", "14", "15"])
+ self.gauge_thickness.current(0) # set the selected item
+ self.gauge_thickness.grid(column=1, row=2)
+
+ # if circle, square, show
+ self.description_diameter = Label(frame, text="Diameter (square, circular)").grid(column=0, row=3)
+ self.diameter = Entry(frame, width=10)
+ self.diameter.grid(column=1, row=3)
+
+ # if rectangle, oval, show
+ self.description_side_long = Label(frame, text="Length of longer side").grid(column=0, row=4)
+ self.side_long = Entry(frame, width=10)
+ self.side_long.grid(column=1, row=4)
+
+ self.description_side_short = Label(frame, text="Length of shorter side").grid(column=0, row=5)
+ self.side_short = Entry(frame, width=10)
+ self.side_short.grid(column=1, row=5)
+
+ # External Values, changeable, but dependent on other components
+ self.description_label2 = Label(master, text="Parameters: external circuit", font=("Arial Bold", 20)).grid(
+ column=0, row=3)
+ frame2 = Frame(master)
+ frame2.grid(column=0, row=4)
+
+ self.description_voltage = Label(frame2, text="Voltage in V").grid(column=0, row=1)
+ self.voltage = Entry(frame2, width=10)
+ self.voltage.grid(column=1, row=1)
+
+ self.description_resistance = Label(frame2, text="resistance in ohms").grid(column=0, row=2)
+ self.resistance = Entry(frame2, width=10)
+ self.resistance.grid(column=1, row=2)
+
+ self.input_shape = ttk.Combobox(frame, values=["rectangle", "circle", "ellipse", "square"])
+ self.input_shape.current(0) # set the selected item
+ self.input_shape.grid(column=0, row=0)
+
+ # click to solve
+ self.button = Button(master, text='Solve', command=self.calc)
+ self.button.grid(column=0, row=9)
+ self.close_button = Button(master, text="Close", command=master.quit).grid(column=0, row=10)
+ # output
+ self.output_label = Label(master, text="output here")
+ self.output_label.grid(column=0, row=11)
+
+ def calc(self):
+
+ # relating the combobox index to text
+ rectangle = 0
+ circle = 1
+ ellipse = 2
+ square = 3
+
+ n = num_of_loops(float(self.length_solenoid.get()), float(self.gauge_thickness.get()))
+ i = max_current(float(self.voltage.get()), float(self.resistance.get()))
+
+ if self.input_shape.current() == rectangle:
+ area = cross_section_area_rect(float(self.side_short.get()), float(self.side_long.get()))
+ mf = calc_mf_rect_solenoid(n, i, float(self.side_short.get()), float(self.side_long.get()))
+ printed = calc_force(area, mf)
+ self.output_label.configure(text=printed)
+
+ if self.input_shape.current() == circle:
+ area = cross_section_area_circle(float(self.diameter.get()))
+ mf = calc_mf_circular_solenoid(n, i, float(self.diameter.get()))
+ printed = calc_force(area, mf)
+ self.output_label.configure(text=printed)
+
+ '''if self.input_shape.current() == ellipse:
+ area = cross_section_area_ellipse()
+ mf = calc_mf_ellipse_solenoid()
+ printed = calc_force(area, mf)
+ self.output_label.configure(text=printed)'''
+
+ if self.input_shape.current() == square:
+ area = cross_section_area_square(float(self.diameter.get()))
+ mf = calc_mf_square_solenoid(n, i, float(self.diameter.get()))
+ printed = calc_force(area, mf)
+ self.output_label.configure(text=printed)
+
+
+
+if __name__ == '__main__':
+ root = Tk()
+ my_gui = solenoidGUI(root)
+ root.mainloop()
+ # AWG conversions to mm
+ import json
+
+ with open('awg_data.json', 'r') as awg_json:
+ awg_data = json.load(awg_json)
+ print(awg_data['AWG'])
diff --git a/models/solenoids/awg_data.json b/models/solenoids/awg_data.json
new file mode 100644
index 00000000..7eb4a51d
--- /dev/null
+++ b/models/solenoids/awg_data.json
@@ -0,0 +1,55 @@
+{
+ "AWG": {
+ "12": {
+ "mm": "2.05232"
+ },
+ "13": {
+ "mm": "1.8288"
+ },
+ "14": {
+ "mm": "1.62814"
+ },
+ "15": {
+ "mm": "1.45034"
+ },
+ "16": {
+ "mm": "1.29032"
+ },
+ "17": {
+ "mm": "1.15062"
+ },
+ "18": {
+ "mm": "1.02362"
+ },
+ "19": {
+ "mm": "0.91186"
+ },
+ "20": {
+ "mm": "0.8128"
+ },
+ "21": {
+ "mm": "0.7239"
+ },
+ "22": {
+ "mm": "0.64516"
+ },
+ "23": {
+ "mm": "0.57404"
+ },
+ "24": {
+ "mm": "0.51054"
+ },
+ "25": {
+ "mm": "0.45466"
+ },
+ "26": {
+ "mm": "0.40386"
+ },
+ "27": {
+ "mm": "0.36068"
+ },
+ "28": {
+ "mm": "0.32004"
+ }
+ }
+}
\ No newline at end of file
diff --git a/models/solenoids/em_force_model.py b/models/solenoids/em_force_model.py
new file mode 100644
index 00000000..928390ca
--- /dev/null
+++ b/models/solenoids/em_force_model.py
@@ -0,0 +1,144 @@
+# Purpose: Calculating the magnetic field strength at the center of the solenoid using Biot Savart Law
+# This value is then used as an input to determine the electromagnetic force.
+# For more information refer to 'Solenoid Research' (google docs)
+
+# variables declarations used in this program are categorized into ...
+# . values to be determined by design, values to be changed during play, output/calculated values
+from typing import Dict
+import scipy
+from scipy import constants
+import matplotlib.pyplot as plt
+import numpy as np
+
+
+# __all__ = [constants]
+
+
+def input_values():
+ parameter_list = {
+ 'length_solenoid': 10, # length of solenoid
+ 'wire_gauge': 1, # wire gauge
+ 'diameter': 1, # diameter of circular, side length of square
+ 'side_long': 2, # longer side for rect, longer value for ellipse
+ 'side_short': 1, # shorter side for rect, shorter value for ellipse
+
+ # External Values, changeable, but dependent on other components
+
+ 'max_voltage': 4, # the voltage across the capacitor
+ 'resistance': 1, # resistance of the device
+ 'capacitance': 3, # capacitance of our capacitor
+
+ # input values, changes to model (visually)
+
+ 'total_time': 5., # the time frame we wish to look at
+ 'time_interval': 0.2, # ### we want to change length of pulse
+ }
+ return parameter_list
+
+
+def time_constant(resistance, capaciatance):
+ tc = resistance * capaciatance
+ return tc
+
+
+def max_current(max_voltage, resistance):
+ mc = max_voltage / resistance
+ return mc
+
+ # for i in range(0, parameter_list['total_time']):
+ # current_at_t.append(current(max_current, time_elapsed, time_constant))
+ # i+time_elapsed
+
+
+def num_of_loops(solenoid_length, gauge_thickness):
+ n = solenoid_length / gauge_thickness
+ return n
+
+
+# magnetic field (mf) calculations and area calculations
+
+
+def calc_mf_circular_solenoid(num_loops, current, diameter):
+ magnetic_field = scipy.constants.mu_0 * num_loops * current / diameter
+ return magnetic_field
+
+
+def cross_section_area_circle(dia): # area calculation (cross section of solenoid)
+ area = scipy.power((dia / 2), 2) * scipy.pi
+ return area
+
+
+def calc_mf_square_solenoid(num_loops, current, side_length):
+ magnetic_field = np.sqrt(2) * scipy.constants.mu_0 * num_loops * current / (scipy.pi * side_length / 2)
+ return magnetic_field
+
+
+def cross_section_area_square(side_length): # area calculation (cross section of solenoid)
+ area = side_length * side_length
+ return area
+
+
+def calc_mf_rect_solenoid(num_loops, current, length_short, length_long):
+ magnetic_field = (2 * scipy.constants.mu_0 * num_loops * current / scipy.pi) * (
+ length_long / (
+ length_short * np.sqrt(scipy.power(length_long, 2) + scipy.power(length_short, 2))) + length_short /
+ (length_long * np.sqrt(scipy.power(length_long, 2) + scipy.power(length_short, 2))))
+ return magnetic_field
+
+
+def cross_section_area_rect(length_long, length_short): # area calculation (cross section of solenoid)
+ area = length_short * length_long
+ return area
+
+
+def calc_mf_ellipse_solenoid(num_loops, current, length_short, length_long): # ****needs work
+ magnetic_field = 1
+ return magnetic_field
+
+
+def cross_section_area_ellipse(length_long, length_short): # area calculation (cross section of solenoid)
+ # area = 1 * 1
+ return area
+
+
+def calc_force(area, mf):
+ force = area * scipy.power(mf, 2) / 2 / scipy.constants.mu_0
+ return force
+
+
+'''# time_elapsed = np.arange(0., parameter_list['total_time'], parameter_list['time_interval'])
+
+ # plt.plot(time_elapsed, current(max_current, time_elapsed, time_constant), 'b--')
+
+ plt.plot(time_elapsed, calculate_field_strength_at_p(calculate_constant_multiplier(parameter_list['num_coils'],
+ current(max_current,
+ time_elapsed,
+ time_constant),
+ parameter_list[
+ 'length_solenoid'],
+ parameter_list['outer_diameter'],
+ parameter_list[
+ 'inner_diameter']),
+ length_add_distance_from_center,
+ ln_term_a,
+ length_subtract_distance_from_center,
+ ln_term_b),
+ 'b--')
+
+ plt.show()'''
+
+##
+
+# FUNCTIONS TO BE CALLED FROM MAIN FUNCTION
+# THIS WAS DONE TO REDUCE CLUTTER IN THE MAIN FUNCTION AND TO CREATE LESS COMPLEX UNIT TEST CASES FOR THE FUNCTIONS
+
+##
+
+'''def current(max_current, time_elapsed, time_constant):
+ cur = max_current * scipy.power(scipy.e, -time_elapsed / time_constant)
+ return cur
+
+'''
+
+# if __name__ == "__main__":
+# input_values()
\ No newline at end of file
diff --git a/models/solenoids/requirements.txt b/models/solenoids/requirements.txt
new file mode 100644
index 00000000..e9b621ff
--- /dev/null
+++ b/models/solenoids/requirements.txt
@@ -0,0 +1 @@
+numpy==1.17.3
diff --git a/models/solenoids/test_em_force_model.py b/models/solenoids/test_em_force_model.py
new file mode 100644
index 00000000..1c787f94
--- /dev/null
+++ b/models/solenoids/test_em_force_model.py
@@ -0,0 +1,29 @@
+from unittest import TestCase
+from .em_force_model import em_force_model, calculate_remanence
+
+
+class EmForceModelTestCase(TestCase):
+ def __init__(self, *args):
+ super(EmForceModelTestCase, self).__init__(*args)
+
+ def setUp(self):
+ pass
+
+ def tearDown(self):
+ pass
+
+ def test_calculate_remanence_test_case(self):
+ current = 0.1 # Amps
+ length_solenoid = 10 #
+ num_turns = 50 #
+ val = calculate_remanence(
+ current,
+ length_solenoid,
+ num_turns
+ )
+ self.assertNotEqual(val, 1, "Expected not equal")
+
+
+
+
+ def test_
\ No newline at end of file