API documentation

Kamodo

class kamodo.Kamodo(*funcs, **kwargs)

Kamodo base class demonstrating common API for scientific resources.

This API provides access to scientific fields and their properties through:

  • Interpolation of variables at user-defined points
  • Automatic unit conversions
  • Function composition convenient for coordinate transformations and data pipelining

Note: While intended for with space weather applications, the kamodo base class was designed to be as generic as possible, and should be applicable to a wide range of scientific domains and disciplines.

Initialization

class kamodo.Kamodo(*funcs, **kwargs)
init(self, *funcs, **kwargs)

Initialize Kamodo object with functions or by keyword

Inputs

  • funcs - (optional) list of (str) expressions to register in f(x)=x format

  • kwargs - (optional) key,value pairs of functions to register

    • key - left-hand-side symbol (str)
    • value - can be one of:
      • latex or python (str) expression e.g. "x^2-x-1"
      • kamodofied function with appropriate .meta and .data attributers (see @kamodofy decorator)
      • lambda function (having no meta or data attributes)
  • verbose - (optional) (default=False) flag to turn on all debugging print statements

returns - dictionary-like kamodo object of (symbol, function) pairs

usage:

    kobj = Kamodo(
        'f(x[cm])[kg/m^3]=x^2-x-1', # full expressions with units
        area = kamodofy(lambda x: x*x, units='cm^2'), # kamodofied functions
        h = 'sin(x)', # key-value expressions
        )

Registering functions

class kamodo.Kamodo(*funcs, **kwargs)
setitem(self, sym_name, input_expr)

Assigns a function or expression to a new symbol, performing automatic function composition and inserting unit conversions where appropriate.

  • sym_name - function symbol to associate with right-hand-side in one of the following formats:

    • f - a lone fuction symbol (alphabetic argument ordering)
    • f(z,x,y) - explicit argument ordering
    • f[kg] - output unit assignment
    • f(x[cm])[kg] - output and input unit assignment
  • input_expr - rhs string or kamodofied function, one of:

    • right-hand-side expression: python or latex str (e.g.x^2-x-1)
    • kamodofied function with appropriate .meta and .data attributers (see @kamodofy)
    • lambda function (having no meta or data attributes)

Raises: - NameError when left-hand-side units incompatible with right-hand-side expression

returns: None

usage:

Setting left-hand-side units will automatically trigger unit conversion

kobj = Kamodo()
kobj['radius[m]'] = 'r'
kobj['area[cm^2]'] = 'pi * radius^2'
kobj

The above kobj will render in a Jupyter notebook like this:

Kamodo will raise an error if left-hand-side units are incompatible with the right-hand-side expression

kobj = Kamodo()
kobj['area[cm^2]'] = 'x^2' # area has units of cm^2
try:
    kobj['g(x)[kg]'] = 'area' # mass not compatible with square length
except NameError as m:
    print(m)

output:

Retrieving functions

Registered functions may be accessed via dictionary or attribute syntax.

class kamodo.Kamodo(*funcs, **kwargs)
getitem(self, key)

Given a symbol string, retrieves the corresponding function.

input: key - string or function symbol

returns: the associated function

usage :

Rretrieval by function name:

    kobj['f'] = 'x^2-x-1'
    f = kobj['f']
    f(3) # returns 5

It is also possible to retreive by function symbol:

    from kamodo import sympify
    fsymbol = sympify('f') # converts str to symbol

    kobj['f'] = 'x^2-x-1'
    f = kobj[fsymbol]
    f(3) # returns 5
getattr(self, name)

Retrieves a given function as an attribute.

input - name of function to retrieve

returns the associated function

Usage:

k = Kamodo(f='x^2-x-1')
k.f

The above renders as follows in a jupyter notebook

Evaluation

Function evaluation may be performed either by keyword or attribute syntax:

k = Kamodo(f='x^2-x-1')
assert k.f(3) == k['f'](3)

For closed-form expressions, kamodo uses the highly optimized numexpr library if available and will fall back to numpy otherwise:

x = np.linspace(-5,5,33000111)
k.f(x)

Programmatic evaluation is also possible:

class kamodo.Kamodo(*funcs, **kwargs)
evaluate(self, variable, *args, **kwargs)

Evaluate a given function variable using kwargs.

If the variable is not present, try to parse it as an equation and evaluate the expression.

inputs :

  • variable - str:
    • function string name to evaluate
    • semicolon delmitted list of equations, the last of which will be evaluated
  • args - not presently used
  • kwargs - key-word arguments passed to function (required)

returns : dictionary of input kwargs and output {variable: self.variable(**kwargs)}

usage :

k = Kamodo(f='x+y')

result = k.evaluate('f', x=3, y=4)['f']
assert k.f(3,4) == result
assert k.evaluate('g=f+3', x=3, y=4)['g'] == result+3
assert k.evaluate('g=f+3;h=g+2', x=3, y=4)['h'] == result+3+2

RPC server

Start a Kamodo asyncio server using kamodo.serve:

class kamodo.Kamodo(*funcs, **kwargs)
serve(self, host='localhost', port='60000', certfile=None, keyfile=None)

Serve registered functions using Kamodo-RPC spec

Uses asyncio and capnp proto

inputs :

  • host - str: localhost, ipv4 or ipv6 address (localhost by default)
  • port - str: port to serve from
  • certfile - certficicate to authenticate clients
  • keyfile - private key file to authenticate

returns : None

usage :

k = Kamodo(f='x+y')

k.serve() # start rpc server

see kamodo/rpc/kamodo.capnp

RPC client

Start a Kamodo Client using the KamodoClient class:

class kamodo.KamodoClient(host='localhost', port='60000', certfile='selfsigned.cert', **kwargs)
init(self, host='localhost', port='60000', certfile='selfsigned.cert', **kwargs)

CapnProto Kamodo client

Abstracts a remote kamodo server using capn proto binary message types

inputs :

  • host - str: localhost, ipv4 or ipv6 address (localhost by default)
  • port - str: port to serve from
  • certfile - certficicate to authenticate clients (selfsigned.cert)

returns : Kamodo object with server-side functions

usage :

k = KamodoClient() # connect to localhost:60000 by default
k.f # assuming f is registered on remote

Plotting

single function plots

For plotting single variables, the figure method is most appropriate

class kamodo.Kamodo(*funcs, **kwargs)
figure(self, variable, indexing='ij', **kwargs)

Generates a plotly figure for a single variable and keyword arguments

inputs :

  • variable: the name of a previously registered function
  • kwargs: {arg: values} to pass to registered function
  • indexing: determines order by which 2d matrices are given (affects contour_plot, carpet_plot, and plane)

returns : plotly figure (dict-like)

raises: SyntaxError if variable not found

multi-function plots

For multiple functions, the plot method is more convenient

class kamodo.Kamodo(*funcs, **kwargs)
plot(self, *variables, plot_partial={}, **figures)

Generates a plotly figure from multiple variables and keyword arguments

inputs :

  • variable: the name of a previously registered function
  • plot_partial: dict(dict) of {varname: {arg: values}} partial arguments to fix
  • figures: dict {variable: {arg: values}} to pass to registered function

returns : plotly figure (dict-like). When run in a jupyter notebook, an inline plotly figure will be displayed.

raises :

  • TypeError when required function arguments are not specified
  • KeyError when no plotting function can be associated with input/output shapes

usage :

k = Kamodo(
    f=lambda x=np.array([2,3,4]): x**2-x-1,
    g='sin(x)')

k.plot('f') # plots f using default arguments for f

k.plot(f={x:[3,4,5]}, g={x{-2, 3, 4}}) # plots f at x=[3,4,5] and g at [-2,3,4]

Use the plot_partial keyword to lower the dimensionality of a function by fixing some of its variables:

from kamodo import kamodofy, gridify, Kamodo
from scipy.interpolate import RegularGridInterpolator
import numpy as np

# define sample coordinate and data arrays
t, lon, lat, ht = np.linspace(0.,24.,10),
    np.linspace(0.,360.,20),
    np.linspace(-90.,90.,50),
    np.linspace(100.,10000.,250)

variable = np.reshape(
    np.linspace(0., np.pi, 2500000), # data
    (10, 20, 50, 250)) # match coordinate arrays

# define and kamodofy interpolating function
rgi = RegularGridInterpolator((t, lon, lat, ht), variable, bounds_error = False, fill_value=np.NaN)

@kamodofy(units='m/s', data=variable)
def interpolator(xvec):
    return rgi(xvec)

#gridify same function
interpolator_grid = kamodofy(gridify(interpolator, time = t, lon=lon, lat = lat,  height = ht), units='m/s', data=variable, arg_units={'time':'hr','lon':'deg','lat':'deg','height':'km'})

#register in a new kamodo object
kamodo_object = Kamodo()
kamodo_object['v'] = interpolator
kamodo_object['v_ijkl'] = interpolator_grid # 4 dimensional
kamodo_object

kamodo_object.plot(v_ijkl = {'time':10.,'lat':90.})

The above line raises an Error: not supported: out_dim ('N', 'M'), arg_dims [(1,), ('N',), (1,), ('M',)]

Since there is no straight-foward way to plot a high-dimensional function, we can use plot_partial instead to get a lower-dimensional slice instead:

kamodo_object.plot('v_ijkl', plot_partial={'v_ijkl': {'lon':10.,'lat':90.}})

See also @partial decorator to fix a function's arguments at registration time.

LaTeX rendering

The following methods allow Kamodo to integrate seemlessly with modern publication workflows. This includes support for LaTeX rendering within jupyter notebooks, LaTeX printing for manuscript preparation, and a high-level detail summary of registered functions.

class kamodo.Kamodo(*funcs, **kwargs)
repr_latex(self)

Provides notebook rendering of kamodo object's registered functions.

inputs - N/A

returns latex string - obtained from to_latex method

Usage:

k = Kamodo(f='x^2-x-1')
k

When placed on a line by itself, the above object will be rendered by jupyter notebooks like this:

More on the repr_latex method can be found here

Note: Registered functions are also equiped with their own repr_latex method.

to_latex(self, keys=None, mode='equation')

Generate list of LaTeX-formated formulas

inputs :

  • keys - (optional) list(str) of registered functions to generate LaTeX from

  • mode - (optional) string determines to wrap formulas

    • 'equation' (default) wraps formulas in begin{equation} ... end{equation}

    • 'inline': wraps formulas in dollar signs

returns : LaTeX-formated string

Note: This function does not need to be called directly for rendering in jupyter because the repr_latex method is automatically attached.

detail(self)

Constructs a pandas dataframe from signatures

inputs - N/A

returns - pandas dataframe

usage:

k = Kamodo('rho(x[cm])[g/cm^3]=x^2', g = 'x+y')
k.detail()

outputs:

symbol units lhs rhs arg_units
rho rho(x) g/cm3 rho(x) x2 {'x': 'cm'}
g g(x, y) g x + y {}

Plotting

Plot types

As described in Visualization, Kamodo automatically maps registered functions to certain plot types. All such functions expect the same input variables and return a triplet [trace], chart_type, layout where [trace] is a list of plotly trace objects.

kamodo.plotting.get_plot_types_df()

pack the plot types into a dataframe

The available plot types may be imported thusly:

from kamodo.plotting import plot_types

Scatter plot

kamodo.plotting.scatter_plot(result, titles, verbose=False, **kwargs)
  • Generates a 3D scatter plot.

inputs :

  • result: dictionary returned by Kamodo().evaluate
  • titles: dictionary holding plot titles
  • indexing: str 'ij' or 'xy' determining array order
  • verbose: boolean (default False) for debugging
  • kwargs: additional arguments specific to plot types

returns - tuple:

Line plot

