Imports and configuration

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')

What this extension is for

TODO

Usage

Project and simulation setup

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)

Using the FarFieldEvaluation class

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')

Analyzing data

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:

  • numerical aperture: NA
  • far field power: power
  • directivity: directivity
  • total power: 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

Saving and loading

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

Plotting results

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()