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

Update PySDM/mcp_output/mcp_plugin/mcp_service.py

Browse files
PySDM/mcp_output/mcp_plugin/mcp_service.py CHANGED
@@ -1,400 +1,87 @@
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:
 
 
 
 
 
 
 
 
 
 
 
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: