guohanghui commited on
Commit
8cd8c34
·
verified ·
1 Parent(s): 032b944

Update PySDM/mcp_output/mcp_plugin/mcp_service.py

Browse files
PySDM/mcp_output/mcp_plugin/mcp_service.py CHANGED
@@ -1,87 +1,400 @@
 
 
 
 
 
 
 
 
 
 
1
  from fastmcp import FastMCP
 
 
 
 
 
 
 
2
 
3
  # Create the FastMCP service application
4
  mcp = FastMCP("pysdm_service")
5
 
6
 
7
- @mcp.tool(name="list_attributes", description="List all available attributes in PySDM")
8
- def list_attributes() -> dict:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9
  """
10
- List all available attributes in PySDM.
 
 
 
 
11
 
12
  Returns:
13
- - dict: A dictionary with success status and list of available attributes.
14
  """
15
  try:
16
- from PySDM.attributes import __all__ as attributes
17
- return {
18
- "success": True,
19
- "attributes": attributes,
20
- "count": len(attributes)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
21
  }
 
22
  except Exception as e:
23
- return {"success": False, "error": str(e)}
24
 
25
 
26
- @mcp.tool(name="list_dynamics", description="List all available dynamics in PySDM")
27
- def list_dynamics() -> dict:
 
 
28
  """
29
- List all available dynamics in PySDM.
 
 
 
30
 
31
  Returns:
32
- - dict: A dictionary with success status and list of available dynamics.
33
  """
34
  try:
35
- from PySDM.dynamics import __all__ as dynamics
36
- return {
37
- "success": True,
38
- "dynamics": dynamics,
39
- "count": len(dynamics)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
40
  }
 
41
  except Exception as e:
42
- return {"success": False, "error": str(e)}
43
 
44
 
45
- @mcp.tool(name="list_physics", description="List all available physics modules in PySDM")
46
- def list_physics() -> dict:
47
  """
48
- List all available physics modules in PySDM.
 
 
 
49
 
50
  Returns:
51
- - dict: A dictionary with success status and list of available physics modules.
52
  """
53
  try:
54
- from PySDM.physics import __all__ as physics_modules
55
- return {
56
- "success": True,
57
- "physics_modules": physics_modules,
58
- "count": len(physics_modules)
 
 
 
 
 
 
 
59
  }
 
60
  except Exception as e:
61
- return {"success": False, "error": str(e)}
 
62
 
 
63
 
64
- @mcp.tool(name="simulate", description="Run a PySDM simulation")
65
- def simulate(config: dict) -> dict:
66
  """
67
- Run a PySDM simulation based on the provided configuration.
68
 
69
  Parameters:
70
- - config: A dictionary containing simulation parameters.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
71
 
72
  Returns:
73
- - dict: Simulation results or status.
74
  """
75
  try:
76
- from PySDM import Simulation
77
- simulation = Simulation(config)
78
- results = simulation.run()
79
- return {
80
- "success": True,
81
- "results": results
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
82
  }
 
83
  except Exception as e:
84
- return {"success": False, "error": str(e)}
85
 
86
 
87
  def create_app() -> FastMCP:
 
1
+ import os
2
+ import sys
3
+ from typing import List, Optional, Dict, Any
4
+ import math
5
+
6
+ # Add the local source directory to sys.path
7
+ source_path = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))), "source")
8
+ if source_path not in sys.path:
9
+ sys.path.insert(0, source_path)
10
+
11
  from fastmcp import FastMCP
12
+ import numpy as np
13
+
14
+ # Import PySDM modules
15
+ from PySDM.physics import constants as const
16
+ from PySDM.physics import si
17
+ from PySDM.physics.trivia import Trivia
18
+ from PySDM.formulae import Formulae
19
 
20
  # Create the FastMCP service application
21
  mcp = FastMCP("pysdm_service")
22
 
23
 
