musica 0.12.0__cp311-cp311-win32.whl → 0.12.2__cp311-cp311-win32.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.cp311-win32.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 +1 -1
  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 +124 -0
  14. musica/examples/carma_sulfate.py +246 -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 -1
  21. musica/mechanism_configuration/aqueous_equilibrium.py +227 -54
  22. musica/mechanism_configuration/arrhenius.py +228 -42
  23. musica/mechanism_configuration/branched.py +249 -66
  24. musica/mechanism_configuration/condensed_phase_arrhenius.py +243 -50
  25. musica/mechanism_configuration/condensed_phase_photolysis.py +16 -19
  26. musica/mechanism_configuration/emission.py +10 -6
  27. musica/mechanism_configuration/first_order_loss.py +133 -26
  28. musica/mechanism_configuration/henrys_law.py +7 -48
  29. musica/mechanism_configuration/mechanism_configuration.py +114 -41
  30. musica/mechanism_configuration/phase.py +6 -2
  31. musica/mechanism_configuration/photolysis.py +12 -7
  32. musica/mechanism_configuration/reactions.py +20 -8
  33. musica/mechanism_configuration/simpol_phase_transfer.py +180 -51
  34. musica/mechanism_configuration/species.py +23 -4
  35. musica/mechanism_configuration/surface.py +14 -9
  36. musica/mechanism_configuration/ternary_chemical_activation.py +352 -0
  37. musica/mechanism_configuration/troe.py +259 -44
  38. musica/mechanism_configuration/tunneling.py +196 -49
  39. musica/mechanism_configuration/user_defined.py +9 -4
  40. musica/mechanism_configuration/wet_deposition.py +11 -8
  41. musica/mechanism_configuration.cpp +184 -95
  42. musica/musica.cpp +48 -61
  43. musica/test/examples/v1/full_configuration/full_configuration.json +39 -22
  44. musica/test/examples/v1/full_configuration/full_configuration.yaml +29 -20
  45. musica/test/{test_analytical.py → integration/test_analytical.py} +0 -1
  46. musica/test/integration/test_carma.py +227 -0
  47. musica/test/integration/test_carma_aluminum.py +12 -0
  48. musica/test/integration/test_carma_sulfate.py +17 -0
  49. musica/test/integration/test_sulfate_box_model.py +34 -0
  50. musica/test/integration/test_tuvx.py +62 -0
  51. musica/test/unit/test_parser.py +64 -0
  52. musica/test/{test_serializer.py → unit/test_serializer.py} +2 -2
  53. musica/test/unit/test_state.py +325 -0
  54. musica/test/{test_util_full_mechanism.py → unit/test_util_full_mechanism.py} +152 -122
  55. musica/tools/prepare_build_environment_linux.sh +23 -34
  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 +120 -73
  60. {musica-0.12.0.dist-info → musica-0.12.2.dist-info}/METADATA +41 -39
  61. musica-0.12.2.dist-info/RECORD +70 -0
  62. {musica-0.12.0.dist-info → musica-0.12.2.dist-info}/WHEEL +1 -1
  63. musica-0.12.2.dist-info/entry_points.txt +3 -0
  64. musica/test/examples/v0/config.json +0 -7
  65. musica/test/examples/v0/config.yaml +0 -3
  66. musica/test/examples/v0/reactions.json +0 -193
  67. musica/test/examples/v0/reactions.yaml +0 -142
  68. musica/test/examples/v0/species.json +0 -40
  69. musica/test/examples/v0/species.yaml +0 -19
  70. musica/test/test_parser.py +0 -57
  71. musica/test/tuvx.py +0 -10
  72. musica/tools/prepare_build_environment_windows.sh +0 -22
  73. musica-0.12.0.dist-info/RECORD +0 -57
  74. /musica/test/{test_chapman.py → integration/test_chapman.py} +0 -0
  75. {musica-0.12.0.dist-info → musica-0.12.2.dist-info}/licenses/AUTHORS.md +0 -0
  76. {musica-0.12.0.dist-info → musica-0.12.2.dist-info}/licenses/LICENSE +0 -0
@@ -1,13 +1,17 @@
1
- from typing import Optional, Any, Dict, List, Union, Tuple
2
- from musica import _CondensedPhaseArrhenius, _ReactionComponent
3
- from .phase import Phase
4
- from .species import Species
1
+ from ..constants import BOLTZMANN
2
+ from .utils import _add_other_properties
5
3
  from .reactions import ReactionComponentSerializer
6
- from .utils import _add_other_properties, _remove_empty_keys
7
- from musica.constants import BOLTZMANN
4
+ from .species import Species
5
+ from .phase import Phase
6
+ from typing import Optional, Any, Dict, List, Union, Tuple
7
+ from .. import backend
8
8
 
9
+ _backend = backend.get_backend()
10
+ _CondensedPhaseArrhenius = _backend._mechanism_configuration._CondensedPhaseArrhenius
11
+ _ReactionComponent = _backend._mechanism_configuration._ReactionComponent
9
12
 
10
- class CondensedPhaseArrhenius(_CondensedPhaseArrhenius):
13
+
14
+ class CondensedPhaseArrhenius:
11
15
  """
12
16
  A class representing a condensed phase Arrhenius rate constant.
13
17
 
@@ -21,8 +25,7 @@ class CondensedPhaseArrhenius(_CondensedPhaseArrhenius):
21
25
  E (float): Pressure scaling term [Pa-1].
22
26
  reactants (List[Union[Species, Tuple[float, Species]]]): A list of reactants involved in the reaction.
23
27
  products (List[Union[Species, Tuple[float, Species]]]): A list of products formed in the reaction.
24
- aerosol_phase (Phase): The aerosol phase in which the reaction occurs.
25
- aerosol_phase_water (Species): The water species in the aerosol phase.
28
+ condensed_phase (Phase): The condensed phase in which the reaction occurs.
26
29
  other_properties (Dict[str, Any]): A dictionary of other properties of the condensed phase Arrhenius rate constant.
27
30
  """
28
31
 
@@ -35,10 +38,10 @@ class CondensedPhaseArrhenius(_CondensedPhaseArrhenius):
35
38
  Ea: Optional[float] = None,
36
39
  D: Optional[float] = None,
37
40
  E: Optional[float] = None,
38
- reactants: Optional[List[Union[Species, Tuple[float, Species]]]] = None,
41
+ reactants: Optional[List[Union[Species,
42
+ Tuple[float, Species]]]] = None,
39
43
  products: Optional[List[Union[Species, Tuple[float, Species]]]] = None,
40
- aerosol_phase: Optional[Phase] = None,
41
- aerosol_phase_water: Optional[Species] = None,
44
+ condensed_phase: Optional[Phase] = None,
42
45
  other_properties: Optional[Dict[str, Any]] = None,
43
46
  ):
44
47
  """
@@ -54,21 +57,39 @@ class CondensedPhaseArrhenius(_CondensedPhaseArrhenius):
54
57
  E (float): Pressure scaling term [Pa-1].
55
58
  reactants (List[Union[Species, Tuple[float, Species]]]]: A list of reactants involved in the reaction.
56
59
  products (List[Union[Species, Tuple[float, Species]]]]: A list of products formed in the reaction.
57
- aerosol_phase (Phase): The aerosol phase in which the reaction occurs.
58
- aerosol_phase_water (Species): The water species in the aerosol phase.
60
+ condensed_phase (Phase): The condensed phase in which the reaction occurs.
59
61
  other_properties (Dict[str, Any]): A dictionary of other properties of the condensed phase Arrhenius rate constant.
60
62
  """
