gnnepcsaft-mcp-server 0.1.1__tar.gz → 0.1.2__tar.gz

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 gnnepcsaft-mcp-server might be problematic. Click here for more details.

@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: gnnepcsaft-mcp-server
3
- Version: 0.1.1
3
+ Version: 0.1.2
4
4
  Summary: Model Context Protocol server for GNNePCSAFT tools
5
5
  License: GNU General Public License v3.0
6
6
  Author: wildsonbbl
@@ -0,0 +1,49 @@
1
+ "GNNePCSAFT MCP Server"
2
+
3
+ from typing import Any, Callable, List
4
+
5
+ from mcp.server.fastmcp import FastMCP
6
+
7
+ from .utils import (
8
+ batch_convert_pure_density_to_kg_per_m3,
9
+ batch_critical_points,
10
+ batch_inchi_to_smiles,
11
+ batch_molecular_weights,
12
+ batch_pa_to_bar,
13
+ batch_predict_epcsaft_parameters,
14
+ batch_pure_density,
15
+ batch_pure_h_lv,
16
+ batch_pure_vapor_pressure,
17
+ batch_smiles_to_inchi,
18
+ mixture_density,
19
+ mixture_phase,
20
+ mixture_vapor_pressure,
21
+ pubchem_description,
22
+ pure_phase,
23
+ )
24
+
25
+ mcp = FastMCP("gnnepcsaft")
26
+ fn_list: List[Callable[..., Any]] = [
27
+ batch_convert_pure_density_to_kg_per_m3,
28
+ batch_critical_points,
29
+ batch_inchi_to_smiles,
30
+ batch_molecular_weights,
31
+ batch_pa_to_bar,
32
+ batch_predict_epcsaft_parameters,
33
+ batch_pure_density,
34
+ batch_pure_h_lv,
35
+ batch_pure_vapor_pressure,
36
+ batch_smiles_to_inchi,
37
+ mixture_density,
38
+ mixture_phase,
39
+ mixture_vapor_pressure,
40
+ pubchem_description,
41
+ pure_phase,
42
+ ]
43
+ for fn in fn_list:
44
+ mcp.add_tool(fn)
45
+
46
+
47
+ def run():
48
+ "run stdio"
49
+ mcp.run("stdio")
@@ -0,0 +1,328 @@
1
+ "Utils for MCP Server"
2
+
3
+ from json import loads
4
+ from pathlib import Path
5
+ from typing import List, Literal, Tuple
6
+ from urllib.parse import quote
7
+ from urllib.request import HTTPError, urlopen
8
+
9
+ import numpy as np
10
+ import onnxruntime as ort
11
+ from gnnepcsaft.data.ogb_utils import smiles2graph
12
+ from gnnepcsaft.data.rdkit_util import assoc_number, inchitosmiles, mw, smilestoinchi
13
+ from gnnepcsaft.epcsaft.epcsaft_feos import (
14
+ critical_points_feos,
15
+ mix_den_feos,
16
+ mix_vp_feos,
17
+ pure_den_feos,
18
+ pure_h_lv_feos,
19
+ pure_vp_feos,
20
+ )
21
+
22
+ file_dir = Path(__file__).parent
23
+ model_dir = file_dir / "models"
24
+ ort.set_default_logger_severity(3)
25
+
26
+ msigmae_onnx = ort.InferenceSession(model_dir / "msigmae_7.onnx")
27
+ assoc_onnx = ort.InferenceSession(model_dir / "assoc_8.onnx")
28
+
29
+
30
+ def predict_epcsaft_parameters(
31
+ smiles: str,
32
+ ) -> List[float]:
33
+ """Predict ePC-SAFT parameters
34
+ `[m, sigma, epsilon/kB, kappa_ab, epsilon_ab/kB, dipole moment, na, nb]` with
35
+ the GNNePCSAFT model.
36
+
37
+ Args:
38
+ smiles (str): SMILES of the molecule.
39
+ """
40
+ lower_bounds = np.asarray([1.0, 1.9, 50.0, 0.0, 0.0, 0, 0, 0])
41
+ upper_bounds = np.asarray([25.0, 4.5, 550.0, 0.9, 5000.0, np.inf, np.inf, np.inf])
42
+
43
+ inchi = smilestoinchi(smiles)
44
+
45
+ graph = smiles2graph(smiles)
46
+ na, nb = assoc_number(inchi)
47
+ x, edge_index, edge_attr = (
48
+ graph["node_feat"],
49
+ graph["edge_index"],
50
+ graph["edge_feat"],
51
+ )
52
+
53
+ assoc = 10 ** (
54
+ assoc_onnx.run(
55
+ None,
56
+ {
57
+ "x": x,
58
+ "edge_index": edge_index,
59
+ "edge_attr": edge_attr,
60
+ },
61
+ )[0][0]
62
+ * np.asarray([-1.0, 1.0])
63
+ )
64
+ if na == 0 and nb == 0:
65
+ assoc *= 0
66
+ msigmae = msigmae_onnx.run(
67
+ None,
68
+ {
69
+ "x": x,
70
+ "edge_index": edge_index,
71
+ "edge_attr": edge_attr,
72
+ },
73
+ )[0][0]
74
+ munanb = np.asarray([0.0, na, nb])
75
+ pred = np.hstack([msigmae, assoc, munanb], dtype=np.float64)
76
+ np.clip(pred, lower_bounds, upper_bounds, out=pred)
77
+
78
+ return pred.tolist() # type: ignore
79
+
80
+
81
+ def pure_phase(
82
+ vapor_pressure: float, system_pressure: float
83
+ ) -> Literal["liquid", "vapor"]:
84
+ """
85
+ Given the vapor pressure and system pressure, return the phase of the molecule.
86
+ Both pressures must be in the same unit.
87
+
88
+ Args:
89
+ vapor_pressure (float): The calculated vapor pressure of the pure component.
90
+ system_pressure (float): The actual system pressure.
91
+
92
+ """
93
+ assert isinstance(vapor_pressure, (int, float)), "vapor_pressure must be a number"
94
+ assert isinstance(system_pressure, (int, float)), "system_pressure must be a number"
95
+ assert vapor_pressure > 0, "vapor_pressure must be positive"
96
+ assert system_pressure > 0, "system_pressure must be positive"
97
+
98
+ return "liquid" if vapor_pressure < system_pressure else "vapor"
99
+
100
+
101
+ def mixture_phase(
102
+ bubble_point: float,
103
+ dew_point: float,
104
+ system_pressure: float,
105
+ ) -> Literal["liquid", "vapor", "two-phase"]:
106
+ """
107
+ Given the bubble/dew point of the mixture and the system pressure,
108
+ return the phase of the mixture.
109
+ All pressures must be in the same unit.
110
+
111
+ Args:
112
+ bubble_point (float): The calculated bubble point of the mixture.
113
+ dew_point (float): The calculated dew point of the mixture.
114
+ system_pressure (float): The actual system pressure.
115
+ """
116
+ assert isinstance(bubble_point, (int, float)), "bubble_point must be a number"
117
+ assert isinstance(dew_point, (int, float)), "dew_point must be a number"
118
+ assert isinstance(system_pressure, (int, float)), "system_pressure must be a number"
119
+ assert bubble_point > 0, "bubble_point must be positive"
120
+ assert dew_point > 0, "dew_point must be positive"
121
+ assert system_pressure > 0, "system_pressure must be positive"
122
+ return (
123
+ "liquid"
124
+ if bubble_point < system_pressure
125
+ else ("two-phase" if dew_point <= system_pressure else "vapor")
126
+ )
127
+
128
+
129
+ def pubchem_description(smiles: str) -> str:
130
+ """
131
+ Look for information on PubChem for the SMILES.
132
+
133
+ Args:
134
+ smiles (str): The SMILES of the molecule.
135
+ """
136
+
137
+ try:
138
+ inchi = smilestoinchi(smiles)
139
+ url = (
140
+ "https://pubchem.ncbi.nlm.nih.gov/rest/pug/compound/inchi/description/json?inchi="
141
+ + quote(inchi, safe="")
142
+ )
143
+ with urlopen(url) as ans:
144
+ ans = loads(ans.read().decode("utf8").strip())
145
+ except (TypeError, HTTPError, ValueError):
146
+ ans = "no data available on this molecule in PubChem."
147
+ return ans
148
+
149
+
150
+ def mixture_density(
151
+ parameters: List[List[float]],
152
+ state: List[float],
153
+ kij_matrix: List[List[float]],
154
+ ) -> float:
155
+ """Calculates mixture liquid density (mol/m³) with ePC-SAFT.
156
+
157
+ Args:
158
+ parameters: A list of
159
+ `[m, sigma, epsilon/kB, kappa_ab, epsilon_ab/kB, dipole moment, na, nb, MW]`
160
+ for each component of the mixture
161
+ state: A list with the state of the mixture
162
+ `[Temperature (K), Pressure (Pa), mole_fractions_1, mole_fractions_2, ...]`
163
+ kij_matrix: A matrix of binary interaction parameters
164
+ """
165
+
166
+ return mix_den_feos(parameters, state, kij_matrix)
167
+
168
+
169
+ def mixture_vapor_pressure(
170
+ parameters: List[List[float]],
171
+ state: List[float],
172
+ kij_matrix: List[List[float]],
173
+ ) -> Tuple[float, float]:
174
+ """Calculates mixture `(Bubble point (Pa), Dew point (Pa))` with ePC-SAFT.
175
+
176
+ Args:
177
+ parameters: A list of
178
+ `[m, sigma, epsilon/kB, kappa_ab, epsilon_ab/kB, dipole moment, na, nb, MW]`
179
+ for each component of the mixture
180
+ state: A list with the state of the mixture
181
+ `[Temperature (K), Pressure (Pa), mole_fractions_1, molefractions_2, ...]`.
182
+ The pressure should be any `float` value since it's not used in the calculation.
183
+ kij_matrix: A matrix of binary interaction parameters.
184
+ """
185
+
186
+ return mix_vp_feos(parameters, state, kij_matrix)
187
+
188
+
189
+ def batch_predict_epcsaft_parameters(
190
+ smiles: List[str],
191
+ ) -> List[List[float]]:
192
+ """Predict ePC-SAFT parameters
193
+ `[m, sigma, epsilon/kB, kappa_ab, epsilon_ab/kB, dipole moment, na, nb]`
194
+ for a list of SMILES with the GNNePCSAFT model.
195
+
196
+ Args:
197
+ smiles (List[str]): SMILES of the molecules.
198
+ """
199
+ return [predict_epcsaft_parameters(smi) for smi in smiles]
200
+
201
+
202
+ def batch_molecular_weights(
203
+ smiles: List[str],
204
+ ) -> List[float]:
205
+ """Calcultes molecular weight in `g/mol` for a list of SMILES
206
+
207
+ Args:
208
+ smiles (List[str]): SMILES of the molecules.
209
+ """
210
+ inchi_list = [smilestoinchi(smi) for smi in smiles]
211
+ return [mw(inchi) for inchi in inchi_list]
212
+
213
+
214
+ def batch_inchi_to_smiles(
215
+ inchi_list: List[str],
216
+ ) -> List[str]:
217
+ """Transform a list of InChI to SMILES.
218
+
219
+ Args:
220
+ inchi_list (List[str]): List of InChI
221
+ """
222
+ return [inchitosmiles(inchi) for inchi in inchi_list]
223
+
224
+
225
+ def batch_smiles_to_inchi(
226
+ smiles_list: List[str],
227
+ ) -> List[str]:
228
+ """Transform a list of SMILES to InChI.
229
+
230
+ Args:
231
+ smiles_list (List[str]): List of SMILES
232
+ """
233
+ return [smilestoinchi(smi) for smi in smiles_list]
234
+
235
+
236
+ def batch_pure_density(
237
+ smiles_list: List[str],
238
+ state: List[float],
239
+ ) -> List[float]:
240
+ """Calculates pure liquid density in `kg/m³` with ePC-SAFT for a list of SMILES.
241
+ The state is the same for all molecules. The GNNePCSAFT model is used to predict
242
+ ePCSAFT parameters.
243
+
244
+ Args:
245
+ smiles_list (List[str]): List of SMILES
246
+ state: A list with
247
+ `[Temperature (K), Pressure (Pa)]`
248
+ """
249
+ return [
250
+ pure_den_feos(predict_epcsaft_parameters(smi), state) for smi in smiles_list
251
+ ]
252
+
253
+
254
+ def batch_pure_vapor_pressure(
255
+ smiles_list: List[str],
256
+ temperature: float,
257
+ ) -> List[float]:
258
+ """Calculates pure vapor pressure in `Pa` with ePC-SAFT for a list of SMILES.
259
+ The temperature is the same for all molecules. The GNNePCSAFT model is used to predict
260
+ ePCSAFT parameters.
261
+
262
+ Args:
263
+ smiles_list (List[str]): List of SMILES
264
+ temperature: `Temperature (K)`
265
+ """
266
+ return [
267
+ pure_vp_feos(predict_epcsaft_parameters(smi), [temperature])
268
+ for smi in smiles_list
269
+ ]
270
+
271
+
272
+ def batch_pure_h_lv(
273
+ smiles_list: List[str],
274
+ temperature: float,
275
+ ) -> List[float]:
276
+ """Calculates pure liquid enthalpy of vaporization in `kJ/mol`
277
+ with ePC-SAFT for a list of SMILES.
278
+ The temperature is the same for all molecules.
279
+ The GNNePCSAFT model is used to predict
280
+ ePCSAFT parameters.
281
+
282
+ Args:
283
+ smiles_list (List[str]): List of SMILES
284
+ temperature: `Temperature (K)`
285
+ """
286
+ return [
287
+ pure_h_lv_feos(predict_epcsaft_parameters(smi), [temperature])
288
+ for smi in smiles_list
289
+ ]
290
+
291
+
292
+ def batch_critical_points(
293
+ smiles_list: List[str],
294
+ ) -> List[List[float]]:
295
+ """
296
+ Calculates critical points `[Temperature (K), Pressure (Pa), Density (mol/m³)]` with ePC-SAFT
297
+ for a list of SMILES. The GNNePCSAFT model is used to predict ePCSAFT parameters.
298
+
299
+ Args:
300
+ smiles_list (List[str]): List of SMILES
301
+ """
302
+ return [
303
+ critical_points_feos(predict_epcsaft_parameters(smi)) for smi in smiles_list
304
+ ]
305
+
306
+
307
+ def batch_pa_to_bar(
308
+ pressure_in_pa_list: List[float],
309
+ ) -> List[float]:
310
+ """Convert a list of pressure from `Pa` to `bar`.
311
+
312
+ Args:
313
+ pressure_in_pa_list (List[float]): List of pressure in `Pa`
314
+ """
315
+ return [pa / 100_000.0 for pa in pressure_in_pa_list]
316
+
317
+
318
+ def batch_convert_pure_density_to_kg_per_m3(
319
+ density_list: List[float],
320
+ molecular_weight_list: List[float],
321
+ ) -> List[float]:
322
+ """Convert a list of density from `mol/m³` to `kg/m³`
323
+
324
+ Args:
325
+ density_list (List[float]): List of density in `mol/m³`
326
+ molecular_weight_list (List[float]): List of molecular weight in `g/mol`
327
+ """
328
+ return [den * molw / 1000 for den, molw in zip(density_list, molecular_weight_list)]
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "gnnepcsaft-mcp-server"
3
- version = "0.1.1"
3
+ version = "0.1.2"
4
4
  description = "Model Context Protocol server for GNNePCSAFT tools"
5
5
  authors = [
6
6
  {name = "wildsonbbl",email = "wil_bbl@hotmail.com"}
@@ -26,6 +26,9 @@ include = ["gnnepcsaft_mcp_server/models/assoc_8.onnx", "gnnepcsaft_mcp_server/m
26
26
 
27
27
 
28
28
 
29
+ [tool.poetry.group.dev.dependencies]
30
+ pytest = "^8.3.5"
31
+
29
32
  [build-system]
30
33
  requires = ["poetry-core>=2.0.0,<3.0.0"]
31
34
  build-backend = "poetry.core.masonry.api"
@@ -1,40 +0,0 @@
1
- "GNNePCSAFT MCP Server"
2
-
3
- from typing import Any, Callable, List
4
-
5
- from gnnepcsaft.data.rdkit_util import inchitosmiles, mw, smilestoinchi
6
- from gnnepcsaft.epcsaft.epcsaft_feos import (
7
- critical_points_feos,
8
- mix_den_feos,
9
- mix_vp_feos,
10
- pure_den_feos,
11
- pure_h_lv_feos,
12
- pure_vp_feos,
13
- )
14
- from mcp.server.fastmcp import FastMCP
15
-
16
- from .utils import mixture_phase, prediction, pubchem_description, pure_phase
17
-
18
- mcp = FastMCP("gnnepcsaft")
19
- fn_list: List[Callable[..., Any]] = [
20
- pure_vp_feos,
21
- pure_den_feos,
22
- mix_den_feos,
23
- mix_vp_feos,
24
- pure_phase,
25
- mixture_phase,
26
- pubchem_description,
27
- mw,
28
- smilestoinchi,
29
- prediction,
30
- inchitosmiles,
31
- pure_h_lv_feos,
32
- critical_points_feos,
33
- ]
34
- for fn in fn_list:
35
- mcp.add_tool(fn)
36
-
37
-
38
- def run():
39
- "run stdio"
40
- mcp.run("stdio")
@@ -1,136 +0,0 @@
1
- "Utils for MCP Server"
2
-
3
- from json import loads
4
- from pathlib import Path
5
- from typing import List, Literal
6
- from urllib.parse import quote
7
- from urllib.request import HTTPError, urlopen
8
-
9
- import numpy as np
10
- import onnxruntime as ort
11
- from gnnepcsaft.data.ogb_utils import smiles2graph
12
- from gnnepcsaft.data.rdkit_util import assoc_number, smilestoinchi
13
-
14
- file_dir = Path(__file__).parent
15
- model_dir = file_dir / "models"
16
- ort.set_default_logger_severity(3)
17
-
18
- msigmae_onnx = ort.InferenceSession(model_dir / "msigmae_7.onnx")
19
- assoc_onnx = ort.InferenceSession(model_dir / "assoc_8.onnx")
20
-
21
-
22
- def prediction(
23
- smiles: str,
24
- ) -> List[float]:
25
- """Predict ePC-SAFT parameters
26
- `[m, sigma, epsilon/kB, kappa_ab, epsilon_ab/kB, dipole moment, na, nb]`
27
-
28
- Args:
29
- smiles (str): SMILES of the molecule.
30
- """
31
- lower_bounds = np.asarray([1.0, 1.9, 50.0, 0.0, 0.0, 0, 0, 0])
32
- upper_bounds = np.asarray([25.0, 4.5, 550.0, 0.9, 5000.0, np.inf, np.inf, np.inf])
33
-
34
- inchi = smilestoinchi(smiles)
35
-
36
- graph = smiles2graph(smiles)
37
- na, nb = assoc_number(inchi)
38
- x, edge_index, edge_attr = (
39
- graph["node_feat"],
40
- graph["edge_index"],
41
- graph["edge_feat"],
42
- )
43
-
44
- assoc = 10 ** (
45
- assoc_onnx.run(
46
- None,
47
- {
48
- "x": x,
49
- "edge_index": edge_index,
50
- "edge_attr": edge_attr,
51
- },
52
- )[0][0]
53
- * np.asarray([-1.0, 1.0])
54
- )
55
- if na == 0 and nb == 0:
56
- assoc *= 0
57
- msigmae = msigmae_onnx.run(
58
- None,
59
- {
60
- "x": x,
61
- "edge_index": edge_index,
62
- "edge_attr": edge_attr,
63
- },
64
- )[0][0]
65
- munanb = np.asarray([0.0, na, nb])
66
- pred = np.hstack([msigmae, assoc, munanb], dtype=np.float64)
67
- np.clip(pred, lower_bounds, upper_bounds, out=pred)
68
-
69
- return pred.tolist() # type: ignore
70
-
71
-
72
- def pure_phase(
73
- vapor_pressure: float, system_pressure: float
74
- ) -> Literal["liquid", "vapor"]:
75
- """
76
- Given the vapor pressure and system pressure, return the phase of the molecule.
77
- Both pressures must be in the same unit.
78
-
79
- Args:
80
- vapor_pressure (float): The calculated vapor pressure of the pure component.
81
- system_pressure (float): The actual system pressure.
82
-
83
- """
84
- assert isinstance(vapor_pressure, (int, float)), "vapor_pressure must be a number"
85
- assert isinstance(system_pressure, (int, float)), "system_pressure must be a number"
86
- assert vapor_pressure > 0, "vapor_pressure must be positive"
87
- assert system_pressure > 0, "system_pressure must be positive"
88
-
89
- return "liquid" if vapor_pressure < system_pressure else "vapor"
90
-
91
-
92
- def mixture_phase(
93
- bubble_point: float,
94
- dew_point: float,
95
- system_pressure: float,
96
- ) -> Literal["liquid", "vapor", "two-phase"]:
97
- """
98
- Given the bubble/dew point of the mixture and the system pressure,
99
- return the phase of the mixture.
100
- All pressures must be in the same unit.
101
-
102
- Args:
103
- bubble_point (float): The calculated bubble point of the mixture.
104
- dew_point (float): The calculated dew point of the mixture.
105
- system_pressure (float): The actual system pressure.
106
- """
107
- assert isinstance(bubble_point, (int, float)), "bubble_point must be a number"
108
- assert isinstance(dew_point, (int, float)), "dew_point must be a number"
109
- assert isinstance(system_pressure, (int, float)), "system_pressure must be a number"
110
- assert bubble_point > 0, "bubble_point must be positive"
111
- assert dew_point > 0, "dew_point must be positive"
112
- assert system_pressure > 0, "system_pressure must be positive"
113
- return (
114
- "liquid"
115
- if bubble_point < system_pressure
116
- else ("two-phase" if dew_point <= system_pressure else "vapor")
117
- )
118
-
119
-
120
- def pubchem_description(inchi: str) -> str:
121
- """
122
- Look for information on PubChem for the InChI.
123
-
124
- Args:
125
- inchi (str): The InChI of the molecule.
126
- """
127
- url = (
128
- "https://pubchem.ncbi.nlm.nih.gov/rest/pug/compound/inchi/description/json?inchi="
129
- + quote(inchi, safe="")
130
- )
131
- try:
132
- with urlopen(url) as ans:
133
- ans = loads(ans.read().decode("utf8").strip())
134
- except (TypeError, HTTPError, ValueError):
135
- ans = "no data available on this molecule in PubChem."
136
- return ans