Authoring Problemtypes (Python)
Problemtypes can be written in Python instead of JavaScript — the API is the same, spelled snake_case. Drop a .py file in .kratos/problemtypes/ and it appears in the Problemtype dropdown.
Complete worked examples: faithful Python ports of the five built-in problemtypes live in example/problemtypes/ (structural.py, fluid.py, convection_diffusion.py, potential_flow.py, shallow_water.py). A parity test keeps them byte-identical to the TypeScript originals, so they are always a current, runnable reference — copy one and start editing.
Python problemtypes run inside Pyodide (Python compiled to WebAssembly, bundled with the extension) — they do not use your system Python and cannot import Kratos or touch the filesystem. They only describe the case; the generated ProjectParameters.json runs with your real Kratos install like any other case. The interpreter (~14 MB of WASM) loads lazily the first time a .py problemtype is discovered, so there is no cost when you don't use the feature.
A complete example
# .kratos/problemtypes/my_thermal.py
from kratos_problemtype import define_problemtype, section, field, condition, material_law
def solver_settings(values, ctx):
# ctx is a dict with snake_case keys — see the table below.
return {
"solver_type": "transient",
"analysis_type": "linear",
"model_part_name": ctx["model_part_name"],
"domain_size": ctx["domain_size"],
"model_import_settings": {"input_type": "mdpa", "input_filename": ctx["mdpa_stem"]},
"material_import_settings": {"materials_filename": ctx["materials_file_name"]},
"echo_level": values["echoLevel"],
"problem_domain_sub_model_part_list": ctx["parts_model_parts"],
"processes_sub_model_part_list": ctx["skin_model_parts"],
"time_stepping": {"time_step": values["timeStep"]},
}
define_problemtype(
id="myThermal",
name="My Thermal (py)",
description="Custom transient heat transfer",
analysis_stage="KratosMultiphysics.ConvectionDiffusionApplication.convection_diffusion_analysis",
model_part_name="ThermalModelPart",
materials_file_name="ThermalMaterials.json",
domain_sizes=[2, 3],
sections=[
section("problem", "Problem data",
field("timeStep", "Time step", "number", default=0.1),
field("endTime", "End time", "number", default=1.0),
field("echoLevel", "Echo level", "int", default=1)),
],
parts_condition="parts",
conditions=[
condition("parts", "Thermal body", list="list_other_processes",
target="volume", process_template={}),
condition("temperature", "Fixed temperature",
list="constraints_process_list", target="any",
fields=[field("value", "T [K]", "number", default=293.15)],
process_template={
"python_module": "assign_scalar_variable_process",
"kratos_module": "KratosMultiphysics",
"process_name": "AssignScalarVariableProcess",
"Parameters": {
"model_part_name": "$path",
"variable_name": "TEMPERATURE",
"constrained": True,
"value": "$field:value",
"interval": [0.0, "End"],
},
}),
],
material_laws=[
material_law("thermal", "", variables=[
field("DENSITY", "ρ", "number", default=1000),
field("CONDUCTIVITY", "k", "number", default=0.6),
field("SPECIFIC_HEAT", "c", "number", default=4184),
]),
],
output={"nodal_defaults": ["TEMPERATURE"]},
solver_settings=solver_settings, # required hook
# build_process=..., post_process=..., main_script=... (optional hooks)
)API surface
field(id, label, type, default=None, options=None, visible_when=None,
unit=None, help=None, group=None, min=None, max=None, step=None,
advanced=None, ref_collection=None) # unit: e.g. "kg/m³" — see below
field_group(id, label, icon=None, collapsed=None)
section(id, label, *fields, groups=None, icon=None)
condition(id, label, list="constraints_process_list", target="any",
fields=(), process_template=None, help=None, category=None,
icon=None, no_process=None, emit=None)
domain(id, label, model_part_name, mdpa_suffix, parts_condition,
condition_ids, materials_file_name=None, required=None,
mesh_naming=None) # one physics of a coupled problemtype
process(python_module, process_name=None, kratos_module="KratosMultiphysics",
parameters=None) # process_template sugar
material_law(id, name, variables=(), domain_size=None, domain=None,
application=None, group=None, parameters=None, when=None)
entry_type(id, label, *fields, parts=None, icon=None, help=None)
collection(id, label, *entry_types, min=None, max=None, defaults=None,
icon=None, help=None) # a repeatable list of named entries
INTERVAL_TOTAL # the [0.0, "End"] interval constant
define_problemtype(id, name, analysis_stage, model_part_name,
materials_file_name, domain_sizes,
sections=(), conditions=(), material_laws=(),
parts_condition=None, mesh_naming=None, output=None,
description=None, icon=None, family=None,
solver_settings=None, # required
build_process=None, post_process=None, main_script=None,
domains=None, extra_files=None, validate=None,
collections=None, applications=None,
coupling_interfaces=None, primary_document_name=None,
driver_script_name=None, driver_template=None)
application(name, when=None) # a required Kratos applicationmesh_naming declares the element/condition block names the solver expects — when the mesh differs, Generate writes a renamed <stem>_case.mdpa copy (final name = <base><dim>D<nnodes>N):
mesh_naming={"elements": "$field:elementBase", # or "Element"
"conditions": {2: "LineLoadCondition", 3: "SurfaceLoadCondition"}}When no single base can describe a formulation's names (shells, cables, springs), a kind takes a {"field": "<enum field>", "names": {<value>: <entry>}} table, where an entry is a template ("SpringDamperElement{dim}D"), one exact name ("CableElement3D2N") or a {dim: {nodes: name}} table of exact names; a value with no entry keeps the base meaning, and a shape a table omits makes Generate refuse that block instead of inventing a Kratos type.
icon names a toolbar icon for the problemtype's forms (the built-ins use ptStructural / ptFluid / ptThermal / ptPotentialFlow / ptShallowWater). A condition's list may also be a custom process-list name (e.g. boundary_conditions_process_list).
unit declares the unit a field's value is entered in (field("DENSITY", "ρ", "number", default=1000, unit="kg/m³")). It is what material presets convert into, so a row published in g/cm³ fills a variable declared in kg/m³. Without it the unit is inferred from the brackets in label and then from a well-known Kratos variable name; a value whose unit cannot be established on either side is only accepted as an exact match, never passed through unconverted. See Units and material presets for the recognised quantities.
visible_when accepts one rule dict or a list of them (all must hold); a rule uses "equals" or "one_of". Layout (group, category, family) and domains behave as described in the JavaScript guide. Collections are declared with collection() and entry_type(); a ref field names its collection with ref_collection="responses", and a caseValue field may only appear inside an entry type (both are checked when the file loads). The hooks see ctx["collections"][id], the entries with defaults merged and their parts as dotted modelParts. In a coupled problemtype the hooks receive ctx["domains"][id] with that domain's model_part_name, mdpa_stem, assignments, materials and parts_model_parts. coupling_interfaces, primary_document_name, driver_script_name, driver_template and applications mean what their JavaScript counterparts mean (coupled problemtypes, required applications); application("MeshMovingApplication", when={"field": "aleMesh", "equals": True}) adds a conditional one. An extra_files hook may return a file's content as a dict or list instead of a string: the host writes it as JSON formatted like the primary document, which is how example/problemtypes/coupled.py writes the CoSimulation participant documents byte-identical to the built-in's.
Field types, $path / $root / $field:<id> template placeholders, process lists and the generated document shape are identical to the JavaScript API. options accepts plain strings (options=["a", "b"]) or dicts ({"value": "a", "label": "A"}).
process() builds a process_template dict and derives process_name from the CamelCased python_module when omitted (the Kratos convention — assign_scalar_variable_process → AssignScalarVariableProcess):
process_template=process(
"assign_scalar_variable_process",
parameters={"model_part_name": "$path", "variable_name": "TEMPERATURE",
"value": "$field:value", "interval": INTERVAL_TOTAL})Validation is eager: a wrong field type, an unknown process list/target, a duplicate id, or a parts_condition that names no condition raises ValueError naming the offending id while the file loads — the broken problemtype shows up as a disabled dropdown entry carrying that message instead of failing later at generate time.
Note: field ids and the keys of values stay exactly as you declare them (the built-ins use camelCase like timeStep) — only the API argument names and the ctx keys are snake_case.
Hooks
| Hook | Signature | Default when omitted / returning None |
|---|---|---|
solver_settings | (values, ctx) → dict | — required |
build_process | (cond, assignment, ctx) → dict | None | resolve the condition's process_template |
post_process | (project_parameters, ctx) → dict | unchanged document |
main_script | (ctx) → str | the standard MainKratos.py |
All hook arguments and return values are plain JSON data (dicts, lists, numbers, strings) — no Kratos objects.
The ctx dict
| Key | Contents |
|---|---|
mdpa_stem | mdpa file name without extension |
domain_size | 2 or 3 |
model_part_name, materials_file_name | from the declaration |
values | all section field values flattened |
assignments, materials | the raw user assignments |
parts_model_parts | dotted names of the Parts assignments |
skin_model_parts | dotted names of every non-Parts assignment |
sub_model_parts | all SubModelPart paths in the mesh |
collections | collection id → entries {name, type, parts, values, modelParts} (only when the problemtype declares collections) |