musica 0.12.0__cp39-cp39-win_amd64.whl → 0.12.2__cp39-cp39-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.cp39-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 +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,18 @@
1
1
  from typing import Optional, Any, Dict, List, Union, Tuple
2
- from musica import _Arrhenius, _ReactionComponent
2
+ from .. import backend
3
3
  from .phase import Phase
4
4
  from .species import Species
5
5
  from .reactions import ReactionComponentSerializer
6
- from .utils import _add_other_properties, _remove_empty_keys
7
- from musica.constants import BOLTZMANN
6
+ from .utils import _add_other_properties
7
+ from ..constants import BOLTZMANN
8
8
 
9
+ _backend = backend.get_backend()
10
+ _Arrhenius = _backend._mechanism_configuration._Arrhenius
11
+ _ReactionComponent = _backend._mechanism_configuration._ReactionComponent
12
+ ReactionType = _backend._mechanism_configuration._ReactionType
9
13
 
10
- class Arrhenius(_Arrhenius):
14
+
15
+ class Arrhenius:
11
16
  """
12
17
  A class representing an Arrhenius rate constant.
13
18
 
@@ -46,7 +51,8 @@ class Arrhenius(_Arrhenius):
46
51
  Ea: Optional[float] = None,
47
52
  D: Optional[float] = None,
48
53
  E: Optional[float] = None,
49
- reactants: Optional[List[Union[Species, Tuple[float, Species]]]] = None,
54
+ reactants: Optional[List[Union[Species,
55
+ Tuple[float, Species]]]] = None,
50
56
  products: Optional[List[Union[Species, Tuple[float, Species]]]] = None,
51
57
  gas_phase: Optional[Phase] = None,
52
58
  other_properties: Optional[Dict[str, Any]] = None,
@@ -67,45 +73,199 @@ class Arrhenius(_Arrhenius):
67
73
  gas_phase (Phase): The gas phase in which the reaction occurs.
68
74
  other_properties (Dict[str, Any]): A dictionary of other properties of the Arrhenius rate constant.
69
75
  """
70
- super().__init__()
71
- self.name = name if name is not None else self.name
72
- self.A = A if A is not None else self.A
73
- self.B = B if B is not None else self.B
76
+ # Create the internal C++ instance
77
+ self._instance = _Arrhenius()
78
+
79
+ # Validate mutually exclusive parameters
74
80
  if C is not None and Ea is not None:
75
81
  raise ValueError("Cannot specify both C and Ea.")
76
- self.C = -Ea / BOLTZMANN if Ea is not None else C if C is not None else self.C
77
- self.D = D if D is not None else self.D
78
- self.E = E if E is not None else self.E
79
- self.reactants = (
80
- [
81
- (
82
- _ReactionComponent(r.name)
83
- if isinstance(r, Species)
84
- else _ReactionComponent(r[1].name, r[0])
85
- )
86
- for r in reactants
87
- ]
88
- if reactants is not None
89
- else self.reactants
90
- )
91
- self.products = (
92
- [
93
- (
94
- _ReactionComponent(p.name)
95
- if isinstance(p, Species)
96
- else _ReactionComponent(p[1].name, p[0])
97
- )
98
- for p in products
99
- ]
100
- if products is not None
101
- else self.products
102
- )
103
- self.gas_phase = gas_phase.name if gas_phase is not None else self.gas_phase
104
- self.other_properties = other_properties if other_properties is not None else self.other_properties
105
82
 
106
- @staticmethod
107
- def serialize(instance) -> Dict:
108
- serialize_dict = {
83
+ # Set all parameters
84
+ if name is not None:
85
+ self.name = name
86
+ if A is not None:
87
+ self.A = A
88
+ if B is not None:
89
+ self.B = B
90
+ if Ea is not None:
91
+ self.C = -Ea / BOLTZMANN
92
+ elif C is not None:
93
+ self.C = C
94
+ if D is not None:
95
+ self.D = D
96
+ if E is not None:
97
+ self.E = E
98
+ if reactants is not None:
99
+ self.reactants = reactants
100
+ if products is not None:
101
+ self.products = products
102
+ if gas_phase is not None:
103
+ self.gas_phase = gas_phase
104
+ if other_properties is not None:
105
+ self.other_properties = other_properties
106
+
107
+ # Property delegation to self._instance
108
+ @property
109
+ def name(self) -> str:
110
+ """Get the name of the Arrhenius rate constant."""
111
+ return self._instance.name
112
+
113
+ @name.setter
114
+ def name(self, value: str):
115
+ """Set the name of the Arrhenius rate constant."""
116
+ self._instance.name = value
117
+
118
+ @property
119
+ def A(self) -> float:
120
+ """Get the pre-exponential factor."""
121
+ return self._instance.A
122
+
123
+ @A.setter
124
+ def A(self, value: float):
125
+ """Set the pre-exponential factor."""
126
+ self._instance.A = value
127
+
128
+ @property
129
+ def B(self) -> float:
130
+ """Get the temperature exponent."""
131
+ return self._instance.B
132
+
133
+ @B.setter
134
+ def B(self, value: float):
135
+ """Set the temperature exponent."""
136
+ self._instance.B = value
137
+
138
+ @property
139
+ def C(self) -> float:
140
+ """Get the exponential term."""
141
+ return self._instance.C
142
+
143
+ @C.setter
144
+ def C(self, value: float):
145
+ """Set the exponential term."""
146
+ self._instance.C = value
147
+
148
+ @property
149
+ def D(self) -> float:
150
+ """Get the reference temperature."""
151
+ return self._instance.D
152
+
153
+ @D.setter
154
+ def D(self, value: float):
155
+ """Set the reference temperature."""
156
+ self._instance.D = value
157
+
158
+ @property
159
+ def E(self) -> float:
160
+ """Get the pressure scaling term."""
161
+ return self._instance.E
162
+
163
+ @E.setter
164
+ def E(self, value: float):
165
+ """Set the pressure scaling term."""
166
+ self._instance.E = value
167
+
168
+ @property
169
+ def reactants(self) -> List[Union[Species, Tuple[float, Species]]]:
170
+ """Get the reactants as Python objects."""
171
+ # Convert from C++ _ReactionComponent objects to Python Species objects
172
+ result = []
173
+ for rc in self._instance.reactants:
174
+ if hasattr(rc, 'coefficient') and rc.coefficient != 1.0:
175
+ # Create a tuple with coefficient and species
176
+ species = Species(name=rc.species_name)
177
+ result.append((rc.coefficient, species))
178
+ else:
179
+ # Just the species
180
+ species = Species(name=rc.species_name)
181
+ result.append(species)
182
+ return result
183
+
184
+ @reactants.setter
185
+ def reactants(self, value: List[Union[Species, Tuple[float, Species]]]):
186
+ """Set the reactants, converting from Python to C++ objects."""
187
+ cpp_reactants = []
188
+ for r in value:
189
+ if isinstance(r, Species):
190
+ cpp_reactants.append(_ReactionComponent(r.name))
191
+ elif isinstance(r, tuple) and len(r) == 2:
192
+ coefficient, species = r
193
+ cpp_reactants.append(_ReactionComponent(species.name, coefficient))
194
+ else:
195
+ raise ValueError(f"Invalid reactant format: {r}")
196
+ self._instance.reactants = cpp_reactants
197
+
198
+ @property
199
+ def products(self) -> List[Union[Species, Tuple[float, Species]]]:
200
+ """Get the products as Python objects."""
201
+ # Convert from C++ _ReactionComponent objects to Python Species objects
202
+ result = []
203
+ for rc in self._instance.products:
204
+ if hasattr(rc, 'coefficient') and rc.coefficient != 1.0:
205
+ # Create a tuple with coefficient and species
206
+ species = Species(name=rc.species_name)
207
+ result.append((rc.coefficient, species))
208
+ else:
209
+ # Just the species
210
+ species = Species(name=rc.species_name)
211
+ result.append(species)
212
+ return result
213
+
214
+ @products.setter
215
+ def products(self, value: List[Union[Species, Tuple[float, Species]]]):
216
+ """Set the products, converting from Python to C++ objects."""
217
+ cpp_products = []
218
+ for p in value:
219
+ if isinstance(p, Species):
220
+ cpp_products.append(_ReactionComponent(p.name))
221
+ elif isinstance(p, tuple) and len(p) == 2:
222
+ coefficient, species = p
223
+ cpp_products.append(_ReactionComponent(species.name, coefficient))
224
+ else:
225
+ raise ValueError(f"Invalid product format: {p}")
226
+ self._instance.products = cpp_products
227
+
228
+ @property
229
+ def gas_phase(self) -> str:
230
+ """Get the gas phase name."""
231
+ return self._instance.gas_phase
232
+
233
+ @gas_phase.setter
234
+ def gas_phase(self, value: Union[Phase, str]):
235
+ """Set the gas phase."""
236
+ if isinstance(value, Phase):
237
+ self._instance.gas_phase = value.name
238
+ elif isinstance(value, str):
239
+ self._instance.gas_phase = value
240
+ else:
241
+ raise ValueError(f"Invalid gas_phase type: {type(value)}")
242
+
243
+ @property
244
+ def other_properties(self) -> Dict[str, Any]:
245
+ """Get the other properties."""
246
+ return self._instance.other_properties
247
+
248
+ @other_properties.setter
249
+ def other_properties(self, value: Dict[str, Any]):
250
+ """Set the other properties."""
251
+ self._instance.other_properties = value
252
+
253
+ @property
254
+ def type(self):
255
+ """Get the reaction type."""
256
+ return ReactionType.Arrhenius
257
+
258
+ def _create_serialize_dict(self, instance) -> Dict:
259
+ """
260
+ Helper method to create the serialization dictionary.
261
+
262
+ Args:
263
+ instance: The instance to serialize (either self._instance or a _Arrhenius object).
264
+
265
+ Returns:
266
+ Dict: Base serialization dictionary.
267
+ """
268
+ return {
109
269
  "type": "ARRHENIUS",
110
270
  "name": instance.name,
111
271
  "A": instance.A,
@@ -117,5 +277,31 @@ class Arrhenius(_Arrhenius):
117
277
  "products": ReactionComponentSerializer.serialize_list_reaction_components(instance.products),
118
278
  "gas phase": instance.gas_phase,
119
279
  }
280
+
281
+ def serialize(self) -> Dict:
282
+ """
283
+ Serialize the Arrhenius object to a dictionary using only Python-visible data.
284
+
285
+ Returns:
286
+ Dict: A dictionary representation of the Arrhenius object.
287
+ """
288
+ serialize_dict = self._create_serialize_dict(self._instance)
289
+ _add_other_properties(serialize_dict, self.other_properties)
290
+ return serialize_dict
291
+
292
+ @staticmethod
293
+ def serialize_static(instance) -> Dict:
294
+ """
295
+ Static serialize method for compatibility with C++ _Arrhenius objects.
296
+
297
+ Args:
298
+ instance: The _Arrhenius instance to serialize.
299
+
300
+ Returns:
301
+ Dict: A dictionary representation of the Arrhenius object.
302
+ """
303
+ # Create a temporary Arrhenius object to use the helper method
304
+ temp_arrhenius = Arrhenius()
305
+ serialize_dict = temp_arrhenius._create_serialize_dict(instance)
120
306
  _add_other_properties(serialize_dict, instance.other_properties)
121
- return _remove_empty_keys(serialize_dict)
307
+ return serialize_dict
@@ -1,12 +1,17 @@
1
- from typing import Optional, Any, Dict, List, Union, Tuple
2
- from musica import _Branched, _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
+ _Branched = _backend._mechanism_configuration._Branched
10
+ _ReactionComponent = _backend._mechanism_configuration._ReactionComponent
11
+ ReactionType = _backend._mechanism_configuration._ReactionType
7
12
 
8
13
 
9
- class Branched(_Branched):
14
+ class Branched:
10
15
  """
11
16
  A class representing a branched reaction rate constant.
12
17
 
@@ -32,9 +37,12 @@ class Branched(_Branched):
32
37
  Y: Optional[float] = None,
33
38
  a0: Optional[float] = None,
34
39
  n: Optional[float] = None,
35
- reactants: Optional[List[Union[Species, Tuple[float, Species]]]] = None,
36
- nitrate_products: Optional[List[Union[Species, Tuple[float, Species]]]] = None,
37
- alkoxy_products: Optional[List[Union[Species, Tuple[float, Species]]]] = None,
40
+ reactants: Optional[List[Union[Species,
41
+ Tuple[float, Species]]]] = None,
42
+ nitrate_products: Optional[List[Union[Species,
43
+ Tuple[float, Species]]]] = None,
44
+ alkoxy_products: Optional[List[Union[Species,
45
+ Tuple[float, Species]]]] = None,
38
46
  gas_phase: Optional[Phase] = None,
39
47
  other_properties: Optional[Dict[str, Any]] = None,
40
48
  ):
@@ -53,64 +61,239 @@ class Branched(_Branched):
53
61
  gas_phase (Phase): The gas phase in which the reaction occurs.
54
62
  other_properties (Dict[str, Any]): A dictionary of other properties of the branched reaction rate constant.
55
63
  """
56
- super().__init__()
57
- self.name = name if name is not None else self.name
58
- self.X = X if X is not None else self.X
59
- self.Y = Y if Y is not None else self.Y
60
- self.a0 = a0 if a0 is not None else self.a0
61
- self.n = n if n is not None else self.n
62
- self.reactants = (
63
- [
64
- (
65
- _ReactionComponent(r.name)
66
- if isinstance(r, Species)
67
- else _ReactionComponent(r[1].name, r[0])
68
- )
69
- for r in reactants
70
- ]
71
- if reactants is not None
72
- else self.reactants
73
- )
74
- self.nitrate_products = (
75
- [
76
- (
77
- _ReactionComponent(p.name)
78
- if isinstance(p, Species)
79
- else _ReactionComponent(p[1].name, p[0])
80
- )
81
- for p in nitrate_products
82
- ]
83
- if nitrate_products is not None
84
- else self.nitrate_products
85
- )
86
- self.alkoxy_products = (
87
- [
88
- (
89
- _ReactionComponent(p.name)
90
- if isinstance(p, Species)
91
- else _ReactionComponent(p[1].name, p[0])
92
- )
93
- for p in alkoxy_products
94
- ]
95
- if alkoxy_products is not None
96
- else self.alkoxy_products
97
- )
98
- self.gas_phase = gas_phase.name if gas_phase is not None else self.gas_phase
99
- self.other_properties = other_properties if other_properties is not None else self.other_properties
64
+ # Create the internal C++ instance
65
+ self._instance = _Branched()
100
66
 
101
- @staticmethod
102
- def serialize(instance) -> Dict:
67
+ # Set all parameters
68
+ if name is not None:
69
+ self.name = name
70
+ if X is not None:
71
+ self.X = X
72
+ if Y is not None:
73
+ self.Y = Y
74
+ if a0 is not None:
75
+ self.a0 = a0
76
+ if n is not None:
77
+ self.n = n
78
+ if reactants is not None:
79
+ self.reactants = reactants
80
+ if nitrate_products is not None:
81
+ self.nitrate_products = nitrate_products
82
+ if alkoxy_products is not None:
83
+ self.alkoxy_products = alkoxy_products
84
+ if gas_phase is not None:
85
+ self.gas_phase = gas_phase
86
+ if other_properties is not None:
87
+ self.other_properties = other_properties
88
+
89
+ # Property delegation to self._instance
90
+ @property
91
+ def name(self) -> str:
92
+ """Get the name of the branched reaction rate constant."""
93
+ return self._instance.name
94
+
95
+ @name.setter
96
+ def name(self, value: str):
97
+ """Set the name of the branched reaction rate constant."""
98
+ self._instance.name = value
99
+
100
+ @property
101
+ def X(self) -> float:
102
+ """Get the pre-exponential branching factor."""
103
+ return self._instance.X
104
+
105
+ @X.setter
106
+ def X(self, value: float):
107
+ """Set the pre-exponential branching factor."""
108
+ self._instance.X = value
109
+
110
+ @property
111
+ def Y(self) -> float:
112
+ """Get the exponential branching factor."""
113
+ return self._instance.Y
114
+
115
+ @Y.setter
116
+ def Y(self, value: float):
117
+ """Set the exponential branching factor."""
118
+ self._instance.Y = value
119
+
120
+ @property
121
+ def a0(self) -> float:
122
+ """Get the Z parameter."""
123
+ return self._instance.a0
124
+
125
+ @a0.setter
126
+ def a0(self, value: float):
127
+ """Set the Z parameter."""
128
+ self._instance.a0 = value
129
+
130
+ @property
131
+ def n(self) -> float:
132
+ """Get the A parameter."""
133
+ return self._instance.n
134
+
135
+ @n.setter
136
+ def n(self, value: float):
137
+ """Set the A parameter."""
138
+ self._instance.n = value
139
+
140
+ @property
141
+ def reactants(self) -> List[Union[Species, Tuple[float, Species]]]:
142
+ """Get the reactants as Python objects."""
143
+ # Convert from C++ _ReactionComponent objects to Python Species objects
144
+ result = []
145
+ for rc in self._instance.reactants:
146
+ if hasattr(rc, 'coefficient') and rc.coefficient != 1.0:
147
+ # Create a tuple with coefficient and species
148
+ species = Species(name=rc.species_name)
149
+ result.append((rc.coefficient, species))
150
+ else:
151
+ # Just the species
152
+ species = Species(name=rc.species_name)
153
+ result.append(species)
154
+ return result
155
+
156
+ @reactants.setter
157
+ def reactants(self, value: List[Union[Species, Tuple[float, Species]]]):
158
+ """Set the reactants, converting from Python to C++ objects."""
159
+ cpp_reactants = []
160
+ for r in value:
161
+ if isinstance(r, Species):
162
+ cpp_reactants.append(_ReactionComponent(r.name))
163
+ elif isinstance(r, tuple) and len(r) == 2:
164
+ coefficient, species = r
165
+ cpp_reactants.append(_ReactionComponent(species.name, coefficient))
166
+ else:
167
+ raise ValueError(f"Invalid reactant format: {r}")
168
+ self._instance.reactants = cpp_reactants
169
+
170
+ @property
171
+ def nitrate_products(self) -> List[Union[Species, Tuple[float, Species]]]:
172
+ """Get the nitrate products as Python objects."""
173
+ # Convert from C++ _ReactionComponent objects to Python Species objects
174
+ result = []
175
+ for rc in self._instance.nitrate_products:
176
+ if hasattr(rc, 'coefficient') and rc.coefficient != 1.0:
177
+ # Create a tuple with coefficient and species
178
+ species = Species(name=rc.species_name)
179
+ result.append((rc.coefficient, species))
180
+ else:
181
+ # Just the species
182
+ species = Species(name=rc.species_name)
183
+ result.append(species)
184
+ return result
185
+
186
+ @nitrate_products.setter
187
+ def nitrate_products(self, value: List[Union[Species, Tuple[float, Species]]]):
188
+ """Set the nitrate products, converting from Python to C++ objects."""
189
+ cpp_products = []
190
+ for p in value:
191
+ if isinstance(p, Species):
192
+ cpp_products.append(_ReactionComponent(p.name))
193
+ elif isinstance(p, tuple) and len(p) == 2:
194
+ coefficient, species = p
195
+ cpp_products.append(_ReactionComponent(species.name, coefficient))
196
+ else:
197
+ raise ValueError(f"Invalid nitrate product format: {p}")
198
+ self._instance.nitrate_products = cpp_products
199
+
200
+ @property
201
+ def alkoxy_products(self) -> List[Union[Species, Tuple[float, Species]]]:
202
+ """Get the alkoxy products as Python objects."""
203
+ # Convert from C++ _ReactionComponent objects to Python Species objects
204
+ result = []
205
+ for rc in self._instance.alkoxy_products:
206
+ if hasattr(rc, 'coefficient') and rc.coefficient != 1.0:
207
+ # Create a tuple with coefficient and species
208
+ species = Species(name=rc.species_name)
209
+ result.append((rc.coefficient, species))
210
+ else:
211
+ # Just the species
212
+ species = Species(name=rc.species_name)
213
+ result.append(species)
214
+ return result
215
+
216
+ @alkoxy_products.setter
217
+ def alkoxy_products(self, value: List[Union[Species, Tuple[float, Species]]]):
218
+ """Set the alkoxy products, converting from Python to C++ objects."""
219
+ cpp_products = []
220
+ for p in value:
221
+ if isinstance(p, Species):
222
+ cpp_products.append(_ReactionComponent(p.name))
223
+ elif isinstance(p, tuple) and len(p) == 2:
224
+ coefficient, species = p
225
+ cpp_products.append(_ReactionComponent(species.name, coefficient))
226
+ else:
227
+ raise ValueError(f"Invalid alkoxy product format: {p}")
228
+ self._instance.alkoxy_products = cpp_products
229
+
230
+ @property
231
+ def gas_phase(self) -> str:
232
+ """Get the gas phase name."""
233
+ return self._instance.gas_phase
234
+
235
+ @gas_phase.setter
236
+ def gas_phase(self, value: Union[Phase, str]):
237
+ """Set the gas phase."""
238
+ if isinstance(value, Phase):
239
+ self._instance.gas_phase = value.name
240
+ elif isinstance(value, str):
241
+ self._instance.gas_phase = value
242
+ else:
243
+ raise ValueError(f"Invalid gas_phase type: {type(value)}")
244
+
245
+ @property
246
+ def other_properties(self) -> Dict[str, Any]:
247
+ """Get the other properties."""
248
+ return self._instance.other_properties
249
+
250
+ @other_properties.setter
251
+ def other_properties(self, value: Dict[str, Any]):
252
+ """Set the other properties."""
253
+ self._instance.other_properties = value
254
+
255
+ @property
256
+ def type(self):
257
+ """Get the reaction type."""
258
+ return ReactionType.Branched
259
+
260
+ def serialize(self) -> Dict:
261
+ """
262
+ Serialize the Branched object to a dictionary using only Python-visible data.
263
+
264
+ Returns:
265
+ Dict: A dictionary representation of the Branched object.
266
+ """
103
267
  serialize_dict = {
104
268
  "type": "BRANCHED_NO_RO2",
105
- "name": instance.name,
106
- "X": instance.X,
107
- "Y": instance.Y,
108
- "a0": instance.a0,
109
- "n": instance.n,
110
- "reactants": ReactionComponentSerializer.serialize_list_reaction_components(instance.reactants),
111
- "nitrate products": ReactionComponentSerializer.serialize_list_reaction_components(instance.nitrate_products),
112
- "alkoxy products": ReactionComponentSerializer.serialize_list_reaction_components(instance.alkoxy_products),
113
- "gas phase": instance.gas_phase,
269
+ "name": self.name,
270
+ "X": self.X,
271
+ "Y": self.Y,
272
+ "a0": self.a0,
273
+ "n": self.n,
274
+ "reactants": ReactionComponentSerializer.serialize_list_reaction_components(
275
+ self._instance.reactants),
276
+ "nitrate products": ReactionComponentSerializer.serialize_list_reaction_components(
277
+ self._instance.nitrate_products),
278
+ "alkoxy products": ReactionComponentSerializer.serialize_list_reaction_components(
279
+ self._instance.alkoxy_products),
280
+ "gas phase": self.gas_phase,
114
281
  }
115
- _add_other_properties(serialize_dict, instance.other_properties)
116
- return _remove_empty_keys(serialize_dict)
282
+ _add_other_properties(serialize_dict, self.other_properties)
283
+ return serialize_dict
284
+
285
+ @staticmethod
286
+ def serialize_static(instance) -> Dict:
287
+ """
288
+ Static serialize method for compatibility with C++ _Branched objects.
289
+
290
+ Args:
291
+ instance: The _Branched instance to serialize.
292
+
293
+ Returns:
294
+ Dict: A dictionary representation of the Branched object.
295
+ """
296
+ # Create a temporary Branched object and use its instance serialize method
297
+ temp_branched = Branched()
298
+ temp_branched._instance = instance
299
+ return temp_branched.serialize()