Quantity Tutorial

This tutorial provides an overview of basic mlmc.quantity.quantity.Quantity operations.

The mlmc.quantity module and its related classes allow you to: - Estimate means and variances of MLMC sample results. - Derive new quantities from existing ones. - Perform arithmetic and NumPy-based operations on quantities.

Setup

Before exploring Quantity operations, we first set up a simple synthetic MLMC sampler.

import numpy as np
import mlmc.quantity.quantity_estimate
from examples.synthetic_quantity import create_sampler

Creating a Synthetic Quantity

We begin by creating a synthetic mlmc.quantity.quantity.Quantity with a predefined result_format:

# result_format = [
#     mlmc.QuantitySpec(name="length", unit="m", shape=(2, 1), times=[1, 2, 3], locations=['10', '20']),
#     mlmc.QuantitySpec(name="width", unit="mm", shape=(2, 1), times=[1, 2, 3], locations=['30', '40']),
# ]

sampler, simulation_factory, moments_fn = create_sampler()
root_quantity = mlmc.make_root_quantity(sampler.sample_storage, simulation_factory.result_format())

This means the sample results contain data for two quantities (length and width) at three time steps [1, 2, 3] and two locations each.

root_quantity is an instance of mlmc.quantity.quantity.Quantity, representing the full result data structure.

Mean Estimates

To compute the estimated mean of a quantity:

root_quantity_mean = mlmc.quantity.quantity_estimate.estimate_mean(root_quantity)

The returned object, root_quantity_mean, is a mlmc.quantity.quantity.QuantityMean instance.

To retrieve statistical values:

# Total mean and variance
root_quantity_mean.mean
root_quantity_mean.var

# Means and variances at each level
root_quantity_mean.l_means
root_quantity_mean.l_vars

Moments and Covariance Estimation

To create and estimate statistical moments:

moments_quantity = mlmc.quantity.quantity_estimate.moments(root_quantity, moments_fn=moments_fn)
moments_mean = mlmc.quantity.quantity_estimate.estimate_mean(moments_quantity)

To obtain central moments, first subtract the mean:

central_root_quantity = root_quantity - root_quantity_mean.mean
central_moments_quantity = mlmc.quantity.quantity_estimate.moments(
    central_root_quantity, moments_fn=moments_fn
)
central_moments_mean = mlmc.quantity.quantity_estimate.estimate_mean(central_moments_quantity)

To estimate a covariance matrix:

covariance_quantity = mlmc.quantity.quantity_estimate.covariance(root_quantity, moments_fn=moments_fn)
cov_mean = mlmc.quantity.quantity_estimate.estimate_mean(covariance_quantity)

Quantity Selection

You can access and manipulate sub-quantities directly using the structure defined by result_format:

length = root_quantity["length"]  # Get quantity with name="length"
width = root_quantity["width"]    # Get quantity with name="width"

Both are still mlmc.quantity.quantity.Quantity instances.

Selecting by time:

length_locations = length.time_interpolation(2.5)

Selecting by location:

length_result = length_locations['10']

Now, length_result behaves like a NumPy array:

length_result[1, 0]
length_result[:, 0]
length_result[:, :]
length_result[:1, :1]
length_result[:2, ...]

Note

  • All derived quantities (like length_locations or length_result) remain Quantity instances.

  • Selecting a location before time is not supported.

Binary Operations

Quantity supports standard arithmetic operations:

Between quantities:

quantity = root_quantity + root_quantity
quantity = root_quantity + root_quantity + root_quantity

With constants:

const = 5
quantity_const_add = root_quantity + const
quantity_const_sub = root_quantity - const
quantity_const_mult = root_quantity * const
quantity_const_div = root_quantity / const
quantity_const_mod = root_quantity % const
quantity_add_mult = root_quantity + root_quantity * const

NumPy Universal Functions

Quantity objects are compatible with many NumPy universal functions (ufuncs):

quantity_np_add = np.add(root_quantity, root_quantity)
quantity_np_max = np.max(root_quantity, axis=0, keepdims=True)
quantity_np_sin = np.sin(root_quantity)
quantity_np_sum = np.sum(root_quantity, axis=0, keepdims=True)
quantity_np_maximum = np.maximum(root_quantity, root_quantity)

x = np.ones(24)
quantity_np_divide_const = np.divide(x, root_quantity)
quantity_np_add_const = np.add(x, root_quantity)
quantity_np_arctan2_const = np.arctan2(x, root_quantity)

Conditional Selection

You can select parts of a quantity using logical conditions via the select() method.

selected_quantity = root_quantity.select(0 < root_quantity)

Or using comparisons between quantities:

quantity_add = root_quantity + root_quantity
quantity_add_select = quantity_add.select(root_quantity < quantity_add)
root_quantity_selected = root_quantity.select(-1 != root_quantity)

Multiple conditions are combined using logical AND:

quantity_add.select(root_quantity < quantity_add, root_quantity < 10)

Use NumPy logical functions for more complex conditions:

selected_quantity_or = root_quantity.select(np.logical_or(0 < root_quantity, root_quantity < 10))

You can also explicitly define the selection mask:

mask = np.logical_and(0 < root_quantity, root_quantity < 10)
q_bounded = root_quantity.select(mask)