musica 0.11.1.4__cp312-cp312-win_amd64.whl → 0.12.1__cp312-cp312-win_amd64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of musica might be problematic. Click here for more details.

Files changed (76) hide show
  1. musica/CMakeLists.txt +28 -2
  2. musica/__init__.py +9 -49
  3. musica/_musica.cp312-win_amd64.pyd +0 -0
  4. musica/_version.py +1 -1
  5. musica/backend.py +41 -0
  6. musica/binding_common.cpp +23 -6
  7. musica/carma.cpp +911 -0
  8. musica/carma.py +1729 -0
  9. musica/constants.py +3 -0
  10. musica/cpu_binding.cpp +2 -1
  11. musica/cuda.py +4 -1
  12. musica/examples/__init__.py +1 -0
  13. musica/examples/carma_aluminum.py +123 -0
  14. musica/examples/carma_sulfate.py +245 -0
  15. musica/examples/examples.py +165 -0
  16. musica/examples/sulfate_box_model.py +439 -0
  17. musica/examples/ts1_latin_hypercube.py +245 -0
  18. musica/gpu_binding.cpp +2 -1
  19. musica/main.py +89 -0
  20. musica/mechanism_configuration/__init__.py +1 -0
  21. musica/mechanism_configuration/aqueous_equilibrium.py +274 -0
  22. musica/mechanism_configuration/arrhenius.py +307 -0
  23. musica/mechanism_configuration/branched.py +299 -0
  24. musica/mechanism_configuration/condensed_phase_arrhenius.py +309 -0
  25. musica/mechanism_configuration/condensed_phase_photolysis.py +88 -0
  26. musica/mechanism_configuration/emission.py +71 -0
  27. musica/mechanism_configuration/first_order_loss.py +174 -0
  28. musica/mechanism_configuration/henrys_law.py +44 -0
  29. musica/mechanism_configuration/mechanism_configuration.py +234 -0
  30. musica/mechanism_configuration/phase.py +47 -0
  31. musica/mechanism_configuration/photolysis.py +88 -0
  32. musica/mechanism_configuration/reactions.py +73 -0
  33. musica/mechanism_configuration/simpol_phase_transfer.py +217 -0
  34. musica/mechanism_configuration/species.py +91 -0
  35. musica/mechanism_configuration/surface.py +94 -0
  36. musica/mechanism_configuration/ternary_chemical_activation.py +352 -0
  37. musica/mechanism_configuration/troe.py +352 -0
  38. musica/mechanism_configuration/tunneling.py +250 -0
  39. musica/mechanism_configuration/user_defined.py +88 -0
  40. musica/mechanism_configuration/utils.py +10 -0
  41. musica/mechanism_configuration/wet_deposition.py +52 -0
  42. musica/mechanism_configuration.cpp +184 -96
  43. musica/musica.cpp +48 -61
  44. musica/test/examples/v1/full_configuration/full_configuration.json +67 -35
  45. musica/test/examples/v1/full_configuration/full_configuration.yaml +44 -20
  46. musica/test/{test_analytical.py → integration/test_analytical.py} +1 -2
  47. musica/test/integration/test_carma.py +227 -0
  48. musica/test/integration/test_carma_aluminum.py +11 -0
  49. musica/test/integration/test_carma_sulfate.py +16 -0
  50. musica/test/integration/test_sulfate_box_model.py +34 -0
  51. musica/test/integration/test_tuvx.py +62 -0
  52. musica/test/unit/test_parser.py +64 -0
  53. musica/test/unit/test_serializer.py +69 -0
  54. musica/test/{test_parser.py → unit/test_util_full_mechanism.py} +409 -404
  55. musica/tools/prepare_build_environment_linux.sh +39 -32
  56. musica/tools/prepare_build_environment_macos.sh +1 -0
  57. musica/tuvx.cpp +93 -0
  58. musica/tuvx.py +199 -0
  59. musica/types.py +104 -63
  60. {musica-0.11.1.4.dist-info → musica-0.12.1.dist-info}/METADATA +100 -84
  61. musica-0.12.1.dist-info/RECORD +69 -0
  62. {musica-0.11.1.4.dist-info → musica-0.12.1.dist-info}/WHEEL +1 -1
  63. musica-0.12.1.dist-info/entry_points.txt +3 -0
  64. musica-0.12.1.dist-info/licenses/AUTHORS.md +59 -0
  65. musica/mechanism_configuration.py +0 -1291
  66. musica/test/examples/v0/config.json +0 -7
  67. musica/test/examples/v0/config.yaml +0 -3
  68. musica/test/examples/v0/reactions.json +0 -193
  69. musica/test/examples/v0/reactions.yaml +0 -142
  70. musica/test/examples/v0/species.json +0 -40
  71. musica/test/examples/v0/species.yaml +0 -19
  72. musica/test/tuvx.py +0 -10
  73. musica/tools/prepare_build_environment_windows.sh +0 -22
  74. musica-0.11.1.4.dist-info/RECORD +0 -33
  75. /musica/test/{test_chapman.py → integration/test_chapman.py} +0 -0
  76. {musica-0.11.1.4.dist-info → musica-0.12.1.dist-info}/licenses/LICENSE +0 -0
