We import all required packages for plotting, etc., and pypmj
. Set the path to your configuration file, if you have one, or import jcmwave using the import_jcmwave
-method of pypmj
.
In [ ]:
import numpy as np
import sys
sys.path.append('..')
import os
# os.environ['PYPMJ_CONFIG_FILE'] = '' # <- path to your configuration file
import pypmj as jpy
# jpy.import_jcmwave('') <- manual import if you do not have a configuration file
jpy.load_extension('antenna')
TODO
We will use the mie2D_rot
project shipped with pypmj. Make sure to specify a valid path to this project here.
In [ ]:
project = jpy.JCMProject('../projects/scattering/mie/mie2D_rot')
For showcase, we initialize a single simulation which uses our project. This project does not require any keys, so we provide an empty dictionary.
In [ ]:
sim = jpy.Simulation(keys={}, project=project)
The project represents a simple Mie scatterer, which can be treated as an antenna. To evaluate the directivity and the far field power, we initialize a FarFieldEvaluation
-instance. In the default configuration, we only need to pass our simulation instance. You can further control the resolution, the direction and the dimensionality of the problem in the constructor (type jpy.FarFieldEvaluation?
into a cell for more info).
In [ ]:
ffe = jpy.FarFieldEvaluation(simulation=sim)
In case you configured multiple ressources, weinitialize a custom ResourceManager
to solve the simulation on the local machine.
In [ ]:
rm = jpy.ResourceManager()
rm.use_only_resources('localhost')
We will now analyze the far field characteristics using the analyze_far_field
. The far field evaluator takes care that the simulation is solved before the evaluation. If it is not yet solved, we can provide any keyword we want to the Simulation.solve_standalone
-method in the next call. We demonstarte this by unsing our own resource manager. If the simulation is already solved, these keywords have no effect and the far field is directly evaluated.
In [ ]:
ffe.analyze_far_field(resource_manager=rm)
The FarFieldEvaluation
-instance now has new attributes that describe the antenna characteristics. These are in the default case:
NA
power
directivity
total_power
The first three of the are dictionaries with keys 'up'
and 'down'
, to distinguish the up- and down-directions.
Note: Depending on the direction
parameter, some of these attributes may not be present.
In [ ]:
print ffe.NA.keys()
print ffe.power.keys()
print ffe.directivity.keys()
print ffe.total_power
You can save the attributes which were generated in the evaluation process into a file object easily.
In [ ]:
save_file = os.path.join(sim.working_dir(), 'saved_far_field')
ffe.save_far_field_data(save_file)
The simulation working directory now contains an .npz file.
In [ ]:
os.listdir(sim.working_dir())
To demonstrate loading, we initialize a new, empty far field evaluator and call the load_far_field_data
method with the path to the save file.
In [ ]:
ffe2 = jpy.FarFieldEvaluation()
ffe2.load_far_field_data(save_file)
print ffe2.NA.keys()
print ffe2.power.keys()
print ffe2.directivity.keys()
print ffe2.total_power
We need some additional packages for plotting
In [ ]:
%matplotlib notebook
import matplotlib
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
import seaborn as sns
sns.set_style("whitegrid")
We plot the calculated directivity as a surface plot.
In [ ]:
# Load prefered colormap from the pypmj configuration
cmap =jpy._config.get('Preferences', 'colormap')
# Plot
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
ax.plot_surface(ffe.directivity['up'][0].real,
ffe.directivity['up'][1].real,
ffe.directivity['up'][2].real,
rstride=1, cstride=1, cmap=cmap)
ax.plot_surface(ffe.directivity['down'][0].real,
ffe.directivity['down'][1].real,
ffe.directivity['down'][2].real,
rstride=1, cstride=1, cmap=cmap)
dist = 4
ax.set_xlim([-dist*.6,dist*.6])
ax.set_ylim([-dist*.6,dist*.6])
ax.set_zlim([-dist*.6,dist*.6])
plt.show()
We can further plot the power that is scattered to the upper and lower half-space and the sum of both powers as a function of the numerical aperture. Note that for an NA of 1.0, 100% of the power is collected if we look at the sum. just as it is expected.
In [ ]:
power_up = ffe.power['up']/ffe.total_power*100
power_down = ffe.power['down']/ffe.total_power*100
power_total = power_up + power_down[::-1]
plt.figure()
plt.plot(ffe.NA['up'], power_up, label='Up')
plt.plot(ffe.NA['down'], power_down, label='Down')
plt.plot(ffe.NA['up'], power_total, label='Sum')
plt.xlabel('NA')
plt.ylabel('Collection Efficiency (%)')
plt.legend(loc='best')
plt.show()