Kamodo Core Analysis Suite
  • Home
  • About
  • Visualization
  • Vector fields
  • Syntax
  • Kamodofication Tutorial
    • Kamodofication requirements
    • Model Reader Tutorial
      • Minimal Example: one variable
    • Including defaults
    • Final Implementation
    • Combined models
  • Field Integration Techniques
  • RPC
  • API
  • Contributing
  • Additional Resources
Kamodo Core Analysis Suite
  • »
  • Kamodofication Tutorial

Kamodofication Tutorial¶

This tutorial focuses on building a Kamodofied model from scratch. To see the full implementation, skip down to the Final-Implementation.

Kamodofication requirements¶

To Kamodofy models and data representing physical quantities, we need to define a set of functions representing the interpolation of each physical variable having the following properties:

  • A function name and arguments that follows kamodo's Syntax conventions
  • Default arrays for input arguments
  • A meta attribute containing:
    • 'units' - physical units of the values returned by the function
    • 'citation' - How the model or data source should be cited
    • 'equation' - LaTeX representation of this model/data source (if available)
    • 'hidden_args' - A list of function arguments that should not be rendered
  • A data attribute - The array holding the variable (if available)
  • Any docstrings that provide further context

Model Reader Tutorial¶

Model Readers load data from disk (or server) and provide methods for interpolation. We require that for each variable of interest, the model reader should provide at least one interpolation method that satisfies all of the above requirements. Each model reader will:

  1. Open/close files
  2. Manage state variables
  3. Initialize interpolators
  4. Kamodofy interpolators
  5. Register functions

Minimal Example: one variable¶

In [1]:
Copied!
from kamodo import Kamodo, kamodofy, gridify
from scipy.interpolate import RegularGridInterpolator
import numpy as np
import plotly.io as pio
from kamodo import Kamodo, kamodofy, gridify from scipy.interpolate import RegularGridInterpolator import numpy as np import plotly.io as pio
In [2]:
Copied!
class MyModel(Kamodo): 
    def __init__(self, filename, **kwargs):
        # perform any necessary I/O
        print('opening {}'.format(filename))
        self.filename = filename
        self.missing_value = np.NAN
        
        # store any data needed for interpolation
        self.x = np.linspace(1, 4, 11)
        self.y = np.linspace(4, 7, 22)
        self.z = np.linspace(7, 9, 33) 
        
        xx, yy, zz = np.meshgrid(self.x, self.y, self.z, indexing='ij', sparse=True)
        density_data = 2 * xx**3 + 3 * yy**2 - zz
        
        self.interpolator = RegularGridInterpolator((self.x, self.y, self.z), density_data, 
                                                    bounds_error = False,
                                                   fill_value = self.missing_value)


        
        # Prepare model for function registration for the input argument
        super(MyModel, self).__init__(**kwargs) 
        
        # Wrap the interpolator with a nicer function signature
        @kamodofy(units = 'kg*m**-3')
        def interpolator(xvec):
            return self.interpolator(xvec)
        
        self['rho'] = interpolator


model = MyModel('myfile.dat')
model
class MyModel(Kamodo): def __init__(self, filename, **kwargs): # perform any necessary I/O print('opening {}'.format(filename)) self.filename = filename self.missing_value = np.NAN # store any data needed for interpolation self.x = np.linspace(1, 4, 11) self.y = np.linspace(4, 7, 22) self.z = np.linspace(7, 9, 33) xx, yy, zz = np.meshgrid(self.x, self.y, self.z, indexing='ij', sparse=True) density_data = 2 * xx**3 + 3 * yy**2 - zz self.interpolator = RegularGridInterpolator((self.x, self.y, self.z), density_data, bounds_error = False, fill_value = self.missing_value) # Prepare model for function registration for the input argument super(MyModel, self).__init__(**kwargs) # Wrap the interpolator with a nicer function signature @kamodofy(units = 'kg*m**-3') def interpolator(xvec): return self.interpolator(xvec) self['rho'] = interpolator model = MyModel('myfile.dat') model
opening myfile.dat
Out[2]:
\begin{equation}\rho{\left(\vec{x} \right)}[\frac{kg}{m^{3}}] = \lambda{\left(\vec{x} \right)}\end{equation}