24
+ # ===================== Physical Constants =====================
25
+
26
+ @mcp.tool(name="get_physical_constants", description="Retrieve physical constants")
27
+ def get_physical_constants() -> dict:
28
+ """
29
+ Retrieve physical constants used in PySDM simulations.
30
+
31
+ Returns:
32
+ - dict: Dictionary containing physical constants with their values and units.
33
+ """
34
+ try:
35
+ result = {
36
+ "fundamental_constants": {
37
+ "R_str": {"value": float(const.sci.R), "unit": "J/(K·mol)", "description": "Universal gas constant"},
38
+ "N_A": {"value": float(const.sci.N_A), "unit": "1/mol", "description": "Avogadro constant"},
39
+ "g_std": {"value": float(const.sci.g), "unit": "m/s²", "description": "Standard gravity"},
40
+ "PI": {"value": float(const.PI), "unit": "dimensionless", "description": "Pi"},
41
+ },
42
+ "thermodynamic_constants": {
43
+ "T0": {"value": float(const.T0 / si.kelvin), "unit": "K", "description": "Zero Celsius in Kelvin"},
44
+ "sqrt_two": {"value": float(const.sqrt_two), "unit": "dimensionless", "description": "Square root of 2"},
45
+ "sqrt_pi": {"value": float(const.sqrt_pi), "unit": "dimensionless", "description": "Square root of pi"},
46
+ },
47
+ "numerical_constants": {
48
+ "ONE_THIRD": {"value": float(const.ONE_THIRD), "unit": "dimensionless", "description": "1/3"},
49
+ "TWO_THIRDS": {"value": float(const.TWO_THIRDS), "unit": "dimensionless", "description": "2/3"},
50
+ "PI_4_3": {"value": float(const.PI_4_3), "unit": "dimensionless", "description": "4π/3"},
51
+ },
52
+ "concentration_units": {
53
+ "PPM": {"value": float(const.PPM), "unit": "dimensionless", "description": "Parts per million"},
54
+ "PPB": {"value": float(const.PPB), "unit": "dimensionless", "description": "Parts per billion"},
55
+ "PER_CENT": {"value": float(const.PER_CENT), "unit": "dimensionless", "description": "Percent"},
56
+ "PER_MILLE": {"value": float(const.PER_MILLE), "unit": "dimensionless", "description": "Per mille"},
57
+ }
58
+ }
59
+ return {"success": True, "result": result, "error": None}
60
+ except Exception as e:
61
+ return {"success": False, "result": None, "error": str(e)}
62
+
63
+
64
+ # ===================== Formulae Tools =====================
65
+
66
+ @mcp.tool(name="calculate_saturation_vapour_pressure", description="Calculate saturation vapour pressure over water")
67
+ def calculate_saturation_vapour_pressure(temperature_kelvin: float, method: str = "FlatauWalkoCotton") -> dict:
68
+ """
69
+ Calculate saturation vapour pressure over liquid water using PySDM Formulae.
70
+
71
+ Parameters:
72
+ - temperature_kelvin (float): Temperature in Kelvin.
73
+ - method (str): Method to use - 'FlatauWalkoCotton', 'AugustRocheMagnus', 'MurphyKoop2005', etc.
74
+
75
+ Returns:
76
+ - dict: Saturation vapour pressure in Pascals and hectopascals.
77
+ """
78
+ try:
79
+ formulae = Formulae(saturation_vapour_pressure=method)
80
+ T = temperature_kelvin * si.kelvin
81
+ pvs = formulae.saturation_vapour_pressure.pvs_water(formulae.constants, T)
82
+ pvs_value = float(pvs / si.pascal)
83
+
84
+ result = {
85
+ "temperature_K": temperature_kelvin,
86
+ "temperature_C": temperature_kelvin - 273.15,
87
+ "saturation_vapour_pressure_Pa": pvs_value,
88
+ "saturation_vapour_pressure_hPa": pvs_value / 100,
89
+ "method": method
90
+ }
91
+ return {"success": True, "result": result, "error": None}
92
+ except Exception as e:
93
+ return {"success": False, "result": None, "error": str(e)}
94
+
95
+
96
+ @mcp.tool(name="calculate_condensation", description="Calculate condensation rates")
97
+ def calculate_condensation(temperature: float, pressure: float, relative_humidity: float = 1.0) -> dict:
98
+ """
99
+ Calculate condensation-related parameters using PySDM.
100
+
101
+ Parameters:
102
+ - temperature (float): Temperature in Kelvin.
103
+ - pressure (float): Pressure in Pascals.
104
+ - relative_humidity (float): Relative humidity (0-1 or as fraction >1 for supersaturation).
105
+
106
+ Returns:
107
+ - dict: Condensation parameters including supersaturation and vapour pressure.
108
+ """
109
+ try:
110
+ formulae = Formulae()
111
+ T = temperature * si.kelvin
112
+ pvs = formulae.saturation_vapour_pressure.pvs_water(formulae.constants, T)
113
+ pvs_value = float(pvs / si.pascal)
114
+
115
+ # Actual vapour pressure
116
+ pv = relative_humidity * pvs_value
117
+
118
+ # Supersaturation
119
+ supersaturation = relative_humidity - 1.0
120
+
121
+ # Water vapour mixing ratio (eps = Mv/Md ≈ 0.622)
122
+ eps = 0.622
123
+ mixing_ratio = eps * pv / (pressure - pv) if pressure > pv else float('nan')
124
+
125
+ # Specific humidity
126
+ specific_humidity = mixing_ratio / (1 + mixing_ratio) if not np.isnan(mixing_ratio) else float('nan')
127
+
128
+ result = {
129
+ "temperature_K": temperature,
130
+ "pressure_Pa": pressure,
131
+ "saturation_vapour_pressure_Pa": pvs_value,
132
+ "actual_vapour_pressure_Pa": pv,
133
+ "relative_humidity": relative_humidity,
134
+ "supersaturation": supersaturation,
135
+ "supersaturation_percent": supersaturation * 100,
136
+ "water_vapour_mixing_ratio": mixing_ratio,
137
+ "specific_humidity": specific_humidity
138
+ }
139
+ return {"success": True, "result": result, "error": None}
140
+ except Exception as e:
141
+ return {"success": False, "result": None, "error": str(e)}
142
+
143
+
144
+ # ===================== Particle Dynamics =====================
145
+
146
+ @mcp.tool(name="simulate_particles", description="Simulate particle dynamics using PySDM")
147
+ def simulate_particles(particle_count: int, time_step: float) -> dict:
148
  """
149
+ Get information about particle simulation parameters in PySDM.
150
+
151
+ Parameters:
152
+ - particle_count (int): Number of super-droplets to simulate.
153
+ - time_step (float): Time step for the simulation in seconds.
154
 
155
  Returns:
156
+ - dict: Simulation configuration and recommendations.
157
  """
158
  try:
159
+ from PySDM.dynamics.condensation import DEFAULTS as COND_DEFAULTS
160
+
161
+ defaults = {
162
+ "rtol_x": COND_DEFAULTS.rtol_x,
163
+ "rtol_thd": COND_DEFAULTS.rtol_thd,
164
+ "dt_cond_range": (float(COND_DEFAULTS.cond_range[0] / si.second), float(COND_DEFAULTS.cond_range[1] / si.second)),
165
+ "schedule": COND_DEFAULTS.schedule,
166
+ }
167
+
168
+ result = {
169
+ "configuration": {
170
+ "n_sd": particle_count,
171
+ "dt": time_step,
172
+ "dt_unit": "seconds"
173
+ },
174
+ "solver_defaults": defaults,
175
+ "available_dynamics": [
176
+ "Condensation",
177
+ "Collision/Coalescence",
178
+ "Displacement",
179
+ "Freezing",
180
+ "AqueousChemistry",
181
+ "IsotopicFractionation",
182
+ "VapourDepositionOnIce"
183
+ ],
184
+ "recommendations": {
185
+ "adaptive_timestep": "Recommended for condensation",
186
+ "suggested_n_sd": "100-10000 for typical cloud simulations"
187
+ }
188
  }
189
+ return {"success": True, "result": result, "error": None}
190
  except Exception as e:
191
+ return {"success": False, "result": None, "error": str(e)}
192
 
193
 
194
+ # ===================== Trivia/Utility Functions =====================
195
+
196
+ @mcp.tool(name="calculate_droplet_volume", description="Calculate droplet volume from radius")
197
+ def calculate_droplet_volume(radius_um: float) -> dict:
198
  """
199
+ Calculate droplet volume and related properties using PySDM Trivia functions.
200
+
201
+ Parameters:
202
+ - radius_um (float): Droplet radius in micrometers.
203
 
204
  Returns:
205
+ - dict: Volume, surface area, and mass of the droplet.
206
  """
207
  try:
208
+ formulae = Formulae()
209
+ radius_m = radius_um * 1e-6
210
+
211
+ volume = Trivia.volume(formulae.constants, radius_m)
212
+ surface_area = Trivia.area(formulae.constants, radius_m)
213
+
214
+ # Assuming water density ~1000 kg/m³
215
+ rho_w = 1000.0
216
+ mass = rho_w * float(volume)
217
+
218
+ result = {
219
+ "radius_um": radius_um,
220
+ "radius_m": radius_m,
221
+ "volume_m3": float(volume),
222
+ "volume_um3": float(volume) * 1e18,
223
+ "surface_area_m2": float(surface_area),
224
+ "surface_area_um2": float(surface_area) * 1e12,
225
+ "mass_kg": mass,
226
+ "mass_ng": mass * 1e12
227
  }
228
+ return {"success": True, "result": result, "error": None}
229
  except Exception as e:
230
+ return {"success": False, "result": None, "error": str(e)}
231
 
232
 
233
+ @mcp.tool(name="calculate_radius_from_volume", description="Calculate droplet radius from volume")
234
+ def calculate_radius_from_volume(volume_um3: float) -> dict:
235
  """
236
+ Calculate droplet radius from volume using PySDM Trivia functions.
237
+
238
+ Parameters:
239
+ - volume_um3 (float): Droplet volume in cubic micrometers.
240
 
241
  Returns:
242
+ - dict: Radius in various units.
243
  """
244
  try:
245
+ formulae = Formulae()
246
+ volume_m3 = volume_um3 * 1e-18
247
+
248
+ radius_m = Trivia.radius(formulae.constants, volume_m3)
249
+ radius_um = float(radius_m) * 1e6
250
+
251
+ result = {
252
+ "volume_um3": volume_um3,
253
+ "volume_m3": volume_m3,
254
+ "radius_m": float(radius_m),
255
+ "radius_um": radius_um,
256
+ "diameter_um": 2 * radius_um
257
  }
258
+ return {"success": True, "result": result, "error": None}
259
  except Exception as e:
260
+ return {"success": False, "result": None, "error": str(e)}
261
+
262
 
263
+ # ===================== Kappa-Köhler Hygroscopicity =====================
264
 
265
+ @mcp.tool(name="calculate_kappa_koehler", description="Calculate critical supersaturation using kappa-Köhler theory")
266
+ def calculate_kappa_koehler(dry_radius_um: float, kappa: float, temperature_kelvin: float = 293.15) -> dict:
267
  """
268
+ Calculate critical supersaturation and radius using kappa-Köhler theory.
269
 
270
  Parameters:
271
+ - dry_radius_um (float): Dry aerosol radius in micrometers.
272
+ - kappa (float): Hygroscopicity parameter (kappa).
273
+ - temperature_kelvin (float): Temperature in Kelvin (default 293.15 K = 20°C).
274
+
275
+ Returns:
276
+ - dict: Critical supersaturation and activation radius.
277
+ """
278
+ try:
279
+ formulae = Formulae(hygroscopicity="KappaKoehlerLeadingTerms")
280
+
281
+ # Physical constants
282
+ sigma = 0.072 # Surface tension of water (N/m)
283
+ Mv = 18.015e-3 # kg/mol
284
+ rho_w = 1000.0 # kg/m³
285
+ R = const.sci.R
286
+ T = temperature_kelvin
287
+
288
+ dry_radius_m = dry_radius_um * 1e-6
289
+
290
+ # Kelvin parameter A
291
+ A = 2 * sigma * Mv / (rho_w * R * T)
292
+
293
+ # Critical supersaturation (approximation from leading terms)
294
+ S_c = math.sqrt(4 * A**3 / (27 * kappa * dry_radius_m**3))
295
+
296
+ # Critical radius
297
+ r_c = math.sqrt(3 * kappa * dry_radius_m**3 / A)
298
+
299
+ result = {
300
+ "dry_radius_um": dry_radius_um,
301
+ "kappa": kappa,
302
+ "temperature_K": temperature_kelvin,
303
+ "kelvin_parameter_A": A,
304
+ "critical_supersaturation": S_c,
305
+ "critical_supersaturation_percent": S_c * 100,
306
+ "critical_radius_um": r_c * 1e6,
307
+ "activation_diameter_um": 2 * r_c * 1e6
308
+ }
309
+ return {"success": True, "result": result, "error": None}
310
+ except Exception as e:
311
+ return {"success": False, "result": None, "error": str(e)}
312
+
313
+
314
+ # ===================== Isotope Tools =====================
315
+
316
+ @mcp.tool(name="get_isotope_constants", description="Get water isotope constants")
317
+ def get_isotope_constants() -> dict:
318
+ """
319
+ Get water isotope constants (VSMOW standard) from PySDM.
320
+
321
+ Returns:
322
+ - dict: Isotope abundance ratios and atomic masses.
323
+ """
324
+ try:
325
+ from PySDM.physics import constants_defaults as cd
326
+
327
+ result = {
328
+ "VSMOW_ratios": {
329
+ "R_2H": {"value": float(cd.VSMOW_R_2H), "description": "Deuterium abundance ratio"},
330
+ "R_3H": {"value": float(cd.VSMOW_R_3H), "description": "Tritium abundance ratio"},
331
+ "R_18O": {"value": float(cd.VSMOW_R_18O), "description": "Oxygen-18 abundance ratio"},
332
+ "R_17O": {"value": float(cd.VSMOW_R_17O), "description": "Oxygen-17 abundance ratio"},
333
+ },
334
+ "atomic_masses_kg_per_mol": {
335
+ "M_1H": float(cd.M_1H / (si.g / si.mole)),
336
+ "M_2H": float(cd.M_2H / (si.g / si.mole)),
337
+ "M_16O": float(cd.M_16O / (si.g / si.mole)),
338
+ "M_18O": float(cd.M_18O / (si.g / si.mole)),
339
+ },
340
+ "description": "VSMOW (Vienna Standard Mean Ocean Water) is the international standard for water isotope ratios"
341
+ }
342
+ return {"success": True, "result": result, "error": None}
343
+ except Exception as e:
344
+ return {"success": False, "result": None, "error": str(e)}
345
+
346
+
347
+ # ===================== Available Formulae =====================
348
+
349
+ @mcp.tool(name="list_available_formulae", description="List available physics formulae in PySDM")
350
+ def list_available_formulae() -> dict:
351
+ """
352
+ List available physics formulae options in PySDM.
353
 
354
  Returns:
355
+ - dict: Categories of formulae with available options.
356
  """
357
  try:
358
+ result = {
359
+ "saturation_vapour_pressure": [
360
+ "FlatauWalkoCotton",
361
+ "AugustRocheMagnus",
362
+ "Lowe1977",
363
+ "MurphyKoop2005",
364
+ "Wexler1976",
365
+ "Bolton1980"
366
+ ],
367
+ "hygroscopicity": [
368
+ "KappaKoehler",
369
+ "KappaKoehlerLeadingTerms"
370
+ ],
371
+ "latent_heat_vapourisation": [
372
+ "Kirchhoff",
373
+ "Constant"
374
+ ],
375
+ "drop_growth": [
376
+ "Mason1971",
377
+ "FuchsSutugin"
378
+ ],
379
+ "surface_tension": [
380
+ "Constant",
381
+ "CompressedFilm"
382
+ ],
383
+ "terminal_velocity": [
384
+ "GunnKinzer1949",
385
+ "PowerSeries",
386
+ "RogersYau"
387
+ ],
388
+ "freezing_temperature_spectrum": [
389
+ "Null",
390
+ "Bigg1953",
391
+ "Niemand_et_al_2012"
392
+ ],
393
+ "description": "These are configurable physics options in PySDM.Formulae"
394
  }
395
+ return {"success": True, "result": result, "error": None}
396
  except Exception as e:
397
+ return {"success": False, "result": None, "error": str(e)}
398
 
399
 
400
  def create_app() -> FastMCP: