GNNPCSAFTPropertyPackage 0.1.0__py3-none-any.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.
@@ -0,0 +1 @@
1
+ "GNNPCSAFT CAPE-OPEN Property Package"
@@ -0,0 +1,10 @@
1
+ "ICapeIdentification"
2
+
3
+ from comtypes.gen import CAPEOPEN110
4
+
5
+
6
+ class ICapeIdentification(CAPEOPEN110.ICapeIdentification):
7
+ "ICapeIdentification Class with methods implemented"
8
+
9
+ ComponentName = "GNNPCSAFT Property Package"
10
+ ComponentDescription = "PC-SAFT Property Package with GNN-estimated parameters"
@@ -0,0 +1,289 @@
1
+ """
2
+ IPersistStreamInit implementation for a CAPE-OPEN Property Package built
3
+ with comtypes.
4
+
5
+ Why this exists
6
+ ----------------
7
+ Per the CAPE-OPEN "Persistence Common Interface" specification (section
8
+ 4.1) and its Errata/Clarifications document (clarification 3), a PMC that
9
+ wants a PME to save/restore its configuration instead of re-asking the
10
+ user for it every session must implement IPersistStream or
11
+ IPersistStreamInit. IPersistStreamInit is preferred because it also gets
12
+ an InitNew() call when the PMC is created fresh (as opposed to reloaded),
13
+ which VB6-era PMEs rely on -- the Errata explicitly says PMCs "should not
14
+ raise an error if both the InitNew and Load methods are called".
15
+
16
+ Crucially, both InitNew and Load are guaranteed by the COM middleware
17
+ life-cycle to be called *before* ICapeUtilities.Initialize() (see the main
18
+ spec, section 4.1: "even Initialize method ... will be called after
19
+ invoking the methods InitNew and Load"). So Initialize() just needs to
20
+ check whether Load() already gave it a component list, and skip the
21
+ SMILES-entry dialog if so.
22
+
23
+ What gets persisted
24
+ --------------------
25
+ Per the Errata (clarification 4, "What should be saved and restored?"):
26
+ "There is no need to persist data that can be reconstructed by the PMC to
27
+ the same state it was in at the time the PMC was saved." PC-SAFT
28
+ parameters are a deterministic function of a SMILES string, so we do NOT
29
+ persist self.pcsaft_parameters -- we recompute it from the restored SMILES
30
+ list via the existing _config_gnnpcsaft() helper, same as Initialize()
31
+ does for a freshly-entered component list. We DO persist:
32
+ - self.components_smiles (cannot be reconstructed -- it's user input)
33
+ - the kij values (cannot be reconstructed -- also user input)
34
+
35
+ Stream format
36
+ -------------
37
+ A small versioned, self-describing binary blob (little-endian):
38
+ 4 bytes magic b"GPS1"
39
+ int32 version format version, currently 1
40
+ int32 n_components
41
+ per component:
42
+ int32 len(utf8 bytes)
43
+ bytes utf8-encoded SMILES string
44
+ int32 n_kij_values (== n*(n-1)/2 for n components)
45
+ n_kij_values * float64 kij values, in the same flat upper-triangle
46
+ order (i<j, i outer loop) that
47
+ ICapeUtilities._config_gnnpcsaft expects.
48
+ """
49
+
50
+ import ctypes
51
+ import logging
52
+ import struct
53
+ from ctypes import c_ubyte
54
+
55
+ from comtypes import COMMETHOD, GUID, HRESULT, IUnknown
56
+ from comtypes.stream import ISequentialStream
57
+
58
+ from .ecape_errors import ECapeUnknown
59
+ from .utils_common import GNNPCSAFTPPbase
60
+
61
+ _FORMAT_MAGIC = b"GPS1"
62
+ _FORMAT_VERSION = 1
63
+ _READ_CHUNK = 65536
64
+
65
+
66
+ class IStream(ISequentialStream):
67
+ """Minimal binding for the standard OLE IStream interface.
68
+
69
+ IStream's vtable is IUnknown (3 slots) + ISequentialStream's Read/
70
+ Write (2 slots) + Seek/SetSize/CopyTo/Commit/Revert/LockRegion/
71
+ UnlockRegion/Stat/Clone. We only ever call Read/Write, and those sit
72
+ at the same vtable offset in IStream as they do in ISequentialStream
73
+ (right after IUnknown), so inheriting ISequentialStream without
74
+ redeclaring the rest of IStream's methods is enough to call them
75
+ safely -- we simply never reach the undeclared slots.
76
+ """
77
+
78
+ _iid_ = GUID("{0000000C-0000-0000-C000-000000000046}") # IID_IStream
79
+
80
+
81
+ class IPersistStreamInit(IUnknown):
82
+ "IPersistStreamInit implementation"
83
+
84
+ _iid_ = GUID("{7FD52380-4E07-101B-AE2D-08002B2EC713}")
85
+ _methods_ = [
86
+ # IPersist method
87
+ COMMETHOD(
88
+ [], HRESULT, "GetClassID", (["out"], ctypes.POINTER(GUID), "pClassID")
89
+ ),
90
+ # IPersistStream / IPersistStreamInit methods
91
+ COMMETHOD([], HRESULT, "IsDirty"),
92
+ COMMETHOD([], HRESULT, "Load", (["in"], ctypes.POINTER(IStream), "pStm")),
93
+ COMMETHOD(
94
+ [],
95
+ HRESULT,
96
+ "Save",
97
+ (["in"], ctypes.POINTER(IStream), "pStm"),
98
+ (["in"], ctypes.c_int, "fClearDirty"),
99
+ ),
100
+ COMMETHOD(
101
+ [],
102
+ HRESULT,
103
+ "GetSizeMax",
104
+ (["out"], ctypes.POINTER(ctypes.c_ulonglong), "pcbSize"),
105
+ ),
106
+ COMMETHOD([], HRESULT, "InitNew"),
107
+ ]
108
+
109
+
110
+ class ICapePersistence(GNNPCSAFTPPbase):
111
+ """Implements IPersistStreamInit so a PME can save/restore the
112
+ component (SMILES) list and kij matrix without re-prompting the user
113
+ on every load.
114
+ """
115
+
116
+ def __init__(self):
117
+ super().__init__()
118
+ self._dirty = False
119
+
120
+ # -- IPersist ------------------------------------------------------
121
+ def GetClassID(self):
122
+ """Return this PMC's registered CLSID."""
123
+ logging.debug("---> TRYING TO GetClassID <---")
124
+ clsid = getattr(type(self), "_reg_clsid_", None)
125
+ if clsid is None:
126
+ self.raise_cape_error(
127
+ ECapeUnknown,
128
+ "No CLSID registered for this Property Package.",
129
+ interfaceName="IPersistStreamInit",
130
+ operation="GetClassID",
131
+ )
132
+ return GUID(clsid)
133
+
134
+ # -- IPersistStreamInit ---------------------------------------------
135
+ def IsDirty(self):
136
+ """
137
+ S_OK (0) means dirty, S_FALSE (1) means unchanged since last
138
+ Save()/Load()/InitNew().
139
+
140
+ Default S_OK: always "dirty"; the payload is tiny, so saving unconditionally is cheap
141
+ """
142
+ logging.debug("---> TRYING TO IsDirty <---")
143
+ return 0
144
+
145
+ def InitNew(self):
146
+ """Called by the PME when creating a brand-new (not restored)
147
+ instance. Reset to an empty, unconfigured state so
148
+ ICapeUtilities.Initialize() knows to prompt the user, per the
149
+ Errata's note that InitNew and Load may both be called and a PMC
150
+ should not error out over that.
151
+ """
152
+ logging.debug("---> TRYING TO InitNew <---")
153
+ self.components_smiles = None
154
+ self.pcsaft_parameters = None
155
+ self._kij_matrix = None
156
+ self._dirty = False
157
+ return 0
158
+
159
+ def Load(self, stream):
160
+ """Restore components_smiles / kij from a previously Save()'d
161
+ stream, then rebuild pcsaft_parameters and _kij_matrix via the
162
+ same path Initialize()/Edit() use for freshly-entered data.
163
+ """
164
+ logging.debug("---> TRYING TO LOAD <---")
165
+ try:
166
+ data = self._stream_read_all(stream)
167
+ smiles_list, kij_values = self._deserialize(data)
168
+ except Exception as exc: # pylint: disable=broad-exception-caught
169
+ self.raise_cape_error(
170
+ ECapeUnknown,
171
+ f"Could not restore persisted component data: {exc}",
172
+ interfaceName="IPersistStreamInit",
173
+ operation="Load",
174
+ )
175
+
176
+ if smiles_list:
177
+ self._config_gnnpcsaft(smiles_list, kij_values)
178
+ else:
179
+ self.components_smiles = None
180
+ self.pcsaft_parameters = None
181
+ self._kij_matrix = None
182
+
183
+ self._dirty = False
184
+ return 0
185
+
186
+ def Save(self, stream, clear_dirty):
187
+ """Persist components_smiles and the kij values. pcsaft_parameters
188
+ is intentionally NOT persisted -- it's reconstructed on Load()."""
189
+ logging.debug("---> TRYING TO SAVE <---")
190
+ try:
191
+ data = self._serialize(self.components_smiles, self._flatten_kij())
192
+ self._stream_write_all(stream, data)
193
+ except Exception as exc: # pylint: disable=broad-exception-caught
194
+ self.raise_cape_error(
195
+ ECapeUnknown,
196
+ f"Could not save component data: {exc}",
197
+ interfaceName="IPersistStreamInit",
198
+ operation="Save",
199
+ )
200
+
201
+ if clear_dirty:
202
+ self._dirty = False
203
+ return 0
204
+
205
+ def GetSizeMax(self):
206
+ """Upper bound (here, exact size) in bytes of the stream Save()
207
+ would produce right now."""
208
+ logging.debug("---> TRYING TO GetSizeMax <---")
209
+ data = self._serialize(self.components_smiles, self._flatten_kij())
210
+ return len(data)
211
+
212
+ # -- call this from Initialize()/Edit() after _config_gnnpcsaft -----
213
+ def _mark_dirty(self):
214
+ self._dirty = True
215
+
216
+ # -- serialization helpers -------------------------------------------
217
+ def _flatten_kij(self):
218
+ """Flatten self._kij_matrix back into the (i<j) upper-triangle
219
+ list that _config_gnnpcsaft's kij_values parameter expects, i.e.
220
+ the inverse of the matrix-building loop in ICapeUtilities."""
221
+ matrix = getattr(self, "_kij_matrix", None) or []
222
+ n = len(matrix)
223
+ values = []
224
+ for i in range(n):
225
+ for j in range(i + 1, n):
226
+ values.append(matrix[i][j])
227
+ return values
228
+
229
+ @staticmethod
230
+ def _serialize(smiles_list, kij_values):
231
+ buf = bytearray()
232
+ buf += _FORMAT_MAGIC
233
+ buf += struct.pack("<i", _FORMAT_VERSION)
234
+ buf += struct.pack("<i", len(smiles_list))
235
+ for smiles in smiles_list:
236
+ encoded = smiles.encode("utf-8")
237
+ buf += struct.pack("<i", len(encoded))
238
+ buf += encoded
239
+ buf += struct.pack("<i", len(kij_values))
240
+ for value in kij_values:
241
+ buf += struct.pack("<d", value)
242
+ return bytes(buf)
243
+
244
+ @staticmethod
245
+ def _deserialize(data):
246
+ offset = 0
247
+ magic = data[offset : offset + 4]
248
+ offset += 4
249
+ if magic != _FORMAT_MAGIC:
250
+ raise ValueError("Unrecognised persistence stream format.")
251
+
252
+ (version,) = struct.unpack_from("<i", data, offset)
253
+ offset += 4
254
+ if version != _FORMAT_VERSION:
255
+ raise ValueError(f"Unsupported persistence format version: {version}")
256
+
257
+ (n_components,) = struct.unpack_from("<i", data, offset)
258
+ offset += 4
259
+ smiles_list = []
260
+ for _ in range(n_components):
261
+ (str_len,) = struct.unpack_from("<i", data, offset)
262
+ offset += 4
263
+ smiles_list.append(data[offset : offset + str_len].decode("utf-8"))
264
+ offset += str_len
265
+
266
+ (n_kij,) = struct.unpack_from("<i", data, offset)
267
+ offset += 4
268
+ kij_values = list(struct.unpack_from(f"<{n_kij}d", data, offset))
269
+ offset += n_kij * 8
270
+
271
+ return smiles_list, kij_values
272
+
273
+ # -- raw IStream I/O ---------------------------------------------------
274
+ @staticmethod
275
+ def _stream_read_all(istream):
276
+ chunks = []
277
+ while True:
278
+ pv, bytes_read = istream.RemoteRead(_READ_CHUNK)
279
+ if bytes_read == 0:
280
+ break
281
+ chunks.append(bytes(bytearray(pv)[:bytes_read]))
282
+ if bytes_read < _READ_CHUNK:
283
+ break
284
+ return b"".join(chunks)
285
+
286
+ @staticmethod
287
+ def _stream_write_all(istream, data):
288
+ buf = (c_ubyte * len(data)).from_buffer_copy(data)
289
+ istream.RemoteWrite(buf, len(data))
@@ -0,0 +1,321 @@
1
+ "ICapeThermoCompounds"
2
+
3
+ import copy
4
+ import logging
5
+ import math
6
+ from typing import List
7
+
8
+ import si_units as si
9
+ from comtypes.gen import CAPEOPEN110
10
+ from feos import Contributions # pyright: ignore[reportAttributeAccessIssue]
11
+ from gnnepcsaft.pcsaft.feos.pure import (
12
+ critical_points_feos,
13
+ pure_den_feos,
14
+ pure_h_lv_feos,
15
+ pure_surface_tension_at_t_feos,
16
+ pure_vle_at_t_feos,
17
+ pure_vp_feos,
18
+ )
19
+
20
+ from . import ecape_errors
21
+ from .utils_common import GNNPCSAFTPPbase, OutboundVARIANT
22
+
23
+ _CONST_PROPS = [
24
+ "molecularWeight",
25
+ "SMILESformula",
26
+ "criticalDensity",
27
+ "criticalPressure",
28
+ "criticalTemperature",
29
+ "heatOfVaporizationAtNormalBoilingPoint",
30
+ "liquidDensityAt25C",
31
+ "normalBoilingPoint",
32
+ ]
33
+
34
+ T_PROP_LIST = [
35
+ "heatCapacityOfLiquid",
36
+ "heatOfVaporization",
37
+ "vaporPressure",
38
+ "volumeOfLiquid",
39
+ "fugacityCoefficientOfVapor",
40
+ "volumeChangeUponVaporization",
41
+ "surfaceTensionSatLiquid",
42
+ ]
43
+
44
+
45
+ class ICapeThermoCompounds(GNNPCSAFTPPbase, CAPEOPEN110.ICapeThermoCompounds):
46
+ "ICapeThermoCompounds Class with methods implemented"
47
+
48
+ # --- ICapeThermoCompounds ---
49
+ def GetCompoundConstant(self, props, compIds):
50
+ """
51
+ Returns the values of constant Physical Properties for the specified
52
+ Compounds.
53
+
54
+ Args:
55
+ props (CapeArrayString): list of constant Physical Property
56
+ identifiers (section 7.5.2).
57
+ compIds (CapeArrayString): list of Compound identifiers, or
58
+ UNDEFINED for all Compounds.
59
+
60
+ Returns:
61
+ propvals (CapeArrayVariant): C*P values -- the first C values
62
+ are for the first requested property (one per Compound),
63
+ then C values for the second property, and so on.
64
+
65
+ Raises (per spec): ECapeNoImpl, ECapeThrmPropertyNotAvailable,
66
+ ECapeLimitedImpl, ECapeInvalidArgument, ECapeUnknown,
67
+ ECapeBadInvOrder.
68
+ """
69
+ co_props = copy.copy(props.value)
70
+ co_compIds = copy.copy(list(compIds.value))
71
+
72
+ requested_props = [str(p) for p in self._as_list(co_props)]
73
+ logging.debug("IN GetCompoundConstant ---> requesting %r", (props, compIds))
74
+ indices = self._compound_indices(co_compIds)
75
+ pcsaft_parameters = copy.copy(self.pcsaft_parameters)
76
+ assert pcsaft_parameters is not None
77
+ _propvals = []
78
+ for prop in requested_props:
79
+ key = prop.strip()
80
+ for idx in indices:
81
+ if key == "molecularWeight":
82
+ _propvals.append(pcsaft_parameters[idx][8])
83
+ if key == "SMILESformula":
84
+ _propvals.append(co_compIds[idx])
85
+ if key in (
86
+ "criticalDensity",
87
+ "criticalPressure",
88
+ "criticalTemperature",
89
+ ):
90
+ tc, pc, dc = critical_points_feos(pcsaft_parameters[idx])
91
+ if key == "criticalDensity":
92
+ _propvals.append(dc)
93
+ if key == "criticalPressure":
94
+ _propvals.append(pc)
95
+ if key == "criticalTemperature":
96
+ _propvals.append(tc)
97
+ if key == "heatOfVaporizationAtNormalBoilingPoint":
98
+ _propvals.append(
99
+ pure_h_lv_feos(
100
+ pcsaft_parameters[idx],
101
+ [298.15],
102
+ )
103
+ * 1000.0
104
+ )
105
+ if key == "liquidDensityAt25C":
106
+ _propvals.append(
107
+ pure_den_feos(pcsaft_parameters[idx], [298.15, 101325.0])
108
+ )
109
+ if key == "normalBoilingPoint":
110
+ _propvals.append(pure_vp_feos(pcsaft_parameters[idx], [298.15]))
111
+ if key not in _CONST_PROPS:
112
+ self.raise_cape_error(
113
+ error_cls=ecape_errors.ECapeThrmPropertyNotAvailable,
114
+ description=(
115
+ f"Requested compound constant ({key}) are not available;"
116
+ f" only {_CONST_PROPS} are supported by this Property Package"
117
+ ),
118
+ interfaceName="ICapeThermoCompounds",
119
+ operation="GetCompoundConstant",
120
+ )
121
+ logging.debug("IN GetCompoundConstant ---> %r = %r", key, _propvals)
122
+ return OutboundVARIANT(_propvals)
123
+
124
+ def GetCompoundList(self, compIds, formulae, names, boilTemps, molwts, casnos):
125
+ """
126
+ Returns the list of all Compounds, with identifiers and additional
127
+ identifying information.
128
+
129
+ Returns (all ACTUALLYout, CapeArrayString/CapeArrayDouble):
130
+ compIds, formulae, names, boilTemps, molwts, casnos
131
+
132
+ Raises (per spec): ECapeNoImpl, ECapeUnknown, ECapeBadInvOrder.
133
+ """
134
+ self._require_components(
135
+ interfaceName="ICapeThermoCompounds",
136
+ operation="GetCompoundList",
137
+ )
138
+ pcsaft_parameters = copy.copy(self.pcsaft_parameters)
139
+ assert pcsaft_parameters is not None
140
+ assert self.components_smiles is not None
141
+
142
+ return (
143
+ self.bstr_array_variant(self.components_smiles),
144
+ self.bstr_array_variant([""] * len(self.components_smiles)),
145
+ self.bstr_array_variant(self.components_smiles),
146
+ self.r8_array_variant([float("nan")] * len(self.components_smiles)),
147
+ self.r8_array_variant([parameters[8] for parameters in pcsaft_parameters]),
148
+ self.bstr_array_variant([""] * len(self.components_smiles)),
149
+ )
150
+
151
+ def GetConstPropList(self):
152
+ """
153
+ Returns the list of supported constant Physical Properties (i.e.
154
+ those retrievable via GetCompoundConstant).
155
+
156
+ Returns:
157
+ props (CapeArrayString)
158
+
159
+ Raises (per spec): ECapeNoImpl, ECapeUnknown, ECapeBadInvOrder.
160
+ """
161
+ self._require_components(
162
+ interfaceName="ICapeThermoCompounds",
163
+ operation="GetConstPropList",
164
+ )
165
+ return self.bstr_array_variant(_CONST_PROPS)
166
+
167
+ def GetNumCompounds(self):
168
+ """
169
+ Returns the number of Compounds supported.
170
+
171
+ Returns:
172
+ num (CapeLong)
173
+
174
+ Raises (per spec): ECapeNoImpl, ECapeUnknown, ECapeBadInvOrder.
175
+ """
176
+ self._require_components(
177
+ interfaceName="ICapeThermoCompounds",
178
+ operation="GetNumCompounds",
179
+ )
180
+ assert self.components_smiles is not None
181
+ return len(self.components_smiles)
182
+
183
+ def GetPDependentProperty(self, props, pressure, compIds, propVals):
184
+ """
185
+ Returns the values of pressure-dependent Physical Properties for the
186
+ specified pure Compounds.
187
+
188
+ Args:
189
+ props (CapeArrayString): pressure-dependent property identifiers
190
+ (section 7.5.4).
191
+ pressure (CapeDouble): pressure (Pa) at which to evaluate.
192
+ compIds (CapeArrayString): Compound identifiers, or UNDEFINED
193
+ for all Compounds.
194
+
195
+ Returns:
196
+ propvals (CapeArrayDouble): C*P values, ordered property-major.
197
+
198
+ Raises (per spec): ECapeNoImpl, ECapeLimitedImpl,
199
+ ECapeInvalidArgument, ECapeOutOfBounds,
200
+ ECapeThrmPropertyNotAvailable, ECapeUnknown, ECapeBadInvOrder.
201
+ """
202
+
203
+ def GetPDependentPropList(self):
204
+ """
205
+ Returns the list of supported pressure-dependent properties (i.e.
206
+ those retrievable via GetPDependentProperty).
207
+
208
+ Returns:
209
+ props (CapeArrayString)
210
+
211
+ Raises (per spec): ECapeNoImpl, ECapeUnknown, ECapeBadInvOrder.
212
+ """
213
+ return self.empty_array_variant()
214
+
215
+ def GetTDependentProperty(self, props, temperature, compIds, propVals):
216
+ """
217
+ Returns the values of temperature-dependent Physical Properties for
218
+ the specified pure Compounds.
219
+
220
+ Args:
221
+ props (CapeArrayString): temperature-dependent property
222
+ identifiers (section 7.5.3).
223
+ temperature (CapeDouble): temperature (K) at which to evaluate.
224
+ compIds (CapeArrayString): Compound identifiers, or UNDEFINED
225
+ for all Compounds.
226
+
227
+ Returns:
228
+ propvals (CapeArrayDouble): C*P values, ordered property-major.
229
+
230
+ Raises (per spec): ECapeNoImpl, ECapeLimitedImpl,
231
+ ECapeInvalidArgument, ECapeOutOfBounds,
232
+ ECapeThrmPropertyNotAvailable, ECapeUnknown, ECapeBadInvOrder.
233
+ """
234
+ co_props = copy.copy(props.value)
235
+ co_compIds = copy.copy(compIds.value)
236
+
237
+ requested_props = [str(p) for p in self._as_list(co_props)]
238
+ indices = self._compound_indices(co_compIds)
239
+ pcsaft_parameters = copy.copy(self.pcsaft_parameters)
240
+ assert pcsaft_parameters is not None
241
+ _propvals: List[float] = []
242
+ for prop in requested_props:
243
+ key = prop.strip()
244
+ for idx in indices:
245
+ if key == "vaporPressure":
246
+ _propvals.append(
247
+ pure_vp_feos(pcsaft_parameters[idx], [temperature])
248
+ )
249
+ if key == "volumeOfLiquid":
250
+ vp = pure_vp_feos(pcsaft_parameters[idx], [temperature])
251
+ _propvals.append(
252
+ 1 / pure_den_feos(pcsaft_parameters[idx], [temperature, vp])
253
+ )
254
+ if key == "heatOfVaporization":
255
+ h_lv = (
256
+ pure_h_lv_feos(pcsaft_parameters[idx], [temperature]) * 1000.0
257
+ )
258
+ _propvals.append(h_lv)
259
+
260
+ if key == "heatCapacityOfLiquid":
261
+ vle = pure_vle_at_t_feos(
262
+ parameters=pcsaft_parameters[idx], temperature=temperature
263
+ )
264
+ cp = vle.liquid.molar_isobaric_heat_capacity(
265
+ Contributions.Residual
266
+ ) / (si.JOULE / si.MOL / si.KELVIN)
267
+ _propvals.append(cp)
268
+ if key == "fugacityCoefficientOfVapor":
269
+ vle = pure_vle_at_t_feos(
270
+ parameters=pcsaft_parameters[idx], temperature=temperature
271
+ )
272
+ ln_phi = vle.vapor.ln_phi()
273
+ _propvals.append(math.exp(ln_phi[0]))
274
+ if key == "volumeChangeUponVaporization":
275
+ vle = pure_vle_at_t_feos(
276
+ parameters=pcsaft_parameters[idx], temperature=temperature
277
+ )
278
+ volume_vapor = 1 / vle.vapor.density / (si.METER**3 / si.MOL)
279
+ volume_liquid = 1 / vle.liquid.density / (si.METER**3 / si.MOL)
280
+ _propvals.append(volume_vapor - volume_liquid)
281
+ if key == "surfaceTensionSatLiquid":
282
+ st = (
283
+ pure_surface_tension_at_t_feos(
284
+ parameters=pcsaft_parameters[idx], temperature=temperature
285
+ )
286
+ * 1e-3
287
+ )
288
+ _propvals.append(st)
289
+
290
+ logging.debug("IN TDependetProperty ---> %r = %r", key, _propvals)
291
+ return self.r8_array_variant(_propvals)
292
+
293
+ def GetTDependentPropList(self):
294
+ """
295
+ Returns the list of supported temperature-dependent properties (i.e.
296
+ those retrievable via GetTDependentProperty).
297
+
298
+ Returns:
299
+ props (CapeArrayString)
300
+
301
+ Raises (per spec): ECapeNoImpl, ECapeUnknown, ECapeBadInvOrder.
302
+ """
303
+ return self.bstr_array_variant(T_PROP_LIST)
304
+
305
+ def _compound_indices(self, compIds):
306
+ assert self.components_smiles is not None
307
+ if compIds is None:
308
+ return list(range(len(self.components_smiles)))
309
+ lower_names = [n.lower() for n in self.components_smiles]
310
+ indices = []
311
+ for cid in self._as_list(compIds):
312
+ try:
313
+ indices.append(lower_names.index(str(cid).lower()))
314
+ except ValueError:
315
+ self.raise_cape_error(
316
+ error_cls=ecape_errors.ECapeInvalidArgument,
317
+ description=f"Unrecognised compound identifier: {cid!r}",
318
+ interfaceName="ICapeThermoCompounds",
319
+ operation="GetTDependentPropList",
320
+ )
321
+ return indices