we can call the registered function with multiple values, getting nan if out of bounds:

In [3]:
Copied!
model.rho([[2,5,8],
           [0,0,0]])
model.rho([[2,5,8], [0,0,0]])
Out[3]:
array([83.244,    nan])

However, the registered function has no default parameters, so an error will be raised if we do not provide an argument.

In [4]:
Copied!
try:
    model.rho()
except TypeError as m:
    print(m)
try: model.rho() except TypeError as m: print(m)
missing a required argument: 'xvec'

At this point, the end-user of the model cannot generate quick-look graphics:

In [5]:
Copied!
try:
    model.plot('rho')
except TypeError as m:
    print(m)
try: model.plot('rho') except TypeError as m: print(m)
missing a required argument: 'xvec'[]

In order to generate any plots, the user must already know where they can place resolution. For example, they could inspect some of the attributes of the model and guess the size of the domain, then choose points from that space.

In [6]:
Copied!
xx,yy,zz = np.meshgrid(model.x, model.y, model.z)
points = np.column_stack([xx.ravel(),yy.ravel(),zz.ravel()])
randints = np.random.randint(0,len(points), 1000)
xx,yy,zz = np.meshgrid(model.x, model.y, model.z) points = np.column_stack([xx.ravel(),yy.ravel(),zz.ravel()]) randints = np.random.randint(0,len(points), 1000)
In [7]:
Copied!
fig = model.plot(rho = dict(xvec = points[randints] ))
fig = model.plot(rho = dict(xvec = points[randints] ))
In [8]:
Copied!
# pio.write_image(fig, 'images/kamodofied1.svg')
# pio.write_image(fig, 'images/kamodofied1.svg')

kamodofied1

Hopefully, the user doesn't choose points where the solution may be invalid. Next, we'll modify the original function to provide a griddable variable with default parameters.

Including defaults¶

The above example produced a kamodofied model with one variable, but we are unable to produce quick-look graphics, which required the user to inspect the model to guess where interpolation may be valid. Here we show how to include defaults so the user doesn't have to guess.

In [9]:
Copied!
class MyModel(Kamodo): 
    def __init__(self, filename, **kwargs):
        # perform any necessary I/O
        print('opening {}'.format(filename))
        self.filename = filename
        self.missing_value = np.NAN
        
        # store any data needed for interpolation
        self.x = np.linspace(1, 4, 11)
        self.y = np.linspace(4, 7, 22)
        self.z = np.linspace(7, 9, 33) 
        
        xx, yy, zz = np.meshgrid(self.x, self.y, self.z, indexing='ij', sparse=True)
        density_data = 2 * xx**3 + 3 * yy**2 - zz
        
        self.interpolator = RegularGridInterpolator((self.x, self.y, self.z), density_data, 
                                                    bounds_error = False,
                                                   fill_value = self.missing_value)


        
        # Prepare model for function registration for the input argument
        super(MyModel, self).__init__(**kwargs) 
        
        # Wrap the interpolator with a nicer function signature
        @kamodofy(units = 'kg/m**3')
        @gridify(x = self.x, y = self.y, z = self.z) # <--- The only change to the model
        def interpolator(xvec):
            return self.interpolator(xvec)
        
        self['rho'] = interpolator
        

