Overview

uw.Params provides a clean way to define configurable parameters that can be:

  1. Set in notebooks - Just assign new values

  2. Overridden from command line - Via PETSc options (-uw_param_name value)

This makes scripts portable between interactive development and HPC batch execution.

Basic Usage

The recommended pattern is to define default values as named constants before the uw.Params block. This separates “what are the defaults” (easy to find and edit in a notebook) from “how are they validated and overridden” (the uw.Params machinery).

import underworld3 as uw

# --- Default values (edit these in a notebook) ---
RESOLUTION  = 0.05     # cell size for mesh
DIFFUSIVITY = 1.0      # material property
MAX_STEPS   = 100      # solver iterations

params = uw.Params(
    uw_resolution  = RESOLUTION,
    uw_diffusivity = DIFFUSIVITY,
    uw_max_steps   = MAX_STEPS,
    uw_verbose     = True,        # Boolean flag
    uw_solver      = "mumps",     # String option
)

# Use in your model
mesh = uw.meshing.Box(cellSize=params.uw_resolution)

Naming Convention

Use the uw_ prefix for parameter names. This:

  • Avoids collisions with PETSc solver options

  • Makes it clear these are underworld example parameters

  • The CLI flag matches the Python name exactly

Command Line Override

# Override resolution and solver
python script.py -uw_resolution 0.025 -uw_solver superlu_dist

# Works with mpirun
mpirun -np 4 python script.py -uw_resolution 0.01

Why Single-Dash Options?

Underworld uses PETSc-style command-line options, not Python’s standard argparse conventions. The key differences:

Convention

Long option

Short option

Word separator

PETSc (Underworld)

-uw_resolution

underscore _

argparse (Python)

--resolution

-r

hyphen -

Underworld inherits PETSc’s options database, which uses a single dash followed by a descriptive name with underscores. This is by design:

  • PETSc solver options (e.g., -ksp_type gmres, -pc_type lu) use this format

  • Underworld user parameters share the same options database

  • The uw_ prefix prevents collisions with PETSc’s own options

This means standard Python option parsers (argparse, click) do not support this style of options by default and may require custom handling if you try to parse -uw_* flags yourself. Use uw.Params instead — it reads from PETSc’s options database automatically.

Tip

If you’re writing a wrapper script that also needs argparse-style options, parse those with argparse first (for example using parse_known_args), then pass only the remaining/unparsed arguments to PETSc via petsc4py.init(remaining_argv) before importing underworld.

Notebook Override

# Just assign a new value
params.uw_resolution = 0.025

Unit-Aware Parameters

For physical quantities with units, use the uw.Param() wrapper:

params = uw.Params(
    # Physical quantities with units
    uw_cell_size = uw.Param(0.5, units="km", description="Mesh cell size"),
    uw_viscosity = uw.Param(1e21, units="Pa*s"),
    uw_latitude = uw.Param(45.0, units="degree"),

    # Dimensionless ratios
    uw_rayleigh = uw.Param(1e5, type=uw.ParamType.RATIO,
                           description="Rayleigh number"),

    # Plain types still work
    uw_elements = 32,
)

CLI with Units

Units must be provided on the command line for unit-aware parameters:

# These work
python script.py -uw_cell_size "500 m"
python script.py -uw_cell_size 0.5km
python script.py -uw_viscosity "1e22 Pa*s"
python script.py -uw_latitude "0.785 radian"  # Converts to degrees

# These fail with helpful errors
python script.py -uw_cell_size 500        # ERROR: Units required
python script.py -uw_cell_size "500 s"    # ERROR: Dimension mismatch

Accessing Values

Unit-aware parameters return UWQuantity objects that integrate with the scaling system:

cell_size = params.uw_cell_size      # UWQuantity(0.5, "km")
cell_size_m = cell_size.to("meter")  # Convert to meters

# For mesh creation (handles unit conversion automatically)
mesh = uw.meshing.Box(cellSize=params.uw_cell_size)

Parameter Options

The uw.Param() wrapper supports:

Option

Description

value

Default value (required)

units

Unit string, e.g., "km", "Pa*s", "degree"

type

Explicit type: uw.ParamType.QUANTITY, RATIO, INTEGER, etc.

bounds

Tuple (min, max) for validation

description

Help text shown in cli_help()

Bounds Validation

params = uw.Params(
    uw_cell_size = uw.Param(0.5, units="km",
                            bounds=(0.01, 100),
                            description="Must be 0.01-100 km"),
)

# This would raise ValueError
# python script.py -uw_cell_size "0.001 km"  # Below minimum

Inspecting Parameters

Display in Notebooks

Params has rich display with source tracking:

params  # Shows table with values, units, types, and sources

Parameter

Value

Units

Type

Source

uw_cell_size

0.5 kilometer

km

quantity

default

uw_viscosity

1e+21 pascal * second

Pa*s

quantity

CLI

CLI Help

print(params.cli_help())
Command-line options (PETSc format):

  -uw_cell_size <quantity>   Units: km
                             Must be 0.01-100 km
                             (default: 0.5 km)

  -uw_viscosity <quantity>   Units: Pa*s
                             (default: 1e+21 Pa*s)

  -uw_rayleigh <ratio>       Dimensionless ratio
                             Rayleigh number
                             (default: 100000.0)

Example:
  python script.py -uw_cell_size 0.5km -uw_viscosity 1e+21Pa*s

Complete Example

import underworld3 as uw

# --- Default values (edit these in a notebook) ---
CELL_SIZE      = 50.0   # km – target cell size
DEPTH          = 660.0  # km – model depth
VISCOSITY      = 1e21   # Pa·s – reference viscosity
DENSITY_DIFF   = 50.0   # kg/m³ – density contrast
MAX_ITERATIONS = 50
TOLERANCE      = 1e-6

# Define all configurable parameters at the top
params = uw.Params(
    # Mesh parameters
    uw_cell_size = uw.Param(CELL_SIZE, units="km",
                            bounds=(10, 200),
                            description="Target cell size"),
    uw_depth = uw.Param(DEPTH, units="km",
                        description="Model depth"),

    # Physical properties
    uw_viscosity = uw.Param(VISCOSITY, units="Pa*s"),
    uw_density_diff = uw.Param(DENSITY_DIFF, units="kg/m^3"),

    # Solver settings
    uw_max_iterations = MAX_ITERATIONS,
    uw_tolerance = TOLERANCE,
)

# Show help (useful at script start)
if uw.mpi.rank == 0:
    print(params.cli_help())

# Build model using parameters
mesh = uw.meshing.Box(
    minCoords=(0, 0),
    maxCoords=(uw.quantity(2000, "km"), params.uw_depth),
    cellSize=params.uw_cell_size,
)

# ... rest of model setup

Run with:

# Default parameters
python convection.py

# Override for higher resolution
python convection.py -uw_cell_size 25km -uw_depth 1000km

# HPC run with many overrides
mpirun -np 256 python convection.py \
    -uw_cell_size 10km \
    -uw_viscosity "5e20 Pa*s" \
    -uw_max_iterations 100

Using Parameters with Solvers: Expressions

When passing parameters to solver constitutive models, wrap them in uw.expression(). This creates a named symbolic container that the JIT compiler can update efficiently — changing the value between time steps does not trigger recompilation of the C extension.

import underworld3 as uw

# Define parameters
VISCOSITY = 1e21      # Named constant
MODULUS = 1e10        # Named constant
DT = 0.01            # Timestep

params = uw.Params(
    uw_viscosity = VISCOSITY,
    uw_modulus = MODULUS,
)

# Wrap in expressions for solver use
eta = uw.expression("eta", params.uw_viscosity)
mu = uw.expression("mu", params.uw_modulus)
dt_e = uw.expression("dt_e", DT)

# Pass expressions to constitutive model
stokes.constitutive_model.Parameters.shear_viscosity_0 = eta
stokes.constitutive_model.Parameters.shear_modulus = mu
stokes.constitutive_model.Parameters.dt_elastic = dt_e

# Time-stepping: change dt without recompilation
for step in range(100):
    dt_e.sym = compute_new_timestep()  # Updates value, ~0ms
    stokes.solve()                      # No JIT rebuild needed

Without expressions, changing a solver parameter between steps requires setting _force_setup=True on the solve call, which triggers a full JIT recompilation (~5–15 seconds). With expressions, parameter updates go through PETSc’s constants[] array and cost essentially nothing.

Angle Units

Angles work naturally - you can define in degrees and provide radians (or vice versa):

params = uw.Params(
    uw_latitude = uw.Param(45.0, units="degree"),
)

# CLI accepts any angle unit
# python script.py -uw_latitude "0.785 radian"
# python script.py -uw_latitude "45 deg"

# Convert as needed
lat_rad = params.uw_latitude.to("radian")