@@ -1,43 +1,50 @@
1
- #! /bin/bash
2
-
1
+ #!/bin/bash
3
2
  set -e
4
3
  set -x
5
4
 
6
- # Update the mirror list to use vault.centos.org
7
- sed -i s/mirror.centos.org/vault.centos.org/g /etc/yum.repos.d/*.repo
8
- sed -i s/^#.*baseurl=http/baseurl=http/g /etc/yum.repos.d/*.repo
9
- sed -i s/^mirrorlist=http/#mirrorlist=http/g /etc/yum.repos.d/*.repo
5
+ target_arch="$(uname -m)"
6
+ echo "Detected target_arch: $target_arch"
10
7
 
11
- sed -i 's/mirrorlist/#mirrorlist/g' /etc/yum.repos.d/CentOS-*
12
- sed -i 's|#baseurl=http://mirror.centos.org|baseurl=http://vault.centos.org|g' /etc/yum.repos.d/CentOS-*
8
+ # Set package manager based on architecture
9
+ # x86_64 and aarch64 use manylinux_2_28 (AlmaLinux 8) with dnf
10
+ # i686 uses manylinux2014 (CentOS 7) with yum
11
+ if [ "$target_arch" = "i686" ]; then
12
+ PKG_MGR="yum"
13
+
14
+ # CentOS 7 is EOL, so we need to use vault.centos.org for i686 builds
15
+ # Replace the repo files to point to vault.centos.org
16
+ sed -i 's/mirrorlist/#mirrorlist/g' /etc/yum.repos.d/CentOS-*.repo
17
+ sed -i 's|#baseurl=http://mirror.centos.org|baseurl=http://vault.centos.org|g' /etc/yum.repos.d/CentOS-*.repo
18
+ else
19
+ PKG_MGR="dnf"
20
+ fi
13
21
 
14
- yum install -y zip tree wget
22
+ echo "Using package manager: $PKG_MGR"
15
23
 
16
- # Use CIBW_ARCHS or CIBW_ARCH if set, else fallback to uname -m
17
- if [ -n "$CIBW_ARCHS" ]; then
18
- target_arch="$CIBW_ARCHS"
19
- elif [ -n "$CIBW_ARCH" ]; then
20
- target_arch="$CIBW_ARCH"
21
- else
22
- target_arch="$(uname -m)"
24
+ $PKG_MGR -y update
25
+
26
+ if [ "$target_arch" = "x86_64" ] || [ "$target_arch" = "aarch64" ]; then
27
+ # For manylinux_2_28 (AlmaLinux 8), epel-release is required to get netcdf, for some reason
28
+ $PKG_MGR install -y epel-release
29
+ $PKG_MGR install -y netcdf-devel netcdf-fortran-devel
23
30
  fi
24
31
 
25
- echo "Detected target_arch: $target_arch"
32
+ $PKG_MGR install -y tree wget zip lapack-devel
26
33
 
27
34
  if [ "$target_arch" = "x86_64" ]; then
28
- # Install CUDA 12.2 for x86_64:
29
- yum-config-manager --add-repo https://developer.download.nvidia.com/compute/cuda/repos/rhel8/x86_64/cuda-rhel8.repo
30
- # error mirrorlist.centos.org doesn't exists anymore.
31
- sed -i s/mirror.centos.org/vault.centos.org/g /etc/yum.repos.d/*.repo
32
- sed -i s/^#.*baseurl=http/baseurl=http/g /etc/yum.repos.d/*.repo
33
- sed -i s/^mirrorlist=http/#mirrorlist=http/g /etc/yum.repos.d/*.repo
34
- yum install --setopt=obsoletes=0 -y \
35
- cuda-nvcc-12-2 \
36
- cuda-cudart-devel-12-2 \
37
- libcurand-devel-12-2 \
38
- libcublas-devel-12-2
39
- ln -s cuda-12.2 /usr/local/cuda
40
-
35
+ # Install CUDA 12.8 for x86_64 on AlmaLinux 8 (manylinux_2_28) - supports GCC 14
36
+ dnf config-manager --add-repo https://developer.download.nvidia.com/compute/cuda/repos/rhel8/x86_64/cuda-rhel8.repo
37
+ dnf install --setopt=obsoletes=0 -y \
38
+ cuda-nvcc-12-8 \
39
+ cuda-cudart-devel-12-8 \
40
+ libcurand-devel-12-8 \
41
+ libcublas-devel-12-8
42
+ ln -sf cuda-12.8 /usr/local/cuda
43
+
44
+ # Verify CUDA installation
45
+ echo "=== CUDA Installation Verification ==="
46
+ /usr/local/cuda/bin/nvcc --version
47
+
41
48
  # list the installed CUDA packages
42
- tree -L 4 /usr/local/cuda-12.2
43
- fi
49
+ # tree -L 4 /usr/local/cuda
50
+ fi
@@ -0,0 +1 @@
1
+ brew install netcdf netcdf-fortran lapack
musica/tuvx.cpp ADDED
@@ -0,0 +1,93 @@
1
+ #include "binding_common.hpp"
2
+
3
+ #include <musica/tuvx/tuvx.hpp>
4
+
5
+ #include <pybind11/numpy.h>
6
+ #include <pybind11/pybind11.h>
7
+ #include <pybind11/stl.h>
8
+
9
+ namespace py = pybind11;
10
+
11
+ void bind_tuvx(py::module_& tuvx)
12
+ {
13
+ tuvx.def("_get_tuvx_version", []() { return musica::TUVX::GetVersion(); }, "Get the version of the TUV-x instance");
14
+
15
+ tuvx.def(
16
+ "_create_tuvx",
17
+ [](const char* config_path)
18
+ {
19
+ try
20
+ {
21
+ auto tuvx_instance = new musica::TUVX();
22
+ tuvx_instance->CreateFromConfigOnly(config_path);
23
+ return reinterpret_cast<std::uintptr_t>(tuvx_instance);
24
+ }
25
+ catch (const std::exception& e)
26
+ {
27
+ throw py::value_error(
28
+ "Error creating TUV-x instance from config file: " + std::string(config_path) + " - " + e.what());
29
+ }
30
+ },
31
+ "Create a TUV-x instance from a JSON configuration file");
32
+
33
+ tuvx.def(
34
+ "_delete_tuvx",
35
+ [](std::uintptr_t tuvx_ptr)
36
+ {
37
+ musica::TUVX* tuvx_instance = reinterpret_cast<musica::TUVX*>(tuvx_ptr);
38
+ delete tuvx_instance;
39
+ },
40
+ "Delete a TUV-x instance");
41
+
42
+ tuvx.def(
43
+ "_run_tuvx",
44
+ [](std::uintptr_t tuvx_ptr)
45
+ {
46
+ musica::TUVX* tuvx_instance = reinterpret_cast<musica::TUVX*>(tuvx_ptr);
47
+
48
+ // Get dimensions
49
+ int n_photolysis = tuvx_instance->GetPhotolysisRateCount();
50
+
51
+ int n_heating = tuvx_instance->GetHeatingRateCount();
52
+
53
+ int n_layers = tuvx_instance->GetNumberOfLayers();
54
+
55
+ int n_sza_steps = tuvx_instance->GetNumberOfSzaSteps();
56
+
57
+ // Allocate output arrays (3D: sza_step, layer, reaction/heating_type)
58
+ std::vector<double> photolysis_rates(n_sza_steps * n_layers * n_photolysis);
59
+ std::vector<double> heating_rates(n_sza_steps * n_layers * n_heating);
60
+
61
+ // Run TUV-x (everything comes from the JSON config)
62
+ tuvx_instance->RunFromConfig(photolysis_rates.data(), heating_rates.data());
63
+
64
+ // Return as numpy arrays with shape (n_sza_steps, n_layers, n_reactions/n_heating)
65
+ py::array_t<double> py_photolysis =
66
+ py::array_t<double>({ n_sza_steps, n_layers, n_photolysis }, photolysis_rates.data());
67
+ py::array_t<double> py_heating = py::array_t<double>({ n_sza_steps, n_layers, n_heating }, heating_rates.data());
68
+
69
+ return py::make_tuple(py_photolysis, py_heating);
70
+ },
71
+ "Run TUV-x (all parameters come from JSON config)",
72
+ py::arg("tuvx_instance"));
73
+
74
+ tuvx.def(
75
+ "_get_photolysis_rate_names",
76
+ [](std::uintptr_t tuvx_ptr)
77
+ {
78
+ musica::TUVX* tuvx_instance = reinterpret_cast<musica::TUVX*>(tuvx_ptr);
79
+
80
+ return tuvx_instance->GetPhotolysisRateNames();
81
+ },
82
+ "Get photolysis rate names");
83
+
84
+ tuvx.def(
85
+ "_get_heating_rate_names",
86
+ [](std::uintptr_t tuvx_ptr)
87
+ {
88
+ musica::TUVX* tuvx_instance = reinterpret_cast<musica::TUVX*>(tuvx_ptr);
89
+
90
+ return tuvx_instance->GetHeatingRateNames();
91
+ },
92
+ "Get heating rate names");
93
+ }
musica/tuvx.py ADDED
@@ -0,0 +1,199 @@
1
+ """
2
+ TUV-x photolysis calculator Python interface.
3
+
4
+ This module provides a simplified Python interface to the TUV-x photolysis calculator.
5
+ It allows users to create a TUV-x instance from a JSON configuration file and
6
+ calculate photolysis rates and heating rates.
7
+
8
+ Note: TUV-x is only available on macOS and Linux platforms.
9
+ """
10
+
11
+ import os
12
+ import json
13
+ import tempfile
14
+ from typing import Dict, Tuple, List
15
+ import numpy as np
16
+ from . import backend
17
+
18
+ _backend = backend.get_backend()
19
+
20
+ version = _backend._tuvx._get_tuvx_version() if backend.tuvx_available() else None
21
+
22
+
23
+ class TUVX:
24
+ """
25
+ A Python interface to the TUV-x photolysis calculator.
26
+
27
+ This class provides a simplified interface that only requires a JSON configuration
28
+ file to set up and run TUV-x calculations. All parameters (solar zenith angle,
29
+ earth-sun distance, atmospheric profiles, etc.) are specified in the JSON config.
30
+ """
31
+
32
+ def __init__(self, config_path: str):
33
+ """
34
+ Initialize a TUV-x instance from a configuration file.
35
+
36
+ Args:
37
+ config_path: Path to the JSON configuration file
38
+
39
+ Raises:
40
+ FileNotFoundError: If the configuration file doesn't exist
41
+ ValueError: If TUV-x initialization fails or TUVX is not available
42
+ """
43
+ if not backend.tuvx_available():
44
+ raise ValueError("TUV-x backend is not available on windows.")
45
+
46
+ if not os.path.exists(config_path):
47
+ raise FileNotFoundError(
48
+ f"Configuration file not found: {config_path}")
49
+
50
+ self._tuvx_instance = _backend._tuvx._create_tuvx(config_path)
51
+ self._config_path = config_path
52
+
53
+ # Cache the names for efficiency
54
+ self._photolysis_names = None
55
+ self._heating_names = None
56
+
57
+ def __del__(self):
58
+ """Clean up the TUV-x instance."""
59
+ if hasattr(self, '_tuvx_instance') and self._tuvx_instance is not None:
60
+ _backend._tuvx._delete_tuvx(self._tuvx_instance)
61
+
62
+ @property
63
+ def photolysis_rate_names(self) -> List[str]:
64
+ """
65
+ Get the names of photolysis rates.
66
+
67
+ Returns:
68
+ List of photolysis rate names
69
+ """
70
+ if self._photolysis_names is None:
71
+ self._photolysis_names = _backend._tuvx._get_photolysis_rate_names(
72
+ self._tuvx_instance)
73
+ return self._photolysis_names
74
+
75
+ @property
76
+ def heating_rate_names(self) -> List[str]:
77
+ """
78
+ Get the names of heating rates.
79
+
80
+ Returns:
81
+ List of heating rate names
82
+ """
83
+ if self._heating_names is None:
84
+ self._heating_names = _backend._tuvx._get_heating_rate_names(
85
+ self._tuvx_instance)
86
+ return self._heating_names
87
+
88
+ def run(self) -> Tuple[np.ndarray, np.ndarray]:
89
+ """
90
+ Run the TUV-x photolysis calculator.
91
+
92
+ All parameters (solar zenith angle, Earth-Sun distance, atmospheric profiles,
93
+ etc.) are read from the JSON configuration file.
94
+
95
+ Returns:
96
+ Tuple of (photolysis_rate_constants, heating_rates) as numpy arrays
97
+ - photolysis_rate_constants: Shape (n_layers, n_reactions) [s^-1]
98
+ - heating_rates: Shape (n_layers, n_heating_rates) [K s^-1]
99
+ """
100
+ photolysis_rates, heating_rates = _backend._tuvx._run_tuvx(
101
+ self._tuvx_instance)
102
+
103
+ return photolysis_rates, heating_rates
104
+
105
+ def get_photolysis_rate_constant(
106
+ self,
107
+ reaction_name: str,
108
+ photolysis_rates: np.ndarray
109
+ ) -> np.ndarray:
110
+ """
111
+ Extract photolysis rate constants for a specific reaction.
112
+
113
+ Args:
114
+ reaction_name: Name of the photolysis reaction
115
+ photolysis_rates: Output from run() method
116
+
117
+ Returns:
118
+ 1D array of photolysis rate constants for all layers [s^-1]
119
+
120
+ Raises:
121
+ KeyError: If reaction_name is not found
122
+ """
123
+ names = self.photolysis_rate_names
124
+ if reaction_name not in names:
125
+ raise KeyError(
126
+ f"Reaction '{reaction_name}' not found. "
127
+ f"Available reactions: {names}"
128
+ )
129
+
130
+ reaction_index = names.index(reaction_name)
131
+ return photolysis_rates[:, reaction_index]
132
+
133
+ def get_heating_rate(
134
+ self,
135
+ rate_name: str,
136
+ heating_rates: np.ndarray
137
+ ) -> np.ndarray:
138
+ """
139
+ Extract heating rates for a specific rate type.
140
+
141
+ Args:
142
+ rate_name: Name of the heating rate
143
+ heating_rates: Output from run() method
144
+
145
+ Returns:
146
+ 1D array of heating rates for all layers [K s^-1]
147
+
148
+ Raises:
149
+ KeyError: If rate_name is not found
150
+ """
151
+ names = self.heating_rate_names
152
+ if rate_name not in names:
153
+ raise KeyError(
154
+ f"Heating rate '{rate_name}' not found. "
155
+ f"Available rates: {names}"
156
+ )
157
+
158
+ rate_index = names.index(rate_name)
159
+ return heating_rates[:, rate_index]
160
+
161
+ @staticmethod
162
+ def create_config_from_dict(config_dict: Dict) -> 'TUVX':
163
+ """
164
+ Create a TUVX instance from a configuration dictionary.
165
+
166
+ Args:
167
+ config_dict: Configuration dictionary
168
+
169
+ Returns:
170
+ TUVX instance initialized with the configuration
171
+
172
+ Raises:
173
+ ValueError: If TUV-x backend is not available
174
+ FileNotFoundError: If required data files are not found
175
+ """
176
+ with tempfile.NamedTemporaryFile(
177
+ mode='w', suffix='.json', delete=True) as temp_file:
178
+ json.dump(config_dict, temp_file, indent=2)
179
+ temp_file.flush() # Ensure all data is written to disk
180
+ return TUVX(temp_file.name)
181
+
182
+ @staticmethod
183
+ def create_config_from_json_string(json_string: str) -> 'TUVX':
184
+ """
185
+ Create a TUVX instance from a JSON configuration string.
186
+
187
+ Args:
188
+ json_string: JSON configuration as string
189
+
190
+ Returns:
191
+ TUVX instance initialized with the configuration
192
+
193
+ Raises:
194
+ json.JSONDecodeError: If json_string is not valid JSON
195
+ ValueError: If TUV-x backend is not available
196
+ FileNotFoundError: If required data files are not found
197
+ """
198
+ config_dict = json.loads(json_string)
199
+ return TUVX.create_config_from_dict(config_dict)
musica/types.py CHANGED
@@ -3,50 +3,36 @@
3
3
  #
4
4
  # This file is part of the musica Python package.
5
5
  # For more information, see the LICENSE file in the top-level directory of this distribution.
6
- from typing import Optional, Dict, List, Union, Tuple
6
+ from __future__ import annotations
7
+ from .constants import GAS_CONSTANT
8
+ from typing import Optional, Dict, List, Union, Tuple, TYPE_CHECKING, Any
7
9
  from os import PathLike
8
10
  import math
9
- from musica import (
10
- _Conditions,
11
- _SolverType,
12
- _Solver,
13
- _State,
14
- _create_solver,
15
- _create_solver_from_mechanism,
16
- _create_state,
17
- _micm_solve,
18
- _vector_size,
19
- _species_ordering,
20
- _user_defined_rate_parameters_ordering,
21
- )
22
- import musica.mechanism_configuration as mc
23
-
24
- AVOGADRO = 6.02214076e23 # mol^-1
25
- BOLTZMANN = 1.380649e-23 # J K^-1
26
- GAS_CONSTANT = AVOGADRO * BOLTZMANN # J K^-1 mol^-1
27
11
 
28
- FilePath = Union[str, "PathLike[str]"]
29
-
30
-
31
- def _get_vector_matrix_indices(row_index: int, column_index: int, vector_size: int) -> Tuple[int, int]:
32
- """
33
- Get the row and column indices for a matrix given the row and column indices for a vector.
12
+ # Import backend symbols from the backend module
13
+ from . import backend
14
+
15
+ # Get all the backend symbols we need
16
+ _backend = backend.get_backend()
17
+ _Conditions = _backend._core._Conditions
18
+ _SolverType = _backend._core._SolverType
19
+ _create_solver = _backend._core._create_solver
20
+ _create_solver_from_mechanism = _backend._core._create_solver_from_mechanism
21
+ _create_state = _backend._core._create_state
22
+ _micm_solve = _backend._core._micm_solve
23
+ _vector_size = _backend._core._vector_size
24
+ _species_ordering = _backend._core._species_ordering
25
+ _user_defined_rate_parameters_ordering = _backend._core._user_defined_rate_parameters_ordering
26
+ mc = _backend._mechanism_configuration
27
+
28
+
29
+ # For type hints
30
+ if TYPE_CHECKING:
31
+ from .mechanism_configuration import Mechanism
32
+ else:
33
+ Mechanism = mc._Mechanism
34
34
 
35
- Parameters
36
- ----------
37
- row_index : int
38
- Row index of the vector.
39
- column_index : int
40
- Column index of the vector.
41
- vector_size : int
42
- Size of the vector.
43
-
44
- Returns
45
- -------
46
- tuple[int, int]
47
- Index for which state matrix to use and the index in that matrix'x underlying data vector.
48
- """
49
- return (row_index // vector_size, column_index * (vector_size - 1) + row_index % vector_size)
35
+ FilePath = Union[str, "PathLike[str]"]
50
36
 
51
37
 
52
38
  class Conditions(_Conditions):
@@ -92,20 +78,22 @@ class State():
92
78
  State class for the MICM solver. It contains the initial conditions and species concentrations.
93
79
  """
94
80
 
95
- def __init__(self, solver: _Solver, number_of_grid_cells: int, vector_size: int = 0):
81
+ def __init__(self, solver: Any, number_of_grid_cells: int, vector_size: int = 0):
96
82
  if number_of_grid_cells < 1:
97
83
  raise ValueError("number_of_grid_cells must be greater than 0.")
98
84
  super().__init__()
99
85
  self.__states = [
100
- _create_state(solver, min(vector_size, number_of_grid_cells - i * vector_size))
86
+ _create_state(solver, min(
87
+ vector_size, number_of_grid_cells - i * vector_size))
101
88
  for i in range(math.ceil(number_of_grid_cells / vector_size))
102
89
  ] if vector_size > 0 else [_create_state(solver, number_of_grid_cells)]
103
90
  self.__species_ordering = _species_ordering(self.__states[0])
104
- self.__user_defined_rate_parameters_ordering = _user_defined_rate_parameters_ordering(self.__states[0])
91
+ self.__user_defined_rate_parameters_ordering = _user_defined_rate_parameters_ordering(
92
+ self.__states[0])
105
93
  self.__number_of_grid_cells = number_of_grid_cells
106
94
  self.__vector_size = vector_size
107
95
 
108
- def get_internal_states(self) -> List[_State]:
96
+ def get_internal_states(self) -> List[Any]:
109
97
  """
110
98
  Get the internal states of the MICM solver.
111
99
 
@@ -134,13 +122,15 @@ class State():
134
122
  if isinstance(value, float) or isinstance(value, int):
135
123
  value = [value]
136
124
  if len(value) != self.__number_of_grid_cells:
137
- raise ValueError(f"Concentration list for {name} must have length {self.__number_of_grid_cells}.")
125
+ raise ValueError(
126
+ f"Concentration list for {name} must have length {self.__number_of_grid_cells}.")
138
127
  # Counter 'k' is used to map grid cell indices across multiple state segments.
139
128
  k = 0
140
129
  for state in self.__states:
141
130
  cell_stride, species_stride = state.concentration_strides()
142
131
  for i_cell in range(state.number_of_grid_cells()):
143
- state.concentrations[i_species * species_stride + i_cell * cell_stride] = value[k]
132
+ state.concentrations[i_species *
133
+ species_stride + i_cell * cell_stride] = value[k]
144
134
  k += 1
145
135
 
146
136
  def set_user_defined_rate_parameters(
@@ -157,7 +147,8 @@ class State():
157
147
  """
158
148
  for name, value in user_defined_rate_parameters.items():
159
149
  if name not in self.__user_defined_rate_parameters_ordering:
160
- raise ValueError(f"User-defined rate parameter {name} not found in the mechanism.")
150
+ raise ValueError(
151
+ f"User-defined rate parameter {name} not found in the mechanism.")
161
152
  i_param = self.__user_defined_rate_parameters_ordering[name]
162
153
  if isinstance(value, float) or isinstance(value, int):
163
154
  value = [value]
@@ -169,10 +160,10 @@ class State():
169
160
  for state in self.__states:
170
161
  cell_stride, param_stride = state.user_defined_rate_parameter_strides()
171
162
  for i_cell in range(state.number_of_grid_cells()):
172
- state.user_defined_rate_parameters[i_param * param_stride + i_cell * cell_stride] = value[k]
163
+ state.user_defined_rate_parameters[i_param *
164
+ param_stride + i_cell * cell_stride] = value[k]
173
165
  k += 1
174
166
 
175
-
176
167
  def set_conditions(self,
177
168
  temperatures: Union[Union[float, int], List[Union[float, int]]],
178
169
  pressures: Union[Union[float, int], List[Union[float, int]]],
@@ -194,22 +185,28 @@ class State():
194
185
  """
195
186
  if isinstance(temperatures, float) or isinstance(temperatures, int):
196
187
  if self.__number_of_grid_cells > 1:
197
- raise ValueError(f"temperatures must be a list of length {self.__number_of_grid_cells}.")
188
+ raise ValueError(
189
+ f"temperatures must be a list of length {self.__number_of_grid_cells}.")
198
190
  temperatures = [temperatures]
199
191
  if isinstance(pressures, float) or isinstance(pressures, int):
200
192
  if self.__number_of_grid_cells > 1:
201
- raise ValueError(f"pressures must be a list of length {self.__number_of_grid_cells}.")
193
+ raise ValueError(
194
+ f"pressures must be a list of length {self.__number_of_grid_cells}.")
202
195
  pressures = [pressures]
203
196
  if air_densities is not None and (isinstance(air_densities, float) or isinstance(air_densities, int)):
204
197
  if self.__number_of_grid_cells > 1:
205
- raise ValueError(f"air_densities must be a list of length {self.__number_of_grid_cells}.")
198
+ raise ValueError(
199
+ f"air_densities must be a list of length {self.__number_of_grid_cells}.")
206
200
  air_densities = [air_densities]
207
201
  if len(temperatures) != self.__number_of_grid_cells:
208
- raise ValueError(f"temperatures must be a list of length {self.__number_of_grid_cells}.")
202
+ raise ValueError(
203
+ f"temperatures must be a list of length {self.__number_of_grid_cells}.")
209
204
  if len(pressures) != self.__number_of_grid_cells:
210
- raise ValueError(f"pressures must be a list of length {self.__number_of_grid_cells}.")
205
+ raise ValueError(
206
+ f"pressures must be a list of length {self.__number_of_grid_cells}.")
211
207
  if air_densities is not None and len(air_densities) != self.__number_of_grid_cells:
212
- raise ValueError(f"air_densities must be a list of length {self.__number_of_grid_cells}.")
208
+ raise ValueError(
209
+ f"air_densities must be a list of length {self.__number_of_grid_cells}.")
213
210
  k = 0
214
211
  for state in self.__states:
215
212
  for condition in state.conditions:
@@ -272,11 +269,36 @@ class State():
272
269
  conditions["air_density"] = []
273
270
  for state in self.__states:
274
271
  for i_cell in range(state.number_of_grid_cells()):
275
- conditions["temperature"].append(state.conditions[i_cell].temperature)
276
- conditions["pressure"].append(state.conditions[i_cell].pressure)
277
- conditions["air_density"].append(state.conditions[i_cell].air_density)
272
+ conditions["temperature"].append(
273
+ state.conditions[i_cell].temperature)
274
+ conditions["pressure"].append(
275
+ state.conditions[i_cell].pressure)
276
+ conditions["air_density"].append(
277
+ state.conditions[i_cell].air_density)
278
278
  return conditions
279
279
 
280
+ def get_species_ordering(self) -> Dict[str, int]:
281
+ """
282
+ Get the species ordering for the state.
283
+
284
+ Returns
285
+ -------
286
+ Dict[str, int]
287
+ Dictionary of species names and their indices.
288
+ """
289
+ return self.__species_ordering
290
+
291
+ def get_user_defined_rate_parameters_ordering(self) -> Dict[str, int]:
292
+ """
293
+ Get the user-defined rate parameters ordering for the state.
294
+
295
+ Returns
296
+ -------
297
+ Dict[str, int]
298
+ Dictionary of user-defined rate parameter names and their indices.
299
+ """
300
+ return self.__user_defined_rate_parameters_ordering
301
+
280
302
 
281
303
  class MICM():
282
304
  """
@@ -298,19 +320,38 @@ class MICM():
298
320
  def __init__(
299
321
  self,
300
322
  config_path: FilePath = None,
301
- mechanism: mc.Mechanism = None,
302
- solver_type: _SolverType = None,
323
+ mechanism: Mechanism = None,
324
+ solver_type: Any = None,
325
+ ignore_non_gas_phases: bool = True,
303
326
  ):
327
+ """ Initialize the MICM solver.
328
+
329
+ Parameters
330
+ ----------
331
+ config_path : FilePath, optional
332
+ Path to the configuration file. If provided, this will be used to create the solver.
333
+ mechanism : Mechanism, optional
334
+ Mechanism object which specifies the chemical mechanism to use. If provided, this will be used
335
+ to create the solver.
336
+ solver_type : SolverType, optional
337
+ Type of solver to use. If not provided, the default solver type will be used.
338
+ ignore_non_gas_phases : bool, optional
339
+ If True, non-gas phases will be ignored when configuring micm with the mechanism. This is only needed
340
+ until micm properly supports non-gas phases. This option is only supported when passing in a mechanism.
341
+ """
304
342
  self.__solver_type = solver_type
305
343
  self.__vector_size = _vector_size(solver_type)
306
344
  if config_path is None and mechanism is None:
307
- raise ValueError("Either config_path or mechanism must be provided.")
345
+ raise ValueError(
346
+ "Either config_path or mechanism must be provided.")
308
347
  if config_path is not None and mechanism is not None:
309
- raise ValueError("Only one of config_path or mechanism must be provided.")
348
+ raise ValueError(
349
+ "Only one of config_path or mechanism must be provided.")
310
350
  if config_path is not None:
311
351
  self.__solver = _create_solver(config_path, solver_type)
312
352
  elif mechanism is not None:
313
- self.__solver = _create_solver_from_mechanism(mechanism, solver_type)
353
+ self.__solver = _create_solver_from_mechanism(
354
+ mechanism, solver_type, ignore_non_gas_phases)
314
355
 
315
356
  def solver_type(self) -> SolverType:
316
357
  """