model = MyModel('myfile.dat')
model
class MyModel(Kamodo): def __init__(self, filename, **kwargs): # perform any necessary I/O print('opening {}'.format(filename)) self.filename = filename self.missing_value = np.NAN # store any data needed for interpolation self.x = np.linspace(1, 4, 11) self.y = np.linspace(4, 7, 22) self.z = np.linspace(7, 9, 33) xx, yy, zz = np.meshgrid(self.x, self.y, self.z, indexing='ij', sparse=True) density_data = 2 * xx**3 + 3 * yy**2 - zz self.interpolator = RegularGridInterpolator((self.x, self.y, self.z), density_data, bounds_error = False, fill_value = self.missing_value) # Prepare model for function registration for the input argument super(MyModel, self).__init__(**kwargs) # Wrap the interpolator with a nicer function signature @kamodofy(units = 'kg/m**3') @gridify(x = self.x, y = self.y, z = self.z) # <--- The only change to the model def interpolator(xvec): return self.interpolator(xvec) self['rho'] = interpolator model = MyModel('myfile.dat') model
opening myfile.dat
Out[9]:
\begin{equation}\rho{\left(x,y,z \right)}[\frac{kg}{m^{3}}] = \lambda{\left(x,y,z \right)}\end{equation}

By adding the @gridify line, we have modified the original function to be one that generates gridded data. Moreover, the variable now has default parameters.

In [10]:
Copied!
model.rho().shape
model.rho().shape
Out[10]:
(22, 11, 33)

We can now specify one or more arguments to get a plane mapping of the solution.

In [11]:
Copied!
model.rho(z = 8).shape
model.rho(z = 8).shape
Out[11]:
(22, 11)

But how do we know to choose the plane z=8 for a valid solution? We can use kamodo's function inspection to get the default ranges for each parameter.

In [12]:
Copied!
from kamodo import get_defaults
from kamodo import get_defaults
In [13]:
Copied!
get_defaults(model.rho)['z'].mean()
get_defaults(model.rho)['z'].mean()
Out[13]:
8.0

Final Implementation¶

In the final implementation of our model reader, we include multiple variables with different function signatures. Here, the gridded solutions have suffixes _ijk to emphasize their structure. This allows more flexibility for the end user.

In [14]:
Copied!
class MyModel(Kamodo): 
    def __init__(self, filename, **kwargs):
        # perform any necessary I/O
        print('opening {}'.format(filename))
        self.filename = filename
        self.missing_value = np.NAN
        
        # store any data needed for interpolation
        self.x = np.linspace(1, 4, 11)
        self.y = np.linspace(4, 7, 22)
        self.z = np.linspace(7, 9, 33)        
        xx, yy, zz = np.meshgrid(self.x, self.y, self.z, indexing='ij', sparse=True)
        density_data = 2 * xx**3 + 3 * yy**2 - zz
        pressure_data = xx**2 + yy**2 + zz**2
        
        
        self.variables = dict(rho = dict(units = 'kg/m**3', data = density_data),
                              P = dict(units = 'nPa', data = pressure_data))

        # Prepare model for function registration
        super(MyModel, self).__init__(**kwargs) 
        
        for varname in self.variables:
            units = self.variables[varname]['units']
            self.register_variable(varname, units)
            
    def register_variable(self, varname, units):
        interpolator = self.get_grid_interpolator(varname)
        
        # store the interpolator
        self.variables[varname]['interpolator'] = interpolator

        def interpolate(xvec):  
            return self.variables[varname]['interpolator'](xvec)

        # update docstring for this variable
        interpolate.__doc__ = "A function that returns {} in [{}].".format(varname,units)

        self[varname] = kamodofy(interpolate, 
                           units = units, 
                           citation = "Pembroke et al 2019",
                          data = None)
        self[varname + '_ijk'] = kamodofy(gridify(self[varname], 
                                                  x_i = self.x, 
                                                  y_j = self.y, 
                                                  z_k = self.z, squeeze=False),
                            units = units,
                            citation = "Pembroke et al 2019",
                            data = self.variables[varname]['data'])
        
            
    def get_grid_interpolator(self, varname):
        """create a regulard grid interpolator for this variable"""
        data =  self.variables[varname]['data']

        interpolator = RegularGridInterpolator((self.x, self.y, self.z), data, 
                                                bounds_error = False,
                                               fill_value = self.missing_value)
        return interpolator
            

