Templating System
MarrmotFlow uses a Jinja2-based templating system to generate MARRMOT model configuration files. This system provides flexibility in creating model instances and customizing parameters.
Overview
The templating system in MarrmotFlow:
Generates MATLAB code for MARRMOT models
Uses Jinja2 templates for flexibility
Supports multiple model structures
Allows parameter customization
Handles model-specific configurations
Template Structure
Default Template
MarrmotFlow includes a default template for MARRMOT models:
src/marrmotflow/templates/marrmot_models.m.jinja
This template contains the structure for generating MATLAB model files compatible with the MARRMOT toolbox.
Template Variables
The template system uses several variables that are populated during workflow execution:
% Model: {{ model_name }}
% Model number: {{ model_number }}
% Catchment: {{ catchment_name }}
% Generated: {{ timestamp }}
function [theta] = {{ function_name }}()
% Generated MARRMOT model configuration
% Model parameters
theta = [
{% for param in parameters %}
{{ param.value }}, % {{ param.name }} - {{ param.description }}
{% endfor %}
];
end
Using the Template System
Basic Usage
The templating system is used automatically during workflow execution:
from marrmotflow import MARRMOTWorkflow
# Templates are used automatically
workflow = MARRMOTWorkflow(
name="TemplatedWorkflow",
cat="catchments.shp",
forcing_files="climate_data.nc",
forcing_vars={"precip": "precipitation", "temp": "temperature"},
model_number=[7, 37] # Templates will be generated for both models
)
Direct Template Usage
You can also use the templating functions directly:
from marrmotflow.templating import render_models
# Render models directly
model_files = ["model_7.m", "model_37.m"]
rendered_models = render_models(model_files)
# Output is a dictionary with file names as keys and content as values
for filename, content in rendered_models.items():
print(f"Generated {filename}")
print(content[:200] + "...") # First 200 characters
Template Customization
Custom Templates
You can create custom templates for specific needs:
# Custom template path
from marrmotflow.templating import render_models
custom_template = "my_custom_template.m.jinja"
rendered = render_models(
model_files=["custom_model.m"],
template_jinja_path=custom_template
)
Template Context
Understanding the context passed to templates:
# Template context includes:
template_context = {
"model_number": 7,
"model_name": "HBV-96",
"catchment_id": "basin_001",
"catchment_name": "Example Basin",
"parameters": [
{"name": "TT", "value": 0.0, "description": "Temperature threshold"},
{"name": "C0", "value": 3.0, "description": "Degree-day factor"},
# ... more parameters
],
"forcing_data": {
"precipitation": "precip_data",
"temperature": "temp_data",
"pet": "pet_data"
},
"timestamp": "2024-01-01 12:00:00",
"workflow_name": "MyWorkflow"
}
Advanced Template Features
Conditional Logic
Templates can include conditional logic:
% Model configuration for {{ model_name }}
{% if model_number == 7 %}
% HBV-96 specific configuration
snow_routine = true;
{% elif model_number == 37 %}
% GR4J specific configuration
snow_routine = false;
{% endif %}
% Parameters
{% for param in parameters %}
{% if param.active %}
{{ param.name }} = {{ param.value }}; % {{ param.description }}
{% endif %}
{% endfor %}
Loops and Iterations
Templates support loops for repetitive content:
% Catchment data
{% for catchment in catchments %}
catchment_{{ loop.index }} = struct();
catchment_{{ loop.index }}.id = '{{ catchment.id }}';
catchment_{{ loop.index }}.name = '{{ catchment.name }}';
catchment_{{ loop.index }}.area = {{ catchment.area }};
{% endfor %}
Error Handling
Templates include error handling capabilities:
{% if not parameters %}
{{ raise("No parameters defined for model " + model_number|string) }}
{% endif %}
{% for param in parameters %}
{% if param.value is none %}
{{ raise("Parameter " + param.name + " has no value") }}
{% endif %}
{% endfor %}
Template Development
Creating Custom Templates
To create a custom template:
Create a new .jinja file:
% Custom MARRMOT Model Template
% Model: {{ model_name }} ({{ model_number }})
% Workflow: {{ workflow_name }}
% Generated: {{ timestamp }}
function [output] = run_{{ model_name|lower|replace('-', '_') }}(forcing_data)
% Custom model implementation
% Model parameters
{% for param in parameters %}
{{ param.name }} = {{ param.value }}; % {{ param.description }}
{% endfor %}
% Model logic here
output = struct();
output.discharge = []; % Model output
end
Register the template (if using custom template system):
from marrmotflow.templating import render_models
custom_models = render_models(
model_files=["custom_model.m"],
template_jinja_path="path/to/custom_template.m.jinja"
)
Template Best Practices
Organization
Keep templates organized and well-documented:
{#
Template: MARRMOT Model Generator
Purpose: Generate MATLAB code for MARRMOT models
Author: Your Name
Date: {{ timestamp }}
#}
% Generated MARRMOT Model
% Do not edit this file directly - it is auto-generated
Variable Naming
Use clear, descriptive variable names:
% Clear parameter definitions
{% for param in model_parameters %}
{{ param.matlab_name }} = {{ param.calibrated_value }}; % {{ param.physical_meaning }} [{{ param.units }}]
{% endfor %}
Template Testing
Validating Templates
Test your templates with different inputs:
def test_template(template_path, test_contexts):
"""Test template with various contexts."""
from jinja2 import Environment, FileSystemLoader
env = Environment(loader=FileSystemLoader('.'))
template = env.get_template(template_path)
for i, context in enumerate(test_contexts):
try:
result = template.render(context)
print(f"Test {i+1}: SUCCESS")
except Exception as e:
print(f"Test {i+1}: FAILED - {e}")
# Test contexts
test_contexts = [
{"model_number": 7, "parameters": []},
{"model_number": 37, "parameters": [{"name": "X1", "value": 100}]},
]
test_template("marrmot_models.m.jinja", test_contexts)
Template Debugging
Debug template issues:
# Enable template debugging
from jinja2 import Environment, FileSystemLoader, DebugUndefined
env = Environment(
loader=FileSystemLoader('.'),
undefined=DebugUndefined # Shows undefined variables
)
Integration with Workflow
Automatic Template Processing
Templates are processed automatically during workflow execution:
workflow = MARRMOTWorkflow(
name="AutoTemplating",
# ... other parameters
)
# Templates are generated and used internally
# Generated files can be accessed after workflow execution
Manual Template Generation
Generate templates manually for inspection:
from marrmotflow.templating import render_models
# Generate templates for specific models
model_codes = render_models(["model_7.m", "model_37.m"])
# Save to files for inspection
for filename, code in model_codes.items():
with open(f"generated_{filename}", 'w') as f:
f.write(code)
print(f"Saved generated_{filename}")
Future Extensions
The templating system is designed to be extensible:
Support for additional model formats
Custom parameter optimization templates
Integration with other modeling frameworks
Template sharing and version control
Best Practices Summary
Use descriptive names for all template variables
Include comprehensive comments in generated code
Test templates thoroughly with various inputs
Handle errors gracefully with appropriate error messages
Keep templates modular and easy to maintain
Document template purpose and usage
Version control templates along with code changes
Comments and Documentation
Include comprehensive comments:
% ================================================================ % MARRMOT Model: {{ model_name }} % ================================================================ % Description: {{ model_description }} % Reference: {{ model_reference }} % % Parameters: {% for param in parameters %} % {{ param.name }}: {{ param.description }} [{{ param.units }}] {% endfor %} % ================================================================