61
- super().__init__()
62
- self.name = name if name is not None else self.name
63
- self.A = A if A is not None else self.A
64
- self.B = B if B is not None else self.B
63
+ # Create the internal C++ instance
64
+ self._instance = _CondensedPhaseArrhenius()
65
+
66
+ # Store Python objects for reactants, products, phases
67
+ self._reactants = reactants if reactants is not None else []
68
+ self._products = products if products is not None else []
69
+ self._condensed_phase = condensed_phase
70
+ self._other_properties = other_properties if other_properties is not None else {}
71
+
72
+ # Set basic properties on the C++ instance
73
+ if name is not None:
74
+ self._instance.name = name
75
+ if A is not None:
76
+ self._instance.A = A
77
+ if B is not None:
78
+ self._instance.B = B
65
79
  if C is not None and Ea is not None:
66
80
  raise ValueError("Cannot specify both C and Ea.")
67
- self.C = -Ea / BOLTZMANN if Ea is not None else C if C is not None else self.C
68
- self.D = D if D is not None else self.D
69
- self.E = E if E is not None else self.E
70
- self.reactants = (
71
- [
81
+ if Ea is not None:
82
+ self._instance.C = -Ea / BOLTZMANN
83
+ elif C is not None:
84
+ self._instance.C = C
85
+ if D is not None:
86
+ self._instance.D = D
87
+ if E is not None:
88
+ self._instance.E = E
89
+
90
+ # Set reactants on the C++ instance
91
+ if reactants is not None:
92
+ self._instance.reactants = [
72
93
  (
73
94
  _ReactionComponent(r.name)
74
95
  if isinstance(r, Species)
@@ -76,11 +97,10 @@ class CondensedPhaseArrhenius(_CondensedPhaseArrhenius):
76
97
  )
77
98
  for r in reactants
78
99
  ]
79
- if reactants is not None
80
- else self.reactants
81
- )
82
- self.products = (
83
- [
100
+
101
+ # Set products on the C++ instance
102
+ if products is not None:
103
+ self._instance.products = [
84
104
  (
85
105
  _ReactionComponent(p.name)
86
106
  if isinstance(p, Species)
@@ -88,29 +108,202 @@ class CondensedPhaseArrhenius(_CondensedPhaseArrhenius):
88
108
  )
89
109
  for p in products
90
110
  ]
91
- if products is not None
92
- else self.products
93
- )
94
- self.aerosol_phase = aerosol_phase.name if aerosol_phase is not None else self.aerosol_phase
95
- self.aerosol_phase_water = (
96
- aerosol_phase_water.name if aerosol_phase_water is not None else self.aerosol_phase_water
97
- )
98
- self.other_properties = other_properties if other_properties is not None else self.other_properties
99
111
 
100
- @staticmethod
101
- def serialize(instance) -> Dict:
112
+ # Set phase information on the C++ instance
113
+ if condensed_phase is not None:
114
+ self._instance.condensed_phase = condensed_phase.name
115
+ if other_properties is not None:
116
+ self._instance.other_properties = other_properties
117
+
118
+ def __getattr__(self, name):
119
+ """Delegate unknown attribute access to the internal instance."""
120
+ return getattr(self._instance, name)
121
+
122
+ @property
123
+ def type(self):
124
+ """The reaction type."""
125
+ return self._instance.type
126
+
127
+ @property
128
+ def name(self) -> str:
129
+ """The name of the condensed phase Arrhenius rate constant."""
130
+ return self._instance.name
131
+
132
+ @name.setter
133
+ def name(self, value: str):
134
+ self._instance.name = value
135
+
136
+ @property
137
+ def A(self) -> float:
138
+ """Pre-exponential factor [(mol m-3)^(n-1)s-1]."""
139
+ return self._instance.A
140
+
141
+ @A.setter
142
+ def A(self, value: float):
143
+ self._instance.A = value
144
+
145
+ @property
146
+ def B(self) -> float:
147
+ """Temperature exponent [unitless]."""
148
+ return self._instance.B
149
+
150
+ @B.setter
151
+ def B(self, value: float):
152
+ self._instance.B = value
153
+
154
+ @property
155
+ def C(self) -> float:
156
+ """Exponential term [K-1]."""
157
+ return self._instance.C
158
+
159
+ @C.setter
160
+ def C(self, value: float):
161
+ self._instance.C = value
162
+
163
+ @property
164
+ def D(self) -> float:
165
+ """Reference Temperature [K]."""
166
+ return self._instance.D
167
+
168
+ @D.setter
169
+ def D(self, value: float):
170
+ self._instance.D = value
171
+
172
+ @property
173
+ def E(self) -> float:
174
+ """Pressure scaling term [Pa-1]."""
175
+ return self._instance.E
176
+
177
+ @E.setter
178
+ def E(self, value: float):
179
+ self._instance.E = value
180
+
181
+ @property
182
+ def reactants(self) -> List[Union[Species, Tuple[float, Species]]]:
183
+ """A list of reactants involved in the reaction."""
184
+ return self._reactants
185
+
186
+ @reactants.setter
187
+ def reactants(self, value: List[Union[Species, Tuple[float, Species]]]):
188
+ self._reactants = value
189
+ # Update the C++ instance
190
+ self._instance.reactants = [
191
+ (
192
+ _ReactionComponent(r.name)
193
+ if isinstance(r, Species)
194
+ else _ReactionComponent(r[1].name, r[0])
195
+ )
196
+ for r in value
197
+ ]
198
+
199
+ @property
200
+ def products(self) -> List[Union[Species, Tuple[float, Species]]]:
201
+ """A list of products formed in the reaction."""
202
+ return self._products
203
+
204
+ @products.setter
205
+ def products(self, value: List[Union[Species, Tuple[float, Species]]]):
206
+ self._products = value
207
+ # Update the C++ instance
208
+ self._instance.products = [
209
+ (
210
+ _ReactionComponent(p.name)
211
+ if isinstance(p, Species)
212
+ else _ReactionComponent(p[1].name, p[0])
213
+ )
214
+ for p in value
215
+ ]
216
+
217
+ @property
218
+ def condensed_phase(self) -> Phase:
219
+ """The condensed phase in which the reaction occurs."""
220
+ return self._condensed_phase
221
+
222
+ @condensed_phase.setter
223
+ def condensed_phase(self, value: Phase):
224
+ self._condensed_phase = value
225
+ # Update the C++ instance
226
+ self._instance.condensed_phase = value.name if value is not None else ""
227
+
228
+ @property
229
+ def other_properties(self) -> Dict[str, Any]:
230
+ """A dictionary of other properties of the condensed phase Arrhenius rate constant."""
231
+ return self._other_properties
232
+
233
+ @other_properties.setter
234
+ def other_properties(self, value: Dict[str, Any]):
235
+ self._other_properties = value
236
+ # Update the C++ instance
237
+ self._instance.other_properties = value
238
+
239
+ def serialize(self) -> Dict:
240
+ """
241
+ Serialize the CondensedPhaseArrhenius instance to a dictionary.
242
+
243
+ Returns:
244
+ Dict: A dictionary representation of the condensed phase Arrhenius rate constant.
245
+ """
246
+ # Convert Python reactants/products to serializable format
247
+ def serialize_python_components(components):
248
+ result = []
249
+ for component in components:
250
+ if isinstance(component, Species):
251
+ result.append(component.name)
252
+ elif isinstance(component, tuple) and len(component) == 2:
253
+ # Handle (coefficient, Species) tuples
254
+ coefficient, species = component
255
+ result.append({
256
+ "species name": species.name,
257
+ "coefficient": coefficient
258
+ })
259
+ else:
260
+ # Fallback: treat as Species
261
+ result.append(component.name if hasattr(component, 'name') else str(component))
262
+ return result
263
+
102
264
  serialize_dict = {
103
265
  "type": "CONDENSED_PHASE_ARRHENIUS",
104
- "name": instance.name,
105
- "A": instance.A,
106
- "B": instance.B,
107
- "C": instance.C,
108
- "D": instance.D,
109
- "E": instance.E,
110
- "reactants": ReactionComponentSerializer.serialize_list_reaction_components(instance.reactants),
111
- "products": ReactionComponentSerializer.serialize_list_reaction_components(instance.products),
112
- "aerosol phase": instance.aerosol_phase,
113
- "aerosol-phase water": instance.aerosol_phase_water,
266
+ "name": self.name,
267
+ "A": self.A,
268
+ "B": self.B,
269
+ "C": self.C,
270
+ "D": self.D,
271
+ "E": self.E,
272
+ "reactants": serialize_python_components(self._reactants),
273
+ "products": serialize_python_components(self._products),
274
+ "condensed phase": self._condensed_phase.name if self._condensed_phase is not None else "",
114
275
  }
115
- _add_other_properties(serialize_dict, instance.other_properties)
116
- return _remove_empty_keys(serialize_dict)
276
+ _add_other_properties(serialize_dict, self._other_properties)
277
+ return serialize_dict
278
+
279
+ @staticmethod
280
+ def serialize_static(instance) -> Dict:
281
+ """
282
+ Static serialize method for backward compatibility.
283
+
284
+ Args:
285
+ instance: The CondensedPhaseArrhenius instance to serialize (can be Python wrapper or C++ type).
286
+
287
+ Returns:
288
+ Dict: A dictionary representation of the condensed phase Arrhenius rate constant.
289
+ """
290
+ # Check if it's the new composition-based Python wrapper
291
+ if hasattr(instance, '_instance'):
292
+ # New Python wrapper - use instance method
293
+ return instance.serialize()
294
+ else:
295
+ # Old C++ wrapper type - use direct attribute access
296
+ serialize_dict = {
297
+ "type": "CONDENSED_PHASE_ARRHENIUS",
298
+ "name": instance.name,
299
+ "A": instance.A,
300
+ "B": instance.B,
301
+ "C": instance.C,
302
+ "D": instance.D,
303
+ "E": instance.E,
304
+ "reactants": ReactionComponentSerializer.serialize_list_reaction_components(instance.reactants),
305
+ "products": ReactionComponentSerializer.serialize_list_reaction_components(instance.products),
306
+ "condensed phase": instance.condensed_phase,
307
+ }
308
+ _add_other_properties(serialize_dict, instance.other_properties)
309
+ return serialize_dict
@@ -1,10 +1,13 @@
1
- from typing import Optional, Any, Dict, List, Union, Tuple
2
- from musica import _CondensedPhasePhotolysis, _ReactionComponent
3
- from .phase import Phase
4
- from .species import Species
1
+ from .utils import _add_other_properties
5
2
  from .reactions import ReactionComponentSerializer
6
- from .utils import _add_other_properties, _remove_empty_keys
3
+ from .species import Species
4
+ from .phase import Phase
5
+ from typing import Optional, Any, Dict, List, Union, Tuple
6
+ from .. import backend
7
7
 
8
+ _backend = backend.get_backend()
9
+ _CondensedPhasePhotolysis = _backend._mechanism_configuration._CondensedPhasePhotolysis
10
+ _ReactionComponent = _backend._mechanism_configuration._ReactionComponent
8
11
 
9
12
 
10
13
  class CondensedPhasePhotolysis(_CondensedPhasePhotolysis):
@@ -16,8 +19,7 @@ class CondensedPhasePhotolysis(_CondensedPhasePhotolysis):
16
19
  scaling_factor (float): The scaling factor for the photolysis rate constant.
17
20
  reactants (List[Union[Species, Tuple[float, Species]]]): A list of reactants involved in the reaction.
18
21
  products (List[Union[Species, Tuple[float, Species]]]): A list of products formed in the reaction.
19
- aerosol_phase (Phase): The aerosol phase in which the reaction occurs.
20
- aerosol_phase_water (float): The water species in the aerosol phase [unitless].
22
+ condensed_phase (Phase): The condensed phase in which the reaction occurs.
21
23
  other_properties (Dict[str, Any]): A dictionary of other properties of the condensed phase photolysis reaction rate constant.
22
24
  """
23
25
 
@@ -25,10 +27,10 @@ class CondensedPhasePhotolysis(_CondensedPhasePhotolysis):
25
27
  self,
26
28
  name: Optional[str] = None,
27
29
  scaling_factor: Optional[float] = None,
28
- reactants: Optional[List[Union[Species, Tuple[float, Species]]]] = None,
30
+ reactants: Optional[List[Union[Species,
31
+ Tuple[float, Species]]]] = None,
29
32
  products: Optional[List[Union[Species, Tuple[float, Species]]]] = None,
30
- aerosol_phase: Optional[Phase] = None,
31
- aerosol_phase_water: Optional[Species] = None,
33
+ condensed_phase: Optional[Phase] = None,
32
34
  other_properties: Optional[Dict[str, Any]] = None,
33
35
  ):
34
36
  """
@@ -39,8 +41,7 @@ class CondensedPhasePhotolysis(_CondensedPhasePhotolysis):
39
41
  scaling_factor (float): The scaling factor for the photolysis rate constant.
40
42
  reactants (List[Union[Species, Tuple[float, Species]]]): A list of reactants involved in the reaction.
41
43
  products (List[Union[Species, Tuple[float, Species]]]): A list of products formed in the reaction.
42
- aerosol_phase (Phase): The aerosol phase in which the reaction occurs.
43
- aerosol_phase_water (Species): The water species in the aerosol phase [unitless].
44
+ condensed_phase (Phase): The condensed phase in which the reaction occurs.
44
45
  other_properties (Dict[str, Any]): A dictionary of other properties of the condensed phase photolysis reaction rate constant.
45
46
  """
46
47
  super().__init__()
@@ -70,10 +71,7 @@ class CondensedPhasePhotolysis(_CondensedPhasePhotolysis):
70
71
  if products is not None
71
72
  else self.products
72
73
  )
73
- self.aerosol_phase = aerosol_phase.name if aerosol_phase is not None else self.aerosol_phase
74
- self.aerosol_phase_water = (
75
- aerosol_phase_water.name if aerosol_phase_water is not None else self.aerosol_phase_water
76
- )
74
+ self.condensed_phase = condensed_phase.name if condensed_phase is not None else self.condensed_phase
77
75
  self.other_properties = other_properties if other_properties is not None else self.other_properties
78
76
 
79
77
  @staticmethod
@@ -84,8 +82,7 @@ class CondensedPhasePhotolysis(_CondensedPhasePhotolysis):
84
82
  "scaling factor": instance.scaling_factor,
85
83
  "reactants": ReactionComponentSerializer.serialize_list_reaction_components(instance.reactants),
86
84
  "products": ReactionComponentSerializer.serialize_list_reaction_components(instance.products),
87
- "aerosol phase": instance.aerosol_phase,
88
- "aerosol-phase water": instance.aerosol_phase_water,
85
+ "condensed phase": instance.condensed_phase,
89
86
  }
90
87
  _add_other_properties(serialize_dict, instance.other_properties)
91
- return _remove_empty_keys(serialize_dict)
88
+ return serialize_dict
@@ -1,9 +1,13 @@
1
- from typing import Optional, Any, Dict, List, Union, Tuple
2
- from musica import _Emission, _ReactionComponent
3
- from .phase import Phase
4
- from .species import Species
1
+ from .utils import _add_other_properties
5
2
  from .reactions import ReactionComponentSerializer
6
- from .utils import _add_other_properties, _remove_empty_keys
3
+ from .species import Species
4
+ from .phase import Phase
5
+ from typing import Optional, Any, Dict, List, Union, Tuple
6
+ from .. import backend
7
+
8
+ _backend = backend.get_backend()
9
+ _Emission = _backend._mechanism_configuration._Emission
10
+ _ReactionComponent = _backend._mechanism_configuration._ReactionComponent
7
11
 
8
12
 
9
13
  class Emission(_Emission):
@@ -64,4 +68,4 @@ class Emission(_Emission):
64
68
  "gas phase": instance.gas_phase,
65
69
  }
66
70
  _add_other_properties(serialize_dict, instance.other_properties)
67
- return _remove_empty_keys(serialize_dict)
71
+ return serialize_dict
@@ -1,12 +1,17 @@
1
- from typing import Optional, Any, Dict, List, Union, Tuple
2
- from musica import _FirstOrderLoss, _ReactionComponent
3
- from .phase import Phase
4
- from .species import Species
1
+ from .utils import _add_other_properties
5
2
  from .reactions import ReactionComponentSerializer
6
- from .utils import _add_other_properties, _remove_empty_keys
3
+ from .species import Species
4
+ from .phase import Phase
5
+ from typing import Optional, Any, Dict, List, Union, Tuple
6
+ from .. import backend
7
+
8
+ _backend = backend.get_backend()
9
+ _FirstOrderLoss = _backend._mechanism_configuration._FirstOrderLoss
10
+ _ReactionComponent = _backend._mechanism_configuration._ReactionComponent
11
+ ReactionType = _backend._mechanism_configuration._ReactionType
7
12
 
8
13
 
9
- class FirstOrderLoss(_FirstOrderLoss):
14
+ class FirstOrderLoss:
10
15
  """
11
16
  A class representing a first-order loss reaction rate constant.
12
17
 
@@ -22,7 +27,8 @@ class FirstOrderLoss(_FirstOrderLoss):
22
27
  self,
23
28
  name: Optional[str] = None,
24
29
  scaling_factor: Optional[float] = None,
25
- reactants: Optional[List[Union[Species, Tuple[float, Species]]]] = None,
30
+ reactants: Optional[List[Union[Species,
31
+ Tuple[float, Species]]]] = None,
26
32
  gas_phase: Optional[Phase] = None,
27
33
  other_properties: Optional[Dict[str, Any]] = None,
28
34
  ):
@@ -36,26 +42,127 @@ class FirstOrderLoss(_FirstOrderLoss):
36
42
  gas_phase (Phase): The gas phase in which the reaction occurs.
37
43
  other_properties (Dict[str, Any]): A dictionary of other properties of the first-order loss reaction rate constant.
38
44
  """
39
- super().__init__()
40
- self.name = name if name is not None else self.name
41
- self.scaling_factor = scaling_factor if scaling_factor is not None else self.scaling_factor
42
- self.reactants = (
43
- [
44
- (
45
- _ReactionComponent(r.name)
46
- if isinstance(r, Species)
47
- else _ReactionComponent(r[1].name, r[0])
48
- )
49
- for r in reactants
50
- ]
51
- if reactants is not None
52
- else self.reactants
53
- )
54
- self.gas_phase = gas_phase.name if gas_phase is not None else self.gas_phase
55
- self.other_properties = other_properties if other_properties is not None else self.other_properties
45
+ # Create the internal C++ instance
46
+ self._instance = _FirstOrderLoss()
47
+
48
+ # Set properties if provided
49
+ if name is not None:
50
+ self.name = name
51
+ if scaling_factor is not None:
52
+ self.scaling_factor = scaling_factor
53
+ if reactants is not None:
54
+ self.reactants = reactants
55
+ if gas_phase is not None:
56
+ self.gas_phase = gas_phase
57
+ if other_properties is not None:
58
+ self.other_properties = other_properties
59
+
60
+ @property
61
+ def name(self) -> str:
62
+ """Get the name."""
63
+ return self._instance.name
64
+
65
+ @name.setter
66
+ def name(self, value: str):
67
+ """Set the name."""
68
+ self._instance.name = value
69
+
70
+ @property
71
+ def scaling_factor(self) -> float:
72
+ """Get the scaling factor."""
73
+ return self._instance.scaling_factor
74
+
75
+ @scaling_factor.setter
76
+ def scaling_factor(self, value: float):
77
+ """Set the scaling factor."""
78
+ self._instance.scaling_factor = value
79
+
80
+ @property
81
+ def reactants(self) -> List[Union[Species, Tuple[float, Species]]]:
82
+ """Get the reactants as Python objects."""
83
+ # Convert from C++ _ReactionComponent objects to Python Species objects
84
+ result = []
85
+ for rc in self._instance.reactants:
86
+ if hasattr(rc, 'coefficient') and rc.coefficient != 1.0:
87
+ # Create a tuple with coefficient and species
88
+ species = Species(name=rc.species_name)
89
+ result.append((rc.coefficient, species))
90
+ else:
91
+ # Just the species
92
+ species = Species(name=rc.species_name)
93
+ result.append(species)
94
+ return result
95
+
96
+ @reactants.setter
97
+ def reactants(self, value: List[Union[Species, Tuple[float, Species]]]):
98
+ """Set the reactants, converting from Python to C++ objects."""
99
+ cpp_reactants = []
100
+ for r in value:
101
+ if isinstance(r, Species):
102
+ cpp_reactants.append(_ReactionComponent(r.name))
103
+ elif isinstance(r, tuple) and len(r) == 2:
104
+ coefficient, species = r
105
+ cpp_reactants.append(_ReactionComponent(species.name, coefficient))
106
+ else:
107
+ raise ValueError(f"Invalid reactant format: {r}")
108
+ self._instance.reactants = cpp_reactants
109
+
110
+ @property
111
+ def gas_phase(self) -> str:
112
+ """Get the gas phase name."""
113
+ return self._instance.gas_phase
114
+
115
+ @gas_phase.setter
116
+ def gas_phase(self, value: Union[Phase, str]):
117
+ """Set the gas phase."""
118
+ if isinstance(value, Phase):
119
+ self._instance.gas_phase = value.name
120
+ else:
121
+ self._instance.gas_phase = value
122
+
123
+ @property
124
+ def other_properties(self) -> Dict[str, Any]:
125
+ """Get the other properties."""
126
+ return self._instance.other_properties
127
+
128
+ @other_properties.setter
129
+ def other_properties(self, value: Dict[str, Any]):
130
+ """Set the other properties."""
131
+ self._instance.other_properties = value
132
+
133
+ @property
134
+ def type(self):
135
+ """Get the reaction type."""
136
+ return ReactionType.FirstOrderLoss
137
+
138
+ def serialize(self) -> Dict:
139
+ """
140
+ Serialize the FirstOrderLoss object to a dictionary using only Python-visible data.
141
+
142
+ Returns:
143
+ Dict: A dictionary representation of the FirstOrderLoss object.
144
+ """
145
+ serialize_dict = {
146
+ "type": "FIRST_ORDER_LOSS",
147
+ "name": self.name,
148
+ "scaling factor": self.scaling_factor,
149
+ "reactants": ReactionComponentSerializer.serialize_list_reaction_components(self._instance.reactants),
150
+ "gas phase": self.gas_phase,
151
+ }
152
+ _add_other_properties(serialize_dict, self.other_properties)
153
+ return _remove_empty_keys(serialize_dict)
56
154
 
57
155
  @staticmethod
58
- def serialize(instance) -> Dict:
156
+ def serialize_static(instance) -> Dict:
157
+ """
158
+ Static serialize method for compatibility with C++ _FirstOrderLoss objects.
159
+
160
+ Args:
161
+ instance: The _FirstOrderLoss instance to serialize.
162
+
163
+ Returns:
164
+ Dict: A dictionary representation of the FirstOrderLoss object.
165
+ """
59
166
  serialize_dict = {
60
167
  "type": "FIRST_ORDER_LOSS",
61
168
  "name": instance.name,
@@ -64,4 +171,4 @@ class FirstOrderLoss(_FirstOrderLoss):
64
171
  "gas phase": instance.gas_phase,
65
172
  }
66
173
  _add_other_properties(serialize_dict, instance.other_properties)
67
- return _remove_empty_keys(serialize_dict)
174
+ return serialize_dict