Welcome to musica!#
This tutorial will walk you through getting an installation working and running a box model using the musica python package.
NOTE: We’ll use
musicato refer to the python package developed as part of the larger MUlti-Scale Infrastructure for Chemistry and Aerosols (MUSICA) Project. To learn more about the MUSICA Project, see here.
Installation (optional)#
You probably arrived at this tutorial through binder. If so, please skip this section as we’ve taken care of installing everything for you.
If you want to install musica locally, it is available from pypi. Below are instructions that will install musica’s dependencies alongside all of the dependencies requires for this tutorial
pip install musica[tutorial]
We do recommend you install into some virtual environment. We’ve included instructions for both `uv <https://docs.astral.sh/uv/>`__, and `conda <https://docs.conda.io/projects/conda/en/latest/user-guide/getting-started.html>`__
uv (recommended)#
uv python install 3.13
uv venv --python 3.13
source .venv/bin/activate # macOS/Linux
.venv\Scripts\activate # Windows
uv pip install musica[tutorial] # bash
uv pip install 'musica[tutorial]' # zsh
Conda#
conda create --name musica python=3.13
conda activate musica
pip install musica[tutorial] # bash
pip install 'musica[tutorial]' # zsh
CLI#
The musica pacakge comes with a cli tool, musica-cli. This can be useful to copy out some of our examples, or printing the version. You can read more about its usage and options here. To verify your installation worked, you can ask it to print the version number
musica-cli --version
You should see something like
musica 0.15.0 (MICM 3.12.0, TUV-x 0.15.0, CARMA 4.0.0)
Using musica#
Let’s start off by importing musica and getting the versions in python.
Version checks#
[26]:
import musica
[27]:
print(musica.__version__)
0.15.0
Each of musica’s componenents can be imported as a separate package. There are currently three:
micm: ODE solvers and gas-phase chemistry package.
tuv-x**: Photolysis rate constant calculator.
carma**: Aerosol model.
NOTE: The unreliability of fortran compilers prevents inclusion of
tuv-xandcarmainmusicaon some systems (including Windows). Some of the cells below will fail if you are on one these systems. As legacy code is ported to modern languages, we expect to be able to include allmusicacomponents in all deployments.
[28]:
print(musica.micm.__version__)
3.12.0
[29]:
print(musica.tuvx.__version__)
0.15.0
[30]:
print(musica.carma.__version__)
4.0.0
To run chemistry you only need micm, which is our ODE solver. There are separate tutorials for tuv-x and carma so we will forget about them for now.
A simple gas-phase system from a configuration#
There are two ways to configure musica:
using a valid mechanism configuration in json/yaml
using the
musicaAPI.
We’ll use the configuration file approach in this first tutorial chapter. In the next chapter we’ll present the in-code approach, which we’ll use in the remaining examples.
We’ve provided several packaged example configurations with musica to make their usage easy. We will run a simple 3-species system below. We’ll start by importing the required parts of musica.
musica, this will give us access to all parts of musica, includingMICM, our gas-phase solverParser, which can read our mechanism filesfind_config_path, a utility function that will make it easy to load our example
[31]:
import musica
from musica.mechanism_configuration import Parser
from musica.utils import find_config_path
Before we build our model, let’s take a look at the configuration file to see how it’s organized
[32]:
import json
with open(find_config_path("v1", "analytical", "config.json")) as f:
config = json.load(f)
print(json.dumps(config, indent=2))
{
"name": "analytical test",
"reactions": [
{
"type": "ARRHENIUS",
"A": 0.004,
"reactants": [
{
"species name": "A",
"coefficient": 1.0
}
],
"products": [
{
"species name": "B",
"coefficient": 1.0
}
],
"gas phase": "gas"
},
{
"type": "ARRHENIUS",
"A": 0.004,
"reactants": [
{
"species name": "B",
"coefficient": 1.0
}
],
"products": [
{
"species name": "C",
"coefficient": 1.0
}
],
"gas phase": "gas"
}
],
"species": [
{
"name": "A"
},
{
"name": "B"
},
{
"name": "C"
}
],
"phases": [
{
"name": "gas",
"species": [
{
"name": "A"
},
{
"name": "B"
},
{
"name": "C"
}
]
}
],
"version": "1.0.0"
}
This simple mechanism comprises:
Three species (
A,B, andC) in the gas-phaseTwo Arrhenius reactions
A -> BB -> C
The format of the musica configuration is intended to describe the science without introducing details of how musica software is implemented in code.
Now, let’s build a real model
[33]:
parser = Parser()
mechanism = parser.parse(find_config_path("v1", "analytical", "config.json"))
[34]:
for reaction in mechanism.reactions:
print(f"[{reaction.type.name}] {reaction.to_equation()}")
[Arrhenius] A -> B
[Arrhenius] B -> C
Above, we had our parser read the mechanism file and return a valid mechanism. We can see the same reactions from the configuration file, now part of a mechanism instance.
We can now use this to create a MICM solver and a state.
[35]:
solver = musica.MICM(mechanism=mechanism)
state = solver.create_state()
Why do we need a solver and a state?
Conceptually, they fill two distinct roles:
The
stateis a container for time-varying properties for your system of interest (temperature, pressure, species concentrations, etc.)The
solveris the logic needed to advance astatein time. In our case we get the default MICM solver, which is a Rosenbrock 3-stage solver with a predetermined set of solver parameters. We could get a different ODE solver, or set different solver parameters by including additional arguments to theMICM()constructor.
Practically, having a separate solver and state allows you to create as many state containers as you want. Each state can all be advanced in time using the same solver instance. Each state instance can also be configured to represent multiple grid cells, which you’ll read more about in the next tutorial.
For now, we’ll keep things simple with a single one-grid-cell state. Let’s set the initial conditions and move the chemistry forward!
[36]:
state.set_conditions(temperatures=[298.15], pressures=[101325])
state.set_concentrations(
{
'A' : [1.0],
'B' : [0.0]
}
)
Now all that’s left is to solve and collect the concentrations as they change over time. For now, we’ll print them out. Later tutorials have more code to make some pretty graphs.
solver.solve takes in a state and timestep to solve for. Let’s solve for 10 seconds and see how things change
[37]:
def print_concentrations(t, state):
cs = state.get_concentrations()
print(f"{t}\t| {cs['A'][0]:.4f}\t| {cs['B'][0]:.4f}")
print_concentrations(0, state)
for t in range(1, 11):
solver.solve(state, 1)
print_concentrations(t, state)
0 | 1.0000 | 0.0000
1 | 0.9960 | 0.0040
2 | 0.9920 | 0.0079
3 | 0.9881 | 0.0119
4 | 0.9841 | 0.0157
5 | 0.9802 | 0.0196
6 | 0.9763 | 0.0234
7 | 0.9724 | 0.0272
8 | 0.9685 | 0.0310
9 | 0.9646 | 0.0347
10 | 0.9608 | 0.0384
And that’s your first MUSICA box model! Read on to learn how to define mechanisms in-code for solving and to do more some more complex tasks.