Skip to main content
Glama
power_flow.py9.36 kB
""" Power flow analysis module for OpenDSS. This module provides functions for running power flow analysis on OpenDSS circuits, with optional harmonic analysis capabilities. """ import logging from typing import Any import opendssdirect as dss from ..utils.formatters import format_success_response, format_error_response from ..utils.harmonics import get_harmonic_voltages, get_harmonic_currents # Map of solution mode names to their corresponding integer values in OpenDSS SOLUTION_MODES = { "snapshot": 0, "snap": 0, "daily": 1, "yearly": 2, "dutycycle": 3, "direct": 4, "montecarlo1": 5, "montecarlo2": 6, "montecarlo3": 7, "faultstudy": 8, "mf": 9, "peakday": 10, "loadduration1": 11, "loadduration2": 12, } logger = logging.getLogger(__name__) def _perform_harmonic_analysis( all_buses: list[str], harmonic_orders: list[int] ) -> dict[str, Any]: """Perform harmonic analysis on all buses and lines in the circuit. This internal helper function calculates THD for voltages at all buses and currents through all lines, and identifies the worst THD location. Args: all_buses: List of all bus names in the circuit harmonic_orders: List of harmonic orders to analyze Returns: Dictionary containing: - thd_voltage: Dictionary mapping bus ID to THD percentage - thd_current: Dictionary mapping line ID to THD percentage - individual_harmonics: Dictionary of harmonic orders with bus voltages - worst_thd_bus: Bus ID with highest voltage THD - worst_thd_value: THD percentage at worst bus """ thd_voltage: dict[str, float] = {} thd_current: dict[str, float] = {} individual_harmonics: dict[int, dict[str, float]] = {} # Initialize individual harmonics structure for order in harmonic_orders: individual_harmonics[order] = {} # Calculate THD for all buses logger.info(f"Calculating voltage THD for {len(all_buses)} buses...") for bus_name in all_buses: try: result = get_harmonic_voltages(bus_name, harmonic_orders) if result.get("success", False): # Store THD value thd_voltage[bus_name] = result.get("thd_percent", 0.0) # Store individual harmonic voltages harmonic_voltages = result.get("harmonic_voltages", {}) for order, data in harmonic_voltages.items(): avg_voltage = data.get("avg_voltage_pu", 0.0) individual_harmonics[order][bus_name] = avg_voltage except Exception as e: logger.warning(f"Error analyzing harmonics for bus {bus_name}: {e}") continue # Calculate THD for all lines all_lines = dss.Lines.AllNames() logger.info(f"Calculating current THD for {len(all_lines)} lines...") for line_name in all_lines: try: result = get_harmonic_currents(line_name, harmonic_orders) if result.get("success", False): thd_current[line_name] = result.get("thd_percent", 0.0) except Exception as e: logger.warning(f"Error analyzing harmonics for line {line_name}: {e}") continue # Find worst THD bus worst_thd_bus = "" worst_thd_value = 0.0 if thd_voltage: worst_thd_bus = max(thd_voltage, key=thd_voltage.get) worst_thd_value = thd_voltage[worst_thd_bus] logger.info( f"Harmonic analysis complete. Worst THD: {worst_thd_value:.2f}% at bus {worst_thd_bus}" ) return { "thd_voltage": thd_voltage, "thd_current": thd_current, "individual_harmonics": individual_harmonics, "worst_thd_bus": worst_thd_bus, "worst_thd_value": round(worst_thd_value, 4), } def run_power_flow( feeder_id: str, options: dict[str, Any] | None = None ) -> dict[str, Any]: """ Run power flow analysis on a loaded feeder with optional harmonic analysis. Args: feeder_id: Identifier of the IEEE test feeder (e.g., 'IEEE13') options: Dictionary of power flow options - max_iterations: Maximum number of iterations (default: 100) - tolerance: Convergence tolerance (default: 0.0001) - control_mode: Control mode for the solution (default: 'snapshot') - harmonic_analysis: Enable harmonic analysis (default: False) - harmonic_orders: List of harmonic orders to analyze (default: [1, 3, 5, 7, 9, 11, 13]) Returns: Dictionary containing power flow results and metadata: - success: Boolean indicating if operation was successful - data: Dictionary with power flow results: - feeder_id: The feeder identifier - converged: Boolean indicating convergence - iterations: Number of iterations performed - bus_voltages: Dictionary of bus voltages in per-unit - min_voltage: Minimum voltage across all buses - max_voltage: Maximum voltage across all buses - options: The options used for the analysis - harmonics: (Optional) Harmonic analysis results if enabled: - thd_voltage: Dictionary mapping bus ID to THD percentage - thd_current: Dictionary mapping line ID to THD percentage - individual_harmonics: Dictionary of harmonic orders with bus voltages - worst_thd_bus: Bus ID with highest voltage THD - worst_thd_value: THD percentage at worst bus - metadata: Additional metadata - errors: List of error messages if any occurred Example: >>> # Basic power flow >>> result = run_power_flow("IEEE13") >>> >>> # Power flow with harmonic analysis >>> result = run_power_flow("IEEE13", { ... "harmonic_analysis": True, ... "harmonic_orders": [1, 3, 5, 7, 9, 11, 13] ... }) >>> if result['success']: ... harmonics = result['data']['harmonics'] ... print(f"Worst THD: {harmonics['worst_thd_value']:.2f}% at bus {harmonics['worst_thd_bus']}") Note: - Harmonic analysis is optional and disabled by default for backward compatibility - Harmonic analysis requires harmonic sources to be defined in the circuit - Harmonic analysis may significantly increase computation time """ try: # Set default options if not provided options = options or {} max_iterations = options.get("max_iterations", 100) tolerance = options.get("tolerance", 0.0001) control_mode = options.get("control_mode", "snapshot") harmonic_analysis = options.get("harmonic_analysis", False) harmonic_orders = options.get("harmonic_orders", [1, 3, 5, 7, 9, 11, 13]) # Configure power flow settings dss.Solution.MaxControlIterations(max_iterations) dss.Solution.MaxIterations(max_iterations) # Set solution mode (convert string to integer) mode_value = SOLUTION_MODES.get(control_mode.lower(), 0) dss.Solution.Mode(mode_value) # Note: Using default tolerance as it's not configurable in this API # Solve the power flow dss.Solution.Solve() # Check if the solution converged converged = dss.Solution.Converged() iterations = dss.Solution.Iterations() if not converged: return format_error_response("Power flow did not converge") # Get bus voltages bus_voltages = {} all_buses = dss.Circuit.AllBusNames() for bus_name in all_buses: dss.Circuit.SetActiveBus(bus_name) voltages = dss.Bus.puVmagAngle() # Take the magnitude of the first phase voltage (simplified) if voltages and len(voltages) > 0: bus_voltages[bus_name.lower()] = voltages[0] # Calculate min/max voltages if bus_voltages: min_voltage = min(bus_voltages.values()) max_voltage = max(bus_voltages.values()) else: min_voltage = max_voltage = 0.0 # Prepare base results result = { "feeder_id": feeder_id, "converged": converged, "iterations": iterations, "bus_voltages": bus_voltages, "min_voltage": min_voltage, "max_voltage": max_voltage, "options": { "max_iterations": max_iterations, "tolerance": tolerance, "control_mode": control_mode, }, } # Perform harmonic analysis if requested if harmonic_analysis: logger.info("Running harmonic analysis...") harmonics_data = _perform_harmonic_analysis(all_buses, harmonic_orders) result["harmonics"] = harmonics_data result["options"]["harmonic_analysis"] = True result["options"]["harmonic_orders"] = harmonic_orders return format_success_response(result) except Exception as e: error_msg = f"Error running power flow: {str(e)}" logger.exception(error_msg) return format_error_response(error_msg)

Latest Blog Posts

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/ahmedelshazly27/opendss-mcp-server1'

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