kamodo.plotting.line_plot(result, titles, verbose=False, **kwargs)
  • Generates a 1D, 2D or 3D line plot.

inputs :

  • result: dictionary returned by Kamodo().evaluate
  • titles: dictionary holding plot titles
  • indexing: str 'ij' or 'xy' determining array order
  • verbose: boolean (default False) for debugging
  • kwargs: additional arguments specific to plot types

returns - tuple:

Vector plot

kamodo.plotting.vector_plot(result, titles, verbose=False, **kwargs)
  • Generates a 2D or 3D vector plot.

inputs :

  • result: dictionary returned by Kamodo().evaluate
  • titles: dictionary holding plot titles
  • indexing: str 'ij' or 'xy' determining array order
  • verbose: boolean (default False) for debugging
  • kwargs: additional arguments specific to plot types

returns - tuple:

Contour plot

kamodo.plotting.contour_plot(result, titles, indexing, verbose=False, **kwargs)
  • Generates a 2D contour plot.

inputs :

  • result: dictionary returned by Kamodo().evaluate
  • titles: dictionary holding plot titles
  • indexing: str 'ij' or 'xy' determining array order
  • verbose: boolean (default False) for debugging
  • kwargs: additional arguments specific to plot types

returns - tuple:

3D Plane

kamodo.plotting.plane(result, titles, indexing='xy', verbose=False, **kwargs)
  • Generates a 3D plane plot.

  • result: dictionary returned by Kamodo().evaluate

  • titles: dictionary holding plot titles
  • indexing: str 'ij' or 'xy' determining array order
  • verbose: boolean (default False) for debugging
  • kwargs: additional arguments specific to plot types

returns - tuple:

3D Surface

kamodo.plotting.surface(result, titles, verbose=False, **kwargs)
  • Generates a 3d surface-scalar or surface plot.

  • result: dictionary returned by Kamodo().evaluate

  • titles: dictionary holding plot titles
  • indexing: str 'ij' or 'xy' determining array order
  • verbose: boolean (default False) for debugging
  • kwargs: additional arguments specific to plot types

returns - tuple:

Carpet plot

kamodo.plotting.carpet_plot(results, title, xaxis, yaxis, indexing='xy', **kwargs)
  • Generates a 2D skew (carpet) plot.

inputs :

  • result: dictionary returned by Kamodo().evaluate
  • titles: dictionary holding plot titles
  • xaxis: dictionary of {title: latex str}
  • yaxis: dictionary of {title: latex str}
  • indexing: str 'ij' or 'xy' determining array order
  • kwargs: additional arguments specific to plot types

returns - tuple:

Triangulated Mesh plot

kamodo.plotting.tri_surface_plot(result, titles, verbose=False, **kwargs)
  • Generates a 3D mesh (tri-surface) plot.

  • result: dictionary returned by Kamodo().evaluate

  • titles: dictionary holding plot titles
  • indexing: str 'ij' or 'xy' determining array order
  • verbose: boolean (default False) for debugging
  • kwargs: additional arguments specific to plot types

returns - tuple:

Image plot

kamodo.plotting.image(result, titles, verbose=False, **kwargs)
  • Generates a 2D image plot

  • result: dictionary returned by Kamodo().evaluate

  • titles: dictionary holding plot titles
  • indexing: str 'ij' or 'xy' determining array order
  • verbose: boolean (default False) for debugging
  • kwargs: additional arguments specific to plot types

returns - tuple:

Decorators

These decorators may also be imported like this

from kamodo import kamodofy

kamodofy

kamodo.util.kamodofy(_func=None, units='', arg_units=None, data=None, update=None, equation=None, citation=None, hidden_args=[], **kwargs)

Adds meta and data attributes to functions for registering with Komodo objects.

inputs :

  • _func: function to wrap
  • units: (optional) physical output units
  • arg_units: (optional) dictionary { arg : str unit} containing physical input units
  • data: if supplied, set f.data = data, if not supplied, set f.data = f(), assuming it can be called with no arguments. If f cannot be called with no arguments, will set f.data = None
  • update: name of another function's argument to update (see simulation api)
  • equation: str representing right-hand-side of the function
  • citation: str reference for publication
  • hidden_args: arguments of function to hide from latex rendering
  • kwargs: other key word arguments

returns : the decorated function with the following attributes

  • meta is a dictionary containing
    • units: physical output units (str)
    • arg_units: dictionary { arg : str unit}
    • equation: latex str representing right-hand-side of the function
    • citation: str reference for publication
    • hidden: str list of arguments to hide from latex rendering
  • data: default data representing expected function output for default arguments
  • update: name of another function's argument to update

usage :

@kamodofy(units='kg/cm^2', arg_units=dict(x='cm'), citation='Pembroke et. al 2022', hidden_args=['verbose'])
def myfunc(x=30, verbose=True):
    return x**2
myfunc.meta
{'units': 'kg/cm^2',
 'arg_units': {'x': 'cm'},
 'citation': 'Pembroke et. al 2022',
 'equation': None,
 'hidden_args': ['verbose']}

The above metadata is used by Kamodo objects for function registration. Similarly, a data attribute is attached which represents the output of the function when called with no arguments:

myfunc.data
900

gridify

kamodo.util.gridify(_func=None, order='A', squeeze=True, **defaults)

Given a function _func(xvec) taking a single variable of shape (n,dim) and defaults (e.g. x(L), y(M), z(N)) gridify returns a new function _func(x,y,z) that calls _func with points n=LxMxN, reshaping the result to (M, L, N) (order='A', default) or (L, M, N) (order='C'). See np.meshgrid and np.reshape.

inputs :

  • _func: kamodo function
  • order: 'A' str (default) passed to reshape.
    • order = 'A': use indexing='xy' in meshgrid
    • order = 'C': use indexing='ij' in meshgrid
  • squeeze: True (default) passed to reshape before returning

returns : mutated function

usage :

Conceptually, @gridify converts point-like interpolators to grid-based interpolators.

Suppose we have a function r(xvec) that takes an array of shape (n,2) and returns the magnitude r of each point. By applying @gridify, we convert r(xvec) to r(x,y):

@gridify(x=np.linspace(-3,3,5), y=np.linspace(-5,5,11), order='A')
def r(xvec):
    return np.linalg.norm(xvec, axis=-1)

The result is automatically reshaped to match the input arrays for x,y:

r(x=[2,3], y=[3,4,5]).shape # (3, 2)

In addition, r receives defaults for x and y:

r().shape # (11,5)

To see the defaults, use the get_defaults function

from kamodo import get_defaults

defaults = get_defaults(r)
defaults['x'] # (5,)
defaults['y'] # (11,)

We can generate "slices" for fixed values of x or y:

r(x=0) # array([5., 4., 3., 2., 1., 0., 1., 2., 3., 4., 5.])
r(y=0) # array([3. , 1.5, 0. , 1.5, 3. ])

Use order to control the shapes of returned arrays (row vs column major).

@gridify(x=np.linspace(-3,3,5), y=np.linspace(-5,5,11), z=np.linspace(-1,1,13), order='A')
def r(xvec):
    return np.linalg.norm(xvec, axis=-1)

r().shape # (11, 5, 13) corresponds to y, x, z

@gridify(x=np.linspace(-3,3,5), y=np.linspace(-5,5,11), z=np.linspace(-1,1,13), order='C')
def r(xvec):
    return np.linalg.norm(xvec, axis=-1)

r().shape # (5, 11, 13) corresponds to x, y, z

By default, the output array will be squeezed if a dimension has size 1. Use squeeze=False to disable this behavior:

@gridify(x=np.linspace(-3,3,5), y=np.linspace(-5,5,11), squeeze=False)
def r(xvec):
    return np.linalg.norm(xvec, axis=-1)

r(y=0).shape # (1, 5)
r(x=0).shape # (11, 1)

pointlike

kamodo.util.pointlike(_func=None, signature=None, otypes=[], squeeze=None)

Transforms a single-argument function to one that accepts m points of dimension n pointlike wraps np.vectorize

inputs :

  • _func: kamodo function
  • signature: (optional) Generalized universal function signature, e.g., (m,n),(n)->(m) for vectorized matrix-vector multiplication.
  • otypes: list(types) default is [float] The output data type. It must be specified as either a string of typecode characters or a list of data type specifiers. There should be one data type specifier for each output.
  • squeeze: axis on which to squeeze result

returns : modified function

partial

kamodo.util.partial(_func=None, **partial_kwargs)

A partial function decorator, reducing function signature to reflect partially assigned kwargs.

inputs :

  • _func: kamodo function

  • partial_kwargs: (dict) _func arguments to set

returns : updated function with reduced arguments

usage :

@partial(z=1)
def f(x, y=2, z=5):
    return x + y + z
assert f(2,3) == 2+3+1
try:
    f(3,4,5)
except TypeError as m:
    print(m) # wrapped() takes from 1 to 2 positional arguments but 3 were given

Note: This decorator differs significantly from functools.partial in the following ways:

  • functools.partial updates the function defaults without actually eliminating arguments.
  • functools.partial raises TypeError when used as a @decorator

Test Suite

Kamodo features a full suite of tests run via pytest. We highlight a few of these tests below as further examples of Kamodo's expected use cases.

Kamodo Tests

kamodo.test_kamodo.test_Kamodo_expr()
  • Method for testing correctness of expression.
kamodo.test_kamodo.test_Kamodo_latex()
  • Method for testing latex representation of expression.
kamodo.test_kamodo.test_Kamodo_mismatched_symbols()
  • Method for testing symobls in accroding expression.
kamodo.test_kamodo.test_Kamodo_reassignment()
  • Method for testing reassignment of expression.
kamodo.test_kamodo.test_function_registry()
  • Method for testing kamodo's function registry.
kamodo.test_kamodo.test_unit_registry()
  • Method for testing kamodo function's unit registry
kamodo.test_kamodo.test_komodofy_decorator()
  • Method for testing kamodofy dcorator.
kamodo.test_kamodo.test_vectorize()
  • Method for testing vetorization of expression.
kamodo.test_kamodo.test_jit_evaluate()
  • Kamodo testing method for just-in time evaluation.
kamodo.test_kamodo.test_multiple_traces()
  • Method for testing multiple traces in a figure.

Plotting Tests

kamodo.test_plotting.test_scatter_plot()

Method for testing scatter plot's trace, layout and plot type.

kamodo.test_plotting.test_line_plot_line()

Method for testing line plot's trace, layout and plot type.

kamodo.test_plotting.test_line_plot_2d_line()

Method for testing 2D line plot's trace, layout and plot type.

kamodo.test_plotting.test_line_plot_3d_line_pd()

Method for testing 3D line plot's trace, layout and plot type.

kamodo.test_plotting.test_vector_plot_2d_vector()

Method for testing 2D vector plot's trace, layout and plot type.

kamodo.test_plotting.test_vector_plot_3d_vector()

Method for testing 3D vector plot's trace, layout and plot type.

kamodo.test_plotting.test_vector_plot_3d_vector()

Method for testing 3D vector plot's trace, layout and plot type.

kamodo.test_plotting.test_contour_plot_2d_grid()

Method for testing 2D contour plot's trace, layout and plot type.

kamodo.test_plotting.test_contour_plot_2d_skew()

Method for testing 2D skew plot's trace, layout and plot type.

kamodo.test_plotting.test_plane()

Method for testing plane plot's trace, layout and plot type.

kamodo.test_plotting.test_surface_3d_surface()

Method for testing 3D surface plot's trace, layout and plot type.

kamodo.test_plotting.test_arg_shape_pd()

Method for testing argument shape.

kamodo.test_plotting.test_image_plot()

Method for testing image plot's trace, layout and plot type.