The common declaration format

The common declaration format is the format followed by all Mobius2 model, module, library and data files.

Although this specification is important to have for reference, it can be easier to work through some examples and follow the guide rather than reading this documentation.

Each Mobius2 file is a sequence of declarations that can again contain nested declarations in various ways.

A Mobius2 file must always be UTF-8. Except inside string literals, every character must be from the ascii (one-byte) subset of UTF-8. We recommend editing them with a plain text editor such as notepad++.

Files in the Mobius2 declaration format are whitespace agnostic, that is any number of whitespace, tabulars or newlines count as a single whitespace for the parser. Use of newlines and tabulars are therefore entirely for human readability reasons only.

File types

Mobius2 uses the following file types

  • Model files. These contain a single model declaration in the outer scope and nothing more. They have a .txt suffix.
  • Module/Library files. These contain one or more modules or libraries in the outer scope. They have a .txt suffix.
  • Data files. These contain a single data_set declaration in the outer scope. These have a .dat suffix.

Comments.

Anywhere you put a # symbol, the rest of the line is treated as a comment and will be disregarded by the framework. You can also create multi-line comments using /* and */.

Token types

Each Mobius2 file is parsed as a sequence of tokens that are then further interpreted as a sequence of declarations. A token is one of the following

Name Description Examples
identifier A sequence of ascii letters a-zA-Z possibly also containing underscores _, and possibly also containing digits 0-9, but not as the first character. soil, water, Q10, r_denit
quoted_string Text between qotation marks. If single quotation marks, it must be on a single line. If triple quotation marks, it could be on multiple lines. “Soil water”, “Denitrification rate”
integer A whole number. (In math scope (see below), a leading minus sign is interpreted as a separate token.) 8, -5, 3762
real Something representing a real number, possibly including E notation. 23.5, -4e2, 0.45e-4, nan
boolean A truth value true, false
Operator, delimiter or bracket These are many different token types used to group statements or create mathematical expressions +, -, *, /, |, %, &, !, =, !=, >, <, >=, <=, (, ), {, }, [, ], ?, ->, <-, ->>, =>, =>>, ,, .
Date A date value, only available in data files 1999-12-31, 2024-05-07
Time A time value, only available in data files 12:55:33, 05:30:00

Entities and identifiers

Most (but not all) declarations create a model entity. If a model entity is created you may be able to bind it to an identifier. You can then use that identifier to refer to the entity in other parts of the code.

An entity identifier is declared by writing it followed by a colon : and the rest of the declaration. Example:

my_identifier : <declaration>

The declaration type

The declaration type is chosen from one of several different pre-defined keywords. It is the first part of the declaration apart from the optional identifier. For instance

soil : compartment <rest of declaration>

where compartment is the declaration type and soil is the identifier.

The declaration arguments

A declaration can require zero or more arguments (and can some times allow different types of arguments). These are enclosed in parantheses ( .. ), and arguments are separated by commas ,. If there are no arguments, the parantheses are optional.

There are several types of arguments, and the required types depends on the declaration type.

  • Literal value. These can be numbers, boolean values or quoted strings, e.g. 8, 3.14, "Water", true.
  • Entity. This is either
    • The identifier of another entity (it must be declared somewhere in the same scope, but not necessarily before the current declaration).
    • An inlined declaration creating the entity. The inlined declaration follows the same format as the outer declaration.
  • Location. This is the location of a state variable, i.e. a .-separated list of component identifiers.

For example,

soil : compartment("Soil")

par_group("Soil hydrology", soil) <rest of declaration>

par_group("River parameters", river : compartment("River")) <rest of declaration>

A string literal must be on a single line if it is enclosed in single quotation marks, or can be across multiple lines if it is enclosed in triple quotation marks

"""
Hello, biochemists!
"""

Unit declarations

Units are declared using a completely separate format. See the unit documentation.

The declaration body

Some declarations can have or require a declaration body. Declaration bodies are always enclosed in curly brackets { .. }. There are two main types of declaration bodies: Declaration body and math body. The body type of a declaration depends on the declaration type.

Declaration scope bodies

Declaration scope bodies contain a sequence of other declarations. They create a scope so that identifiers declared inside the scope are not visible outside (The exception are parameter groups that export their parameter symbols to the outer scope). However identifiers in the outside scope are visible in the inside one.

Docstrings

A declaration scope body can also contain a single so-called docstring. This can be used by the model creator (or dataset creator) to provide the user with information about how to use this model component. The docstring is a free-floating (typically multiline) string literal. It can contain Markdown-formatted text.

Math scope bodies

A math body is a <block> described in the Mobius2 math format. The math expression typically computes a single value. Math scope bodies can reference certain types of entities that are visible in the outer scope, but you can not declare entities there, only local variables. Unlike the declaration scope, the math scope also has a strict ordering of expressions.

Special

Some declarations have a body type that is unique to its declaration type. These will be explained separately.

Examples

module("The superduper hydrology module", version(1, 0, 0),
	soil : compartment,
	groundwater : compartment,
	water : quantity
) {
"""
A description of the module. It does
* Compute A.
* Some other things
* etc.
"""

	par_group("Soil hydrology", soil) {
		tc : par_real("Soil water time constant", [day], 5)
		fc : par_real("Soil field capacity", [m m], 50)
	}
	
	var(soil.water, [m m], "Soil water")
	
	flux(soil.water.flow, groundwater.water, [m m, day-1], "Recharge") {
		min(soil.water - fc, 0)/tc
	}
}

In the above example, the module declaration has a body with its own scope containing one par_group, one var declaration and a flux declaration. The par_group creates its own inner scope, but exports the parameter symbols from within it to the module scope. The flux declaration has a math body that is able to reference entities in the module scope.

Names

Many declarations have a quoted string as their first argument or last. This argument is called a name, and can be used to interact with the entity from a system external to the model code, e.g. in MobiView2, mobipy or in the data format.

Names are also scoped in a similar way to identifiers (but parameter names are unlike parameter identifiers scoped inside their parameter group).

Notes

Following the main body (if it has one), a declaration can have zero or more notes (depending on the declaration type). These can provide additional information about the declaration. A note is started with a @, followed by a keyword. The set of allowed note keywords is different per declaration type. After this follows potentially an argument list (formatted the same way as for the main declaration), and potentiallt a body for the note. What arguments and/or body is required depends both on the main declaration type and the note type.

Example:

# The @initial note has no arguments, but has a math body.
var(soil.water, [m m], "Soil water") @initial { fc }

Data blocks

In a data file only, a declaration can have a single data block (there is no separate data block per note).

A data block is enclosed in square brackets [ .. ]. A data block can either encode a list, a map, or a directed graph.

Lists

A list is a space-separated list of single values. The single values are either numbers, identifiers or quoted strings.

Examples:

[0 8 3.2]
["Arable" "Semi-natural"]

Maps

A map always opens with a ! after the opening bracket. It is then a space-separated list of key : value pairs. The key is always a single value (number, identifier or string), while the value is a single value or a nested data block (either list or map).

Examples:

[!
	0 : 4
	5 : 10
]

[!
	"Breiangen" : [ 4 5 6 ]
	"Vestfjord" : [ 7 8 9 ]
]

Directed graph

A directed graph only appears as the data block for a directed graph connection in the data set. For now, see the connection data documentation.


Table of contents


© Norsk Institutt for Vannforskning (The Norwegian Institute for Water Research), NIVA 2024.