model = MyModel('myfile.dat')
model
class MyModel(Kamodo): def __init__(self, filename, **kwargs): # perform any necessary I/O print('opening {}'.format(filename)) self.filename = filename self.missing_value = np.NAN # store any data needed for interpolation self.x = np.linspace(1, 4, 11) self.y = np.linspace(4, 7, 22) self.z = np.linspace(7, 9, 33) xx, yy, zz = np.meshgrid(self.x, self.y, self.z, indexing='ij', sparse=True) density_data = 2 * xx**3 + 3 * yy**2 - zz pressure_data = xx**2 + yy**2 + zz**2 self.variables = dict(rho = dict(units = 'kg/m**3', data = density_data), P = dict(units = 'nPa', data = pressure_data)) # Prepare model for function registration super(MyModel, self).__init__(**kwargs) for varname in self.variables: units = self.variables[varname]['units'] self.register_variable(varname, units) def register_variable(self, varname, units): interpolator = self.get_grid_interpolator(varname) # store the interpolator self.variables[varname]['interpolator'] = interpolator def interpolate(xvec): return self.variables[varname]['interpolator'](xvec) # update docstring for this variable interpolate.__doc__ = "A function that returns {} in [{}].".format(varname,units) self[varname] = kamodofy(interpolate, units = units, citation = "Pembroke et al 2019", data = None) self[varname + '_ijk'] = kamodofy(gridify(self[varname], x_i = self.x, y_j = self.y, z_k = self.z, squeeze=False), units = units, citation = "Pembroke et al 2019", data = self.variables[varname]['data']) def get_grid_interpolator(self, varname): """create a regulard grid interpolator for this variable""" data = self.variables[varname]['data'] interpolator = RegularGridInterpolator((self.x, self.y, self.z), data, bounds_error = False, fill_value = self.missing_value) return interpolator model = MyModel('myfile.dat') model
opening myfile.dat
Out[14]:
\begin{equation}\rho{\left(\vec{x} \right)}[\frac{kg}{m^{3}}] = \lambda{\left(\vec{x} \right)}\end{equation} \begin{equation}\rho_{ijk}{\left(x_{i},y_{j},z_{k} \right)}[\frac{kg}{m^{3}}] = \lambda{\left(x_{i},y_{j},z_{k} \right)}\end{equation} \begin{equation}P{\left(\vec{x} \right)}[nPa] = \lambda{\left(\vec{x} \right)}\end{equation} \begin{equation}\operatorname{P_{ijk}}{\left(x_{i},y_{j},z_{k} \right)}[nPa] = \lambda{\left(x_{i},y_{j},z_{k} \right)}\end{equation}
In [15]:
Copied!
model.rho((2,5,8))
model.rho((2,5,8))
Out[15]:
array(83.244)
In [16]:
Copied!
model.P((2,5,8))
model.P((2,5,8))
Out[16]:
array(93.02)
In [17]:
Copied!
model.detail()
model.detail()
Out[17]:
symbol units lhs rhs arg_units
rho rho(xvec) kg/m**3 rho lambda(xvec) None
rho_ijk rho_ijk(x_i, y_j, z_k) kg/m**3 rho_ijk lambda(x_i, y_j, z_k) None
P P(xvec) nPa P lambda(xvec) None
P_ijk P_ijk(x_i, y_j, z_k) nPa P_ijk lambda(x_i, y_j, z_k) None

Here the @kamodofy decorator handles the provisioning of kamodo-specific metadata. For example, the declared function rho now has a meta attribute:

In [18]:
Copied!
model.rho.meta
model.rho.meta
Out[18]:
{'units': 'kg/m**3',
 'arg_units': None,
 'citation': 'Pembroke et al 2019',
 'equation': None,
 'hidden_args': []}

@kamodofy also adds the data attribute, by calling the function with its default parameters:

In [19]:
Copied!
model.rho_ijk.data.shape
model.rho_ijk.data.shape
Out[19]:
(11, 22, 33)

Combined models¶

We could also register the model's interpolating method as part of some other Kamodo object, such as another kamodofied model reader or data source:

In [20]:
Copied!
from kamodo import Kamodo
kamodo = Kamodo(rho = model.rho)
kamodo
from kamodo import Kamodo kamodo = Kamodo(rho = model.rho) kamodo
Out[20]:
\begin{equation}\rho{\left(\vec{x} \right)}[\frac{kg}{m^{3}}] = \lambda{\left(\vec{x} \right)}\end{equation}

We can now compose our density function with expressions defined by other models:

In [21]:
Copied!
kamodo['vol[m^3]'] = '4/3 * pi * (xvec)**(3/2)'
kamodo
kamodo['vol[m^3]'] = '4/3 * pi * (xvec)**(3/2)' kamodo
Out[21]:
\begin{equation}\rho{\left(\vec{x} \right)}[\frac{kg}{m^{3}}] = \lambda{\left(\vec{x} \right)}\end{equation} \begin{equation}\operatorname{vol}{\left(\vec{x} \right)}[m^{3}] = \frac{4 \pi \vec{x}^{\frac{3}{2}}}{3}\end{equation}
In [22]:
Copied!
kamodo['mass'] = 'rho*vol'
kamodo
kamodo['mass'] = 'rho*vol' kamodo
Out[22]:
\begin{equation}\rho{\left(\vec{x} \right)}[\frac{kg}{m^{3}}] = \lambda{\left(\vec{x} \right)}\end{equation} \begin{equation}\operatorname{vol}{\left(\vec{x} \right)}[m^{3}] = \frac{4 \pi \vec{x}^{\frac{3}{2}}}{3}\end{equation} \begin{equation}\operatorname{mass}{\left(\vec{x} \right)}[kg] = \rho{\left(\vec{x} \right)} \operatorname{vol}{\left(\vec{x} \right)}\end{equation}
In [23]:
Copied!
kamodo.detail()
kamodo.detail()
Out[23]:
symbol units lhs rhs arg_units
rho rho(xvec) kg/m**3 rho lambda(xvec) None
vol vol(xvec) m**3 vol 4*pi*xvec**(3/2)/3 {}
mass mass(xvec) kg mass rho(xvec)*vol(xvec) {}

The following lines will save the image to your working directory.

!!! note Saving images requires plotly-orca-1.2.1, available through conda: conda install -c plotly plotly-orca

In [24]:
Copied!
model.rho_ijk().shape
model.rho_ijk().shape
Out[24]:
(22, 11, 33)
In [25]:
Copied!
import plotly.io as pio
fig = model.plot(rho_ijk = dict(z_k = model.z.mean()))
import plotly.io as pio fig = model.plot(rho_ijk = dict(z_k = model.z.mean()))
In [26]:
Copied!
from plotly.offline import iplot, init_notebook_mode, plot
from plotly.offline import iplot, init_notebook_mode, plot
In [27]:
Copied!
init_notebook_mode(connected = True)
init_notebook_mode(connected = True)
In [28]:
Copied!
fig = model.plot(rho_ijk =  dict(z_k = [model.z.mean()]))
fig = model.plot(rho_ijk = dict(z_k = [model.z.mean()]))
In [29]:
Copied!
pio.write_image(fig, 'kamodofied_model_1.svg', validate = False)
pio.write_image(fig, 'kamodofied_model_1.svg', validate = False)

We use markdown to embed the image into the notebook. Kamodofied Density

Alternative ways to graph:

In [30]:
Copied!
## uncomment to open interactive plot in the notebook
# from plotly.offline import init_notebook_mode, iplot
# init_notebook_mode(connected = True)
# iplot(kamodo.plot(rho = dict(x = model.x.mean())))
## uncomment to open interactive plot in the notebook # from plotly.offline import init_notebook_mode, iplot # init_notebook_mode(connected = True) # iplot(kamodo.plot(rho = dict(x = model.x.mean())))
In [31]:
Copied!
# # uncomment to open interactive plot in separate tab
# from plotly.offline import plot
# plot(kamodo.plot(rho = dict(z = 8)))
# # uncomment to open interactive plot in separate tab # from plotly.offline import plot # plot(kamodo.plot(rho = dict(z = 8)))
Previous Next

Built with MkDocs using a theme provided by Read the Docs.
« Previous Next »