Skip to main content
Glama

Physics MCP Server

by BlinkZer0
units_smart.py8.01 kB
""" Smart units evaluation with constant substitution and dimensional analysis """ import re import pint from typing import Dict, Any, Optional, Tuple from sympy import symbols, sympify, N from .constants import get_constant # Initialize Pint unit registry ureg = pint.UnitRegistry() # Physical constants with their symbols CONSTANTS_MAP = { 'c': 'c', 'h': 'h', 'hbar': 'hbar', 'e': 'e', 'k_B': 'k_B', 'N_A': 'N_A', 'G': 'G', 'g': 'g', 'epsilon_0': 'epsilon_0', 'mu_0': 'mu_0', 'sigma': 'sigma', 'alpha': 'alpha', 'a_0': 'a_0', 'm_e': 'm_e', 'm_p': 'm_p', 'R': 'R' } def parse_expression_with_units(expr: str) -> Tuple[str, Dict[str, Any]]: """ Parse expression like "2 m / 200 ms" into symbolic expression and units Returns: (symbolic_expr, units_dict) """ # Pattern to match number + unit combinations unit_pattern = r'(\d+(?:\.\d+)?(?:[eE][+-]?\d+)?)\s*([a-zA-Z_][a-zA-Z0-9_/*^]*)' matches = re.findall(unit_pattern, expr) if not matches: # No units found, return as-is return expr, {} # Replace unit expressions with variables symbolic_expr = expr units_dict = {} var_counter = 0 for value_str, unit_str in matches: var_name = f'_var_{var_counter}' var_counter += 1 # Replace in expression original = f'{value_str} {unit_str}' symbolic_expr = symbolic_expr.replace(original, var_name, 1) # Store value and unit try: quantity = ureg.Quantity(float(value_str), unit_str) units_dict[var_name] = { 'value': float(value_str), 'unit': unit_str, 'quantity': quantity } except Exception as e: raise ValueError(f"Invalid unit '{unit_str}': {e}") return symbolic_expr, units_dict def substitute_constants(expr: str, constants: Dict[str, bool]) -> Tuple[str, Dict[str, Any]]: """ Substitute physical constants in expression Args: expr: Mathematical expression constants: Dict of constant names to substitute Returns: (modified_expr, constants_used) """ constants_used = {} modified_expr = expr for const_name, should_substitute in constants.items(): if should_substitute and const_name in CONSTANTS_MAP: try: const_data = get_constant(CONSTANTS_MAP[const_name]) constants_used[const_name] = const_data # Replace constant symbol in expression # Use word boundaries to avoid partial matches pattern = r'\b' + re.escape(const_name) + r'\b' modified_expr = re.sub(pattern, str(const_data['value']), modified_expr) except Exception as e: print(f"Warning: Could not substitute constant {const_name}: {e}") return modified_expr, constants_used def evaluate_with_units(expr: str, constants: Optional[Dict[str, bool]] = None) -> Dict[str, Any]: """ Smart evaluation of expression with units and constants Args: expr: Expression like "2 m / 200 ms" or "c * 1 s" constants: Constants to substitute (e.g., {"c": True, "h": False}) Returns: Result with value, unit, and metadata """ try: # Step 1: Substitute constants if requested if constants: expr, constants_used = substitute_constants(expr, constants) else: constants_used = {} # Step 2: Parse units from expression symbolic_expr, units_dict = parse_expression_with_units(expr) # Step 3: If we have units, do dimensional analysis if units_dict: # Evaluate symbolically first sym_expr = sympify(symbolic_expr) # Substitute unit quantities substitutions = {} for var_name, unit_data in units_dict.items(): substitutions[symbols(var_name)] = unit_data['value'] # Get numerical result numerical_result = float(N(sym_expr.subs(substitutions))) # Calculate resulting units using Pint unit_expr = symbolic_expr for var_name, unit_data in units_dict.items(): unit_expr = unit_expr.replace(var_name, f"({unit_data['value']} * {unit_data['unit']})") try: # Evaluate with Pint for unit calculation result_quantity = ureg.parse_expression(unit_expr) result_unit = str(result_quantity.units) result_value = float(result_quantity.magnitude) # Check if our numerical calculation matches Pint's if abs(result_value - numerical_result) > 1e-10 * abs(result_value): print(f"Warning: Numerical mismatch between symbolic ({numerical_result}) and Pint ({result_value})") except Exception as e: # Fallback: use numerical result with unknown units result_value = numerical_result result_unit = "unknown" print(f"Warning: Could not determine units: {e}") else: # No units, just evaluate numerically try: sym_expr = sympify(expr) result_value = float(N(sym_expr)) result_unit = "dimensionless" except Exception as e: raise ValueError(f"Could not evaluate expression '{expr}': {e}") # Determine if result is exact is_exact = all( const_data.get('uncertainty', 0) == 0 for const_data in constants_used.values() ) return { 'result': result_value, 'value': result_value, 'unit': result_unit, 'exact': is_exact, 'source': 'smart_evaluation', 'constants_used': constants_used, 'original_expression': expr, 'units_parsed': units_dict } except Exception as e: raise ValueError(f"Smart evaluation failed: {e}") def round_trip_test(value: float, from_unit: str, to_unit: str, tolerance: float = 1e-9) -> Dict[str, Any]: """ Test round-trip unit conversion accuracy Args: value: Original value from_unit: Source unit to_unit: Target unit tolerance: Maximum relative error allowed Returns: Test results with accuracy metrics """ try: # Forward conversion original_qty = ureg.Quantity(value, from_unit) converted_qty = original_qty.to(to_unit) # Backward conversion back_converted_qty = converted_qty.to(from_unit) # Calculate errors absolute_error = abs(back_converted_qty.magnitude - value) relative_error = absolute_error / abs(value) if value != 0 else absolute_error # Test passes if relative error is within tolerance test_passed = relative_error < tolerance return { 'passed': test_passed, 'original_value': value, 'original_unit': from_unit, 'converted_value': converted_qty.magnitude, 'converted_unit': to_unit, 'back_converted_value': back_converted_qty.magnitude, 'absolute_error': absolute_error, 'relative_error': relative_error, 'tolerance': tolerance, 'precision_loss': not test_passed } except Exception as e: return { 'passed': False, 'error': str(e), 'original_value': value, 'original_unit': from_unit, 'target_unit': to_unit }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/BlinkZer0/Phys-MCP'

If you have feedback or need assistance with the MCP directory API, please join our Discord server