def __init__(self, settings: SimulationSettingsModel): self._dssInstance = dss self._TempResultList = [] self._dssBuses = {} self._dssObjects = {} self._dssObjectsByClass = {} self._DelFlag = 0 self._pyPlotObjects = {} self.BokehSessionID = None self._settings = settings self._convergenceErrors = 0 self._convergenceErrorsOpenDSS = 0 self._maxConvergenceErrorCount = None self._maxConvergenceError = 0.0 self._controller_iteration_counts = {} self._simulation_range = SimulationFilteredTimeRange.from_settings( settings) root_path = settings.project.project_path active_project_path = root_path / settings.project.active_project import_path = active_project_path / 'Scenarios' active_scenario_path = import_path / settings.project.active_scenario self._ActiveProject = settings.project.active_project self._dssPath = { 'root': root_path, 'Import': import_path, 'pyPlots': active_scenario_path / 'pyPlotList', 'ExportLists': active_scenario_path / 'ExportLists', 'pyControllers': active_scenario_path / 'pyControllerList', 'Export': active_project_path / 'Exports', 'Log': active_project_path / 'Logs', 'dssFiles': active_project_path / 'DSSfiles', 'dssFilePath': active_project_path / 'DSSfiles' / settings.project.dss_file, } if settings.project.dss_file_absolute_path: self._dssPath['dssFilePath'] = Path(settings.project.dss_file) if not self._dssPath['dssFilePath'].exists(): raise InvalidConfiguration( f"DSS file {self._dssPath['dssFilePath']} does not exist") LoggerTag = pyLogger.getLoggerTag(settings) self._Logger = logging.getLogger(__name__) self._reportsLogger = pyLogger.getReportLogger(LoggerTag, self._dssPath["Log"], settings.logging) self._Logger.info('An instance of OpenDSS version ' + self._dssInstance.__version__ + ' has been created.') for key, path in self._dssPath.items(): if path.name == "pyControllerList" and not path.exists(): # This will happen if a zipped project with no controllers is unzipped and then run. path.mkdir() else: assert path.exists(), '{} path: {} does not exist!'.format( key, path) self._CompileModel() #run_command('Set DefaultBaseFrequency={}'.format(settings.frequency.fundamental_frequency)) self._Logger.info('OpenDSS fundamental frequency set to : ' + str(settings.frequency.fundamental_frequency) + ' Hz') #run_command('Set %SeriesRL={}'.format(settings.frequency.percentage_load_in_series)) if settings.frequency.neglect_shunt_admittance: run_command('Set NeglectLoadY=Yes') active_scenario = self._GetActiveScenario() if active_scenario.snapshot_time_point_selection_config.mode != SnapshotTimePointSelectionMode.NONE: self._SetSnapshotTimePoint(active_scenario) self._dssCircuit = self._dssInstance.Circuit self._dssElement = self._dssInstance.Element self._dssBus = self._dssInstance.Bus self._dssClass = self._dssInstance.ActiveClass self._dssCommand = run_command self._dssSolution = self._dssInstance.Solution self._dssSolver = SolveMode.GetSolver(settings=settings, dssInstance=self._dssInstance) self._Modifier = Modifier(self._dssInstance, run_command, settings) self._dssBuses = self.CreateBusObjects() self._dssObjects, self._dssObjectsByClass = self.CreateDssObjects( self._dssBuses) self._dssSolver.reSolve() if settings.profiles.use_profile_manager: #TODO: disable internal profiles self._Logger.info( 'Disabling internal yearly and duty-cycle profiles.') for m in ["Loads", "PVSystem", "Generator", "Storage"]: run_command(f'BatchEdit {m}..* yearly=NONE duty=None') profileSettings = self._settings.profiles.settings profileSettings["objects"] = self._dssObjects self.profileStore = ProfileInterface.Create( self._dssInstance, self._dssSolver, self._settings, self._Logger, **profileSettings) self.ResultContainer = ResultData(settings, self._dssPath, self._dssObjects, self._dssObjectsByClass, self._dssBuses, self._dssSolver, self._dssCommand, self._dssInstance) if settings.project.use_controller_registry: ControllerList = read_controller_settings_from_registry( self._dssPath['pyControllers']) else: pyCtrlReader = pcr(self._dssPath['pyControllers']) ControllerList = pyCtrlReader.pyControllers if ControllerList is not None: self._CreateControllers(ControllerList) if settings.plots.create_dynamic_plots: pyPlotReader = ppr(self._dssPath['pyPlots']) PlotList = pyPlotReader.pyPlots self._CreatePlots(PlotList) for Plot in self._pyPlotObjects: self.BokehSessionID = self._pyPlotObjects[Plot].GetSessionID() if settings.plots.open_plots_in_browser: self._pyPlotObjects[Plot].session.show() break self._increment_flag = True if settings.helics.co_simulation_mode: self._HI = HI.helics_interface(self._dssSolver, self._dssObjects, self._dssObjectsByClass, settings, self._dssPath) self._Logger.info("Simulation initialization complete") return
def __init__(self, params): self._TempResultList = [] self._dssInstance = dss self._dssBuses = {} self._dssObjects = {} self._dssObjectsByClass = {} self._DelFlag = 0 self._pyPlotObjects = {} self.BokehSessionID = None self._Options = params rootPath = params['Project']['Project Path'] self._ActiveProject = params['Project']['Active Project'] importPath = os.path.join(rootPath, params['Project']['Active Project'], 'Scenarios') self._dssPath = { 'root': rootPath, 'Import': importPath, 'pyPlots': os.path.join(importPath, params['Project']['Active Scenario'], 'pyPlotList'), 'ExportLists': os.path.join(importPath, params['Project']['Active Scenario'], 'ExportLists'), 'pyControllers': os.path.join(importPath, params['Project']['Active Scenario'], 'pyControllerList'), 'Export': os.path.join(rootPath, params['Project']['Active Project'], 'Exports'), 'Log': os.path.join(rootPath, params['Project']['Active Project'], 'Logs'), 'dssFiles': os.path.join(rootPath, params['Project']['Active Project'], 'DSSfiles'), 'dssFilePath': os.path.join(rootPath, params['Project']['Active Project'], 'DSSfiles', params['Project']['DSS File']), } if params['Project']['DSS File Absolute Path']: self._dssPath['dssFilePath'] = params['Project']['DSS File'] else: self._dssPath['dssFilePath'] = os.path.join( rootPath, params['Project']['Active Project'], 'DSSfiles', params['Project']['DSS File']) if params["Logging"]["Pre-configured logging"]: self._Logger = logging.getLogger(__name__) else: LoggerTag = pyLogger.getLoggerTag(params) self._Logger = pyLogger.getLogger(LoggerTag, self._dssPath['Log'], LoggerOptions=params["Logging"]) self._Logger.info('An instance of OpenDSS version ' + dss.__version__ + ' has been created.') for key, path in self._dssPath.items(): assert ( os.path.exists(path)), '{} path: {} does not exist!'.format( key, path) self._dssInstance.Basic.ClearAll() self._dssInstance.utils.run_command('Log=NO') run_command('Clear') self._Logger.info('Loading OpenDSS model') try: orig_dir = os.getcwd() reply = run_command('compile ' + self._dssPath['dssFilePath']) finally: os.chdir(orig_dir) self._Logger.info('OpenDSS: ' + reply) assert ('error ' not in reply.lower() ), 'Error compiling OpenDSS model.\n{}'.format(reply) run_command('Set DefaultBaseFrequency={}'.format( params['Frequency']['Fundamental frequency'])) self._Logger.info('OpenDSS fundamental frequency set to : ' + str(params['Frequency']['Fundamental frequency']) + ' Hz') run_command('Set %SeriesRL={}'.format( params['Frequency']['Percentage load in series'])) if params['Frequency']['Neglect shunt admittance']: run_command('Set NeglectLoadY=Yes') self._dssCircuit = dss.Circuit self._dssElement = dss.Element self._dssBus = dss.Bus self._dssClass = dss.ActiveClass self._dssCommand = run_command self._dssSolution = dss.Solution self._dssSolver = SolveMode.GetSolver(SimulationSettings=params, dssInstance=self._dssInstance) self._Modifier = Modifier(dss, run_command, params) self._UpdateDictionary() self._CreateBusObjects() self._dssSolver.reSolve() if params and params['Exports']['Log Results']: if params['Exports']['Result Container'] == 'ResultContainer': self.ResultContainer = RC(params, self._dssPath, self._dssObjects, self._dssObjectsByClass, self._dssBuses, self._dssSolver, self._dssCommand) else: self.ResultContainer = ResultData( params, self._dssPath, self._dssObjects, self._dssObjectsByClass, self._dssBuses, self._dssSolver, self._dssCommand, self._dssInstance) else: self.ResultContainer = None if params['Project']['Use Controller Registry']: ControllerList = read_controller_settings_from_registry( self._dssPath['pyControllers']) else: pyCtrlReader = pcr(self._dssPath['pyControllers']) ControllerList = pyCtrlReader.pyControllers if ControllerList is not None: self._CreateControllers(ControllerList) if params['Plots']['Create dynamic plots']: pyPlotReader = ppr(self._dssPath['pyPlots']) PlotList = pyPlotReader.pyPlots self._CreatePlots(PlotList) for Plot in self._pyPlotObjects: self.BokehSessionID = self._pyPlotObjects[Plot].GetSessionID() if params['Plots']['Open plots in browser']: self._pyPlotObjects[Plot].session.show() break self._increment_flag = True if params['Helics']["Co-simulation Mode"]: self._HI = HI.helics_interface(self._dssSolver, self._dssObjects, self._dssObjectsByClass, params, self._dssPath) return
class OpenDSS: def __init__(self, settings: SimulationSettingsModel): self._dssInstance = dss self._TempResultList = [] self._dssBuses = {} self._dssObjects = {} self._dssObjectsByClass = {} self._DelFlag = 0 self._pyPlotObjects = {} self.BokehSessionID = None self._settings = settings self._convergenceErrors = 0 self._convergenceErrorsOpenDSS = 0 self._maxConvergenceErrorCount = None self._maxConvergenceError = 0.0 self._controller_iteration_counts = {} self._simulation_range = SimulationFilteredTimeRange.from_settings( settings) root_path = settings.project.project_path active_project_path = root_path / settings.project.active_project import_path = active_project_path / 'Scenarios' active_scenario_path = import_path / settings.project.active_scenario self._ActiveProject = settings.project.active_project self._dssPath = { 'root': root_path, 'Import': import_path, 'pyPlots': active_scenario_path / 'pyPlotList', 'ExportLists': active_scenario_path / 'ExportLists', 'pyControllers': active_scenario_path / 'pyControllerList', 'Export': active_project_path / 'Exports', 'Log': active_project_path / 'Logs', 'dssFiles': active_project_path / 'DSSfiles', 'dssFilePath': active_project_path / 'DSSfiles' / settings.project.dss_file, } if settings.project.dss_file_absolute_path: self._dssPath['dssFilePath'] = Path(settings.project.dss_file) if not self._dssPath['dssFilePath'].exists(): raise InvalidConfiguration( f"DSS file {self._dssPath['dssFilePath']} does not exist") LoggerTag = pyLogger.getLoggerTag(settings) self._Logger = logging.getLogger(__name__) self._reportsLogger = pyLogger.getReportLogger(LoggerTag, self._dssPath["Log"], settings.logging) self._Logger.info('An instance of OpenDSS version ' + self._dssInstance.__version__ + ' has been created.') for key, path in self._dssPath.items(): if path.name == "pyControllerList" and not path.exists(): # This will happen if a zipped project with no controllers is unzipped and then run. path.mkdir() else: assert path.exists(), '{} path: {} does not exist!'.format( key, path) self._CompileModel() #run_command('Set DefaultBaseFrequency={}'.format(settings.frequency.fundamental_frequency)) self._Logger.info('OpenDSS fundamental frequency set to : ' + str(settings.frequency.fundamental_frequency) + ' Hz') #run_command('Set %SeriesRL={}'.format(settings.frequency.percentage_load_in_series)) if settings.frequency.neglect_shunt_admittance: run_command('Set NeglectLoadY=Yes') active_scenario = self._GetActiveScenario() if active_scenario.snapshot_time_point_selection_config.mode != SnapshotTimePointSelectionMode.NONE: self._SetSnapshotTimePoint(active_scenario) self._dssCircuit = self._dssInstance.Circuit self._dssElement = self._dssInstance.Element self._dssBus = self._dssInstance.Bus self._dssClass = self._dssInstance.ActiveClass self._dssCommand = run_command self._dssSolution = self._dssInstance.Solution self._dssSolver = SolveMode.GetSolver(settings=settings, dssInstance=self._dssInstance) self._Modifier = Modifier(self._dssInstance, run_command, settings) self._dssBuses = self.CreateBusObjects() self._dssObjects, self._dssObjectsByClass = self.CreateDssObjects( self._dssBuses) self._dssSolver.reSolve() if settings.profiles.use_profile_manager: #TODO: disable internal profiles self._Logger.info( 'Disabling internal yearly and duty-cycle profiles.') for m in ["Loads", "PVSystem", "Generator", "Storage"]: run_command(f'BatchEdit {m}..* yearly=NONE duty=None') profileSettings = self._settings.profiles.settings profileSettings["objects"] = self._dssObjects self.profileStore = ProfileInterface.Create( self._dssInstance, self._dssSolver, self._settings, self._Logger, **profileSettings) self.ResultContainer = ResultData(settings, self._dssPath, self._dssObjects, self._dssObjectsByClass, self._dssBuses, self._dssSolver, self._dssCommand, self._dssInstance) if settings.project.use_controller_registry: ControllerList = read_controller_settings_from_registry( self._dssPath['pyControllers']) else: pyCtrlReader = pcr(self._dssPath['pyControllers']) ControllerList = pyCtrlReader.pyControllers if ControllerList is not None: self._CreateControllers(ControllerList) if settings.plots.create_dynamic_plots: pyPlotReader = ppr(self._dssPath['pyPlots']) PlotList = pyPlotReader.pyPlots self._CreatePlots(PlotList) for Plot in self._pyPlotObjects: self.BokehSessionID = self._pyPlotObjects[Plot].GetSessionID() if settings.plots.open_plots_in_browser: self._pyPlotObjects[Plot].session.show() break self._increment_flag = True if settings.helics.co_simulation_mode: self._HI = HI.helics_interface(self._dssSolver, self._dssObjects, self._dssObjectsByClass, settings, self._dssPath) self._Logger.info("Simulation initialization complete") return @track_timing(timer_stats_collector) def _CompileModel(self): self._dssInstance.Basic.ClearAll() self._dssInstance.utils.run_command('Log=NO') run_command('Clear') self._Logger.info('Loading OpenDSS model') reply = "" try: orig_dir = os.getcwd() reply = run_command('compile ' + str(self._dssPath['dssFilePath'])) finally: os.chdir(orig_dir) self._Logger.info('OpenDSS: ' + reply) if reply != "": raise OpenDssModelError(f"Error compiling OpenDSS model: {reply}") def _ReadControllerDefinitions(self): controllers = None mappings = os.path.join( os.path.dirname(self._dssPath['pyControllers']), "ControllerMappings") if os.path.exists(mappings): ctrl_mapping_files = os.listdir( self._dssPath["ControllerMappings"]) for filename in ctrl_mapping_files: data = load_data( os.path.join(self._dssPath["ControllerMappings"], filename)) return controllers def _ModifyNetwork(self): # self._Modifier.Add_Elements('Storage', {'bus' : ['storagebus'], 'kWRated' : ['2000'], 'kWhRated' : ['2000']}, # True, self._dssObjects) # self._Modifier.Edit_Elements('regcontrol', 'enabled' ,'False') #self._Modifier.Edit_Elements('Load', 'enabled', 'False') return def _CreateControllers(self, ControllerDict): self._pyControls = {} self._pyControls_types = {} for ControllerType, ElementsDict in ControllerDict.items(): for ElmName, SettingsDict in ElementsDict.items(): Controller = pyControllers.pyController.Create( ElmName, ControllerType, SettingsDict, self._dssObjects, self._dssInstance, self._dssSolver) if Controller != -1: controller_name = 'Controller.' + ElmName self._pyControls[controller_name] = Controller class_name, element_name = Controller.ControlledElement( ).split(".") if controller_name not in self._pyControls_types: self._pyControls_types[controller_name] = class_name self._Logger.info('Created pyController -> Controller.' + ElmName) return def _CreatePlots(self, PlotsDict): self.BokehDoc = curdoc() Figures = [] for PlotType, PlotNames in PlotsDict.items(): newPlotNames = list(PlotNames) PlotType1 = ['Topology', 'GISplot', 'NetworkGraph'] PlotType2 = ['SagPlot', 'Histogram'] PlotType3 = ['XY', 'TimeSeries', 'FrequencySweep'] for Name in newPlotNames: PlotSettings = PlotNames[Name] PlotSettings['FileName'] = Name if PlotType in PlotType1: self._pyPlotObjects[PlotType] = pyPlots.pyPlots.Create( PlotType, PlotSettings, self._dssBuses, self._dssObjectsByClass, self._dssCircuit, self._dssSolver) Figures.append(self._pyPlotObjects[PlotType].GetFigure()) #self.BokehDoc.add_root(self._pyPlotObjects[PlotType].GetFigure()) self._Logger.info('Created pyPlot -> ' + PlotType) elif PlotType in PlotType2: self._pyPlotObjects[PlotType + Name] = pyPlots.pyPlots.Create( PlotType, PlotSettings, self._dssBuses, self._dssObjectsByClass, self._dssCircuit, self._dssSolver) self._Logger.info('Created pyPlot -> ' + PlotType) elif PlotType in PlotType3: self._pyPlotObjects[PlotType + Name] = pyPlots.pyPlots.Create( PlotType, PlotSettings, self._dssBuses, self._dssObjects, self._dssCircuit, self._dssSolver) self._Logger.info('Created pyPlot -> ' + PlotType) Layout = row(*Figures) self.BokehDoc.add_root(Layout) self.BokehDoc.title = "PyDSS" self.session = push_session(self.BokehDoc) self.session.show() return def _UpdateControllers(self, Priority, Time, Iteration, UpdateResults): errors = [] maxError = 0 _pyControls_types = set(self._pyControls_types.values()) for class_name in _pyControls_types: self._dssInstance.Basic.SetActiveClass(class_name) elm = self._dssInstance.ActiveClass.First() while elm: element_name = self._dssInstance.CktElement.Name() controller_name = 'Controller.' + element_name if controller_name in self._pyControls: controller = self._pyControls[controller_name] error = controller.Update(Priority, Time, UpdateResults) maxError = error if error > maxError else maxError if Iteration == self._settings.project.max_control_iterations - 1: if error > self._settings.project.error_tolerance: errorTag = { "Report": "Convergence", "Scenario": self._settings.project.active_scenario, "Time": self._dssSolver.GetTotalSeconds(), "DateTime": str(self._dssSolver.GetDateTime()), "Controller": controller.Name(), "Controlled element": controller.ControlledElement(), "Error": error, "Control algorithm": controller.debugInfo()[Priority], } json_object = json.dumps(errorTag) self._reportsLogger.warning(json_object) elm = self._dssInstance.ActiveClass.Next() return maxError < self._settings.project.error_tolerance, maxError @staticmethod def CreateBusObjects(): dssBuses = {} BusNames = dss.Circuit.AllBusNames() dss.run_command('New Fault.DEFAULT Bus1={} enabled=no r=0.01'.format( BusNames[0])) for BusName in BusNames: dss.Circuit.SetActiveBus(BusName) dssBuses[BusName] = dssBus() return dssBuses @staticmethod def CreateDssObjects(dssBuses): dssObjects = {} dssObjectsByClass = defaultdict(dict) InvalidSelection = [ 'Settings', 'ActiveClass', 'dss', 'utils', 'PDElements', 'XYCurves', 'Bus', 'Properties' ] # TODO: this causes a segmentation fault. Aadil says it may not be needed. #self._dssObjectsByClass={'LoadShape': self._GetRelaventObjectDict('LoadShape')} for ElmName in dss.Circuit.AllElementNames(): Class, Name = ElmName.split('.', 1) ClassName = Class + 's' dss.Circuit.SetActiveElement(ElmName) dssObjectsByClass[ClassName][ElmName] = create_dss_element( Class, Name) dssObjects[ElmName] = dssObjectsByClass[ClassName][ElmName] for ObjName in dssObjects.keys(): ClassName = ObjName.split('.')[0] + 's' if ObjName not in dssObjectsByClass[Class]: dssObjectsByClass[Class][ObjName] = dssObjects[ObjName] dssObjects['Circuit.' + dss.Circuit.Name()] = dssCircuit() dssObjectsByClass['Circuits'] = { 'Circuit.' + dss.Circuit.Name(): dssObjects['Circuit.' + dss.Circuit.Name()] } dssObjectsByClass['Buses'] = dssBuses return dssObjects, dssObjectsByClass def _GetRelaventObjectDict(self, key): ObjectList = {} ElmCollection = getattr(self._dssInstance, key) Elem = ElmCollection.First() while Elem: FullName = self._dssInstance.Element.Name() Class, Name = FullName.split('.', 1) ObjectList[FullName] = create_dss_element(Class, Name, self._dssInstance) Elem = ElmCollection.Next() return ObjectList @track_timing(timer_stats_collector) def RunStep(self, step, updateObjects=None): # updating parameters before simulation run if self._settings.logging.log_time_step_updates: self._Logger.info( f'PyDSS datetime - {self._dssSolver.GetDateTime()}') self._Logger.info( f'OpenDSS time [h] - {self._dssSolver.GetOpenDSSTime()}') if self._settings.profiles.use_profile_manager: self.profileStore.update() if self._settings.helics.co_simulation_mode: self._HI.updateHelicsSubscriptions() else: if updateObjects: for object, params in updateObjects.items(): cl, name = object.split('.') self._Modifier.Edit_Element(cl, name, params) # run simulation time step and get results time_step_has_converged = True if not self._settings.project.disable_pydss_controllers: with Timer(timer_stats_collector, "UpdateControllers"): for priority in range(CONTROLLER_PRIORITIES): priority_has_converged = False for i in range( self._settings.project.max_control_iterations): has_converged, error = self._UpdateControllers( priority, step, i, UpdateResults=False) self._Logger.debug( 'Control Loop {} convergence error: {}'.format( priority, error)) if has_converged: priority_has_converged = True break self._dssSolver.reSolve() if i == 0: # Don't track 0. pass elif i not in self._controller_iteration_counts: self._controller_iteration_counts[i] = 1 else: self._controller_iteration_counts[i] += 1 if not priority_has_converged: time_step_has_converged = False self._Logger.warning( 'Control Loop {} no convergence @ {} '.format( priority, step)) self._HandleConvergenceErrorChecks(step, error) self._UpdatePlots() if self._settings.frequency.enable_frequency_sweep and \ self._settings.project.simulation_type != SimulationType.DYNAMIC: self._dssSolver.setMode('Harmonic') for frequency in np.arange( self._settings.frequency.start_frequency, self._settings.frequency.end_frequency + 1, self._settings.frequency.frequency_increment): self._dssSolver.setFrequency( frequency * self._settings.frequency.fundamental_frequency) self._dssSolver.reSolve() self._UpdatePlots() if self._settings.exports.export_results: self.ResultContainer.UpdateResults() if self._settings.project.simulation_type != SimulationType.SNAPSHOT: self._dssSolver.setMode('Snapshot') else: self._dssSolver.setMode('Yearly') if self._settings.helics.co_simulation_mode: self._HI.updateHelicsPublications() self._increment_flag, helics_time = self._HI.request_time_increment( ) return time_step_has_converged def _HandleConvergenceErrorChecks(self, step, error): self._convergenceErrors += 1 if self._maxConvergenceError != 0.0 and error > self._maxConvergenceError: self._Logger.error( "Convergence error %s exceeded max value %s at step %s", error, self._maxConvergenceError, step) raise PyDssConvergenceMaxError( f"Exceeded max convergence error {error}") if self._maxConvergenceErrorCount is not None and self._convergenceErrors > self._maxConvergenceErrorCount: self._Logger.error( "Exceeded convergence error count threshold at step %s", step) raise PyDssConvergenceErrorCountExceeded( f"{self._convergenceErrors} errors occurred") def _HandleOpenDSSConvergenceErrorChecks(self, step): self._convergenceErrorsOpenDSS += 1 if self._maxConvergenceErrorCount is not None and self._convergenceErrorsOpenDSS > self._maxConvergenceErrorCount: self._Logger.error( "Exceeded OpenDSS convergence error count threshold at step %s", step) raise OpenDssConvergenceErrorCountExceeded( f"{self._convergenceErrorsOpenDSS} errors occurred") def DryRunSimulation(self, project, scenario): """Run one time point for getting estimated space.""" if not self._settings.exports.export_results: raise InvalidConfiguration("Log Reults must set to be True.") Steps, _, _ = self._dssSolver.SimulationSteps() self._Logger.info('Dry run simulation...') self.ResultContainer.InitializeDataStore(project.hdf_store, Steps) try: self.RunStep(0) self.ResultContainer.UpdateResults() finally: self.ResultContainer.Close() return self.ResultContainer.max_num_bytes() def initStore(self, hdf_store, Steps, MC_scenario_number=None): self.ResultContainer.InitializeDataStore(hdf_store, Steps, MC_scenario_number) def RunSimulation(self, project, scenario, MC_scenario_number=None): """Yields a tuple of the results of each step. Yields ------ tuple is_complete, step, has_converged, results """ startTime = time.time() Steps, sTime, eTime = self._dssSolver.SimulationSteps() threshold = self._settings.project.convergence_error_percent_threshold if threshold > 0: self._maxConvergenceErrorCount = round(threshold * .01 * Steps) self._maxConvergenceError = self._settings.project.max_error_tolerance dss.Solution.Convergence(self._settings.project.error_tolerance) self._Logger.info('Running simulation from {} till {}.'.format( sTime, eTime)) self._Logger.info('Simulation time step {}.'.format(Steps)) self._Logger.info("Set OpenDSS convergence to %s", dss.Solution.Convergence()) self._Logger.info('Max convergence error count {}.'.format( self._maxConvergenceErrorCount)) self._Logger.info("initializing store") self.ResultContainer.InitializeDataStore(project.hdf_store, Steps, MC_scenario_number) postprocessors = [ pyPostprocess.Create( project, scenario, ppInfo, self._dssInstance, self._dssSolver, self._dssObjects, self._dssObjectsByClass, self._settings, self._Logger, ) for ppInfo in scenario.post_process_infos ] if not postprocessors: self._Logger.info('No post processing script selected') is_complete = False step = 0 has_converged = False current_results = {} try: while step < Steps: pydss_has_converged = True opendss_has_converged = True within_range = self._simulation_range.is_within_range( self._dssSolver.GetDateTime()) if within_range: pydss_has_converged = self.RunStep(step) opendss_has_converged = dss.Solution.Converged() if not opendss_has_converged: self._Logger.error( "OpenDSS did not converge at step=%s pydss_converged=%s", step, pydss_has_converged) self._HandleOpenDSSConvergenceErrorChecks(step) has_converged = pydss_has_converged and opendss_has_converged if step == 0 and self.ResultContainer is not None: size = make_human_readable_size( self.ResultContainer.max_num_bytes()) self._Logger.info( 'Storage requirement estimation: %s, estimated based on first time step run.', size) if postprocessors and within_range: step, has_converged = self._RunPostProcessors( step, Steps, postprocessors) if self._increment_flag: step += 1 # In the case of a frequency sweep, the code updates results at each frequency. # Doing so again would cause a duplicate result. if ( self._settings.exports.export_results and not (self._settings.frequency.enable_frequency_sweep and \ self._settings.project.simulation_type != SimulationType.DYNAMIC) ): store_nan = (not within_range or (not has_converged and self._settings. project.skip_export_on_convergence_error)) self.ResultContainer.UpdateResults(store_nan=store_nan) if self._settings.helics.co_simulation_mode: if self._increment_flag: self._dssSolver.IncStep() else: self._dssSolver.reSolve() else: self._dssSolver.IncStep() if self._settings.exports.export_results: current_results = self.ResultContainer.CurrentResults yield False, step, has_converged, current_results finally: if self._settings and self._settings.exports.export_results: # This is here to guarantee that DatasetBuffers aren't left # with any data in memory. self.ResultContainer.Close() for postprocessor in postprocessors: postprocessor.finalize() if self._settings and self._settings.exports.export_results: self.ResultContainer.ExportResults() timer_stats_collector.log_stats(clear=True) if self._controller_iteration_counts: data = { "Report": "ControllerIterationCounts", "Scenario": self._settings.project.active_scenario, "Counts": self._controller_iteration_counts, } self._reportsLogger.warning(json.dumps(data)) self._Logger.info('Simulation completed in %s seconds', time.time() - startTime) self._Logger.info('End of simulation') yield True, step, has_converged, current_results def _RunPostProcessors(self, step, Steps, postprocessors): for postprocessor in postprocessors: orig_step = step step, has_converged, error = postprocessor.run(step, Steps, simulation=self) assert step <= orig_step, "step cannot increment in postprocessor" if not has_converged: name = postprocessor.__class__.__name__ self._Logger.warn( "postprocessor %s reported a convergence error at step %s", name, step) self._HandleConvergenceErrorChecks(step, error) return step, has_converged def RunMCsimulation(self, project, scenario, samples): from PyDSS.Extensions.MonteCarlo import MonteCarloSim MC = MonteCarloSim(self._settings, self._dssPath, self._dssObjects, self._dssObjectsByClass) for i in range(samples): MC.Create_Scenario() for is_complete, _, _, _ in self.RunSimulation( project, scenario, i): if is_complete: break return def _UpdatePlots(self): for Plot in self._pyPlotObjects: self._pyPlotObjects[Plot].UpdatePlot() return def _GetActiveScenario(self): active_scenario = self._settings.project.active_scenario for scenario in self._settings.project.scenarios: if scenario.name == active_scenario: return scenario raise InvalidConfiguration( f"Active Scenario {active_scenario} is not present") @track_timing(timer_stats_collector) def _SetSnapshotTimePoint(self, scenario): """Adjusts the time parameters based on the mode.""" p_settings = self._settings.project config = scenario.snapshot_time_point_selection_config mode = config.mode assert mode != SnapshotTimePointSelectionMode.NONE, mode if mode != SnapshotTimePointSelectionMode.NONE: if p_settings.simulation_type != SimulationType.QSTS: raise InvalidConfiguration( f"{mode} is only supported with QSTS simulations") # These settings have to be temporarily overridden because of the underlying # implementation to create a load shape dataframes.. orig_start = p_settings.start_time orig_duration = p_settings.simulation_duration_min if orig_duration != p_settings.step_resolution_sec / 60: raise InvalidConfiguration( "Simulation duration must be the same as resolution") try: p_settings.start_time = config.start_time p_settings.simulation_duration_min = config.search_duration_min new_start = get_snapshot_timepoint(self._settings, mode).strftime(DATE_FORMAT) p_settings.start_time = new_start self._Logger.info( "Changed simulation start time from %s to %s", orig_start, new_start, ) except Exception: p_settings.start_time = orig_start raise finally: p_settings.simulation_duration_min = orig_duration else: assert False, f"unsupported mode {mode}"
class OpenDSS: def __init__(self, params): self._TempResultList = [] self._dssInstance = dss self._dssBuses = {} self._dssObjects = {} self._dssObjectsByClass = {} self._DelFlag = 0 self._pyPlotObjects = {} self.BokehSessionID = None self._Options = params rootPath = params['Project']['Project Path'] self._ActiveProject = params['Project']['Active Project'] importPath = os.path.join(rootPath, params['Project']['Active Project'], 'Scenarios') self._dssPath = { 'root': rootPath, 'Import': importPath, 'pyPlots': os.path.join(importPath, params['Project']['Active Scenario'], 'pyPlotList'), 'ExportLists': os.path.join(importPath, params['Project']['Active Scenario'], 'ExportLists'), 'pyControllers': os.path.join(importPath, params['Project']['Active Scenario'], 'pyControllerList'), 'Export': os.path.join(rootPath, params['Project']['Active Project'], 'Exports'), 'Log': os.path.join(rootPath, params['Project']['Active Project'], 'Logs'), 'dssFiles': os.path.join(rootPath, params['Project']['Active Project'], 'DSSfiles'), 'dssFilePath': os.path.join(rootPath, params['Project']['Active Project'], 'DSSfiles', params['Project']['DSS File']), } if params['Project']['DSS File Absolute Path']: self._dssPath['dssFilePath'] = params['Project']['DSS File'] else: self._dssPath['dssFilePath'] = os.path.join( rootPath, params['Project']['Active Project'], 'DSSfiles', params['Project']['DSS File']) if params["Logging"]["Pre-configured logging"]: self._Logger = logging.getLogger(__name__) else: LoggerTag = pyLogger.getLoggerTag(params) self._Logger = pyLogger.getLogger(LoggerTag, self._dssPath['Log'], LoggerOptions=params["Logging"]) self._Logger.info('An instance of OpenDSS version ' + dss.__version__ + ' has been created.') for key, path in self._dssPath.items(): assert ( os.path.exists(path)), '{} path: {} does not exist!'.format( key, path) self._dssInstance.Basic.ClearAll() self._dssInstance.utils.run_command('Log=NO') run_command('Clear') self._Logger.info('Loading OpenDSS model') try: orig_dir = os.getcwd() reply = run_command('compile ' + self._dssPath['dssFilePath']) finally: os.chdir(orig_dir) self._Logger.info('OpenDSS: ' + reply) assert ('error ' not in reply.lower() ), 'Error compiling OpenDSS model.\n{}'.format(reply) run_command('Set DefaultBaseFrequency={}'.format( params['Frequency']['Fundamental frequency'])) self._Logger.info('OpenDSS fundamental frequency set to : ' + str(params['Frequency']['Fundamental frequency']) + ' Hz') run_command('Set %SeriesRL={}'.format( params['Frequency']['Percentage load in series'])) if params['Frequency']['Neglect shunt admittance']: run_command('Set NeglectLoadY=Yes') self._dssCircuit = dss.Circuit self._dssElement = dss.Element self._dssBus = dss.Bus self._dssClass = dss.ActiveClass self._dssCommand = run_command self._dssSolution = dss.Solution self._dssSolver = SolveMode.GetSolver(SimulationSettings=params, dssInstance=self._dssInstance) self._Modifier = Modifier(dss, run_command, params) self._UpdateDictionary() self._CreateBusObjects() self._dssSolver.reSolve() if params and params['Exports']['Log Results']: if params['Exports']['Result Container'] == 'ResultContainer': self.ResultContainer = RC(params, self._dssPath, self._dssObjects, self._dssObjectsByClass, self._dssBuses, self._dssSolver, self._dssCommand) else: self.ResultContainer = ResultData( params, self._dssPath, self._dssObjects, self._dssObjectsByClass, self._dssBuses, self._dssSolver, self._dssCommand, self._dssInstance) else: self.ResultContainer = None if params['Project']['Use Controller Registry']: ControllerList = read_controller_settings_from_registry( self._dssPath['pyControllers']) else: pyCtrlReader = pcr(self._dssPath['pyControllers']) ControllerList = pyCtrlReader.pyControllers if ControllerList is not None: self._CreateControllers(ControllerList) if params['Plots']['Create dynamic plots']: pyPlotReader = ppr(self._dssPath['pyPlots']) PlotList = pyPlotReader.pyPlots self._CreatePlots(PlotList) for Plot in self._pyPlotObjects: self.BokehSessionID = self._pyPlotObjects[Plot].GetSessionID() if params['Plots']['Open plots in browser']: self._pyPlotObjects[Plot].session.show() break self._increment_flag = True if params['Helics']["Co-simulation Mode"]: self._HI = HI.helics_interface(self._dssSolver, self._dssObjects, self._dssObjectsByClass, params, self._dssPath) return def _ReadControllerDefinitions(self): controllers = None mappings = os.path.join( os.path.dirname(self._dssPath['pyControllers']), "ControllerMappings") if os.path.exists(mappings): ctrl_mapping_files = os.listdir( self._dssPath["ControllerMappings"]) for filename in ctrl_mapping_files: data = load_data( os.path.join(self._dssPath["ControllerMappings"], filename)) return controllers def _ModifyNetwork(self): # self._Modifier.Add_Elements('Storage', {'bus' : ['storagebus'], 'kWRated' : ['2000'], 'kWhRated' : ['2000']}, # True, self._dssObjects) # self._Modifier.Edit_Elements('regcontrol', 'enabled' ,'False') #self._Modifier.Edit_Elements('Load', 'enabled', 'False') return def _CreateControllers(self, ControllerDict): self._pyControls = {} for ControllerType, ElementsDict in ControllerDict.items(): for ElmName, SettingsDict in ElementsDict.items(): Controller = pyControllers.pyController.Create( ElmName, ControllerType, SettingsDict, self._dssObjects, self._dssInstance, self._dssSolver) if Controller != -1: self._pyControls['Controller.' + ElmName] = Controller self._Logger.info('Created pyController -> Controller.' + ElmName) return def _CreatePlots(self, PlotsDict): self.BokehDoc = curdoc() Figures = [] for PlotType, PlotNames in PlotsDict.items(): newPlotNames = list(PlotNames) PlotType1 = ['Topology', 'GISplot', 'NetworkGraph'] PlotType2 = ['SagPlot', 'Histogram'] PlotType3 = ['XY', 'TimeSeries', 'FrequencySweep'] for Name in newPlotNames: PlotSettings = PlotNames[Name] PlotSettings['FileName'] = Name if PlotType in PlotType1: self._pyPlotObjects[PlotType] = pyPlots.pyPlots.Create( PlotType, PlotSettings, self._dssBuses, self._dssObjectsByClass, self._dssCircuit, self._dssSolver) Figures.append(self._pyPlotObjects[PlotType].GetFigure()) #self.BokehDoc.add_root(self._pyPlotObjects[PlotType].GetFigure()) self._Logger.info('Created pyPlot -> ' + PlotType) elif PlotType in PlotType2: self._pyPlotObjects[PlotType + Name] = pyPlots.pyPlots.Create( PlotType, PlotSettings, self._dssBuses, self._dssObjectsByClass, self._dssCircuit, self._dssSolver) self._Logger.info('Created pyPlot -> ' + PlotType) elif PlotType in PlotType3: self._pyPlotObjects[PlotType + Name] = pyPlots.pyPlots.Create( PlotType, PlotSettings, self._dssBuses, self._dssObjects, self._dssCircuit, self._dssSolver) self._Logger.info('Created pyPlot -> ' + PlotType) Layout = row(*Figures) self.BokehDoc.add_root(Layout) self.BokehDoc.title = "PyDSS" self.session = push_session(self.BokehDoc) self.session.show() return def _UpdateControllers(self, Priority, Time, UpdateResults): error = 0 for controller in self._pyControls.values(): error += controller.Update(Priority, Time, UpdateResults) if Priority == 0: pass return abs(error) < self._Options['Project']['Error tolerance'], error def _CreateBusObjects(self): BusNames = self._dssCircuit.AllBusNames() self._dssInstance.run_command( 'New Fault.DEFAULT Bus1={} enabled=no r=0.01'.format(BusNames[0])) for BusName in BusNames: self._dssCircuit.SetActiveBus(BusName) self._dssBuses[BusName] = dssBus(self._dssInstance) return def _UpdateDictionary(self): InvalidSelection = [ 'Settings', 'ActiveClass', 'dss', 'utils', 'PDElements', 'XYCurves', 'Bus', 'Properties' ] # TODO: this causes a segmentation fault. Aadil says it may not be needed. #self._dssObjectsByClass={'LoadShape': self._GetRelaventObjectDict('LoadShape')} for ElmName in self._dssInstance.Circuit.AllElementNames(): Class, Name = ElmName.split('.', 1) if Class + 's' not in self._dssObjectsByClass: self._dssObjectsByClass[Class + 's'] = {} self._dssInstance.Circuit.SetActiveElement(ElmName) self._dssObjectsByClass[Class + 's'][ElmName] = create_dss_element( Class, Name, self._dssInstance) self._dssObjects[ElmName] = self._dssObjectsByClass[Class + 's'][ElmName] for ObjName in self._dssObjects.keys(): Class = ObjName.split('.')[0] + 's' if Class not in self._dssObjectsByClass: self._dssObjectsByClass[Class] = {} if ObjName not in self._dssObjectsByClass[Class]: self._dssObjectsByClass[Class][ObjName] = self._dssObjects[ ObjName] self._dssObjects['Circuit.' + self._dssCircuit.Name()] = dssCircuit( self._dssInstance) self._dssObjectsByClass['Circuits'] = { 'Circuit.' + self._dssCircuit.Name(): self._dssObjects['Circuit.' + self._dssCircuit.Name()] } return def _GetRelaventObjectDict(self, key): ObjectList = {} ElmCollection = getattr(self._dssInstance, key) Elem = ElmCollection.First() while Elem: FullName = self._dssInstance.Element.Name() Class, Name = FullName.split('.', 1) ObjectList[FullName] = create_dss_element(Class, Name, self._dssInstance) Elem = ElmCollection.Next() return ObjectList def RunStep(self, step, updateObjects=None): # updating paramters bebore simulation run if self._Options['Helics']['Co-simulation Mode']: if self._increment_flag: self._dssSolver.IncStep() else: self._dssSolver.reSolve() self._HI.updateHelicsSubscriptions() else: self._dssSolver.IncStep() if updateObjects: for object, params in updateObjects.items(): cl, name = object.split('.') self._Modifier.Edit_Element(cl, name, params) # run simulation time step and get results if not self._Options['Project']['Disable PyDSS controllers']: for priority in range(CONTROLLER_PRIORITIES): for i in range( self._Options['Project']['Max Control Iterations']): has_converged, error = self._UpdateControllers( priority, step, UpdateResults=False) self._Logger.debug( 'Control Loop {} convergence error: {}'.format( priority, error)) if has_converged or i == self._Options['Project'][ 'Max Control Iterations'] - 1: if not has_converged: self._Logger.warning( 'Control Loop {} no convergence @ {} '.format( priority, step)) break self._dssSolver.reSolve() self._UpdatePlots() if self._Options['Exports']['Log Results']: self.ResultContainer.UpdateResults() if self._Options['Frequency']['Enable frequency sweep'] and \ self._Options['Project']['Simulation Type'].lower() != 'dynamic': self._dssSolver.setMode('Harmonic') for freqency in np.arange( self._Options['Frequency']['Start frequency'], self._Options['Frequency']['End frequency'] + 1, self._Options['Frequency']['frequency increment']): self._dssSolver.setFrequency( freqency * self._Options['Frequency']['Fundamental frequency']) self._dssSolver.reSolve() self._UpdatePlots() if self._Options['Exports']['Log Results']: self.ResultContainer.UpdateResults() if self._Options['Project']['Simulation Type'].lower( ) == 'snapshot': self._dssSolver.setMode('Snapshot') else: self._dssSolver.setMode('Yearly') if self._Options['Helics']['Co-simulation Mode']: self._HI.updateHelicsPublications() self._increment_flag, helics_time = self._HI.request_time_increment( ) return self.ResultContainer.CurrentResults def DryRunSimulation(self, project, scenario): """Run one time point for getting estimated space.""" if not self._Options['Exports']['Log Results']: raise InvalidConfiguration("Log Reults must set to be True.") Steps, _, _ = self._dssSolver.SimulationSteps() self._Logger.info('Dry run simulation...') self.ResultContainer.InitializeDataStore(project.hdf_store, Steps) try: self.RunStep(0) finally: self.ResultContainer.FlushData() return self.ResultContainer.max_num_bytes() def RunSimulation(self, project, scenario, MC_scenario_number=None): startTime = time.time() Steps, sTime, eTime = self._dssSolver.SimulationSteps() self._Logger.info('Running simulation from {} till {}.'.format( sTime, eTime)) self._Logger.info('Simulation time step {}.'.format(Steps)) if self._Options['Exports'][ 'Result Container'] == 'ResultData' and self.ResultContainer is not None: self.ResultContainer.InitializeDataStore(project.hdf_store, Steps, MC_scenario_number) postprocessors = [ pyPostprocess.Create( project, scenario, ppInfo, self._dssInstance, self._dssSolver, self._dssObjects, self._dssObjectsByClass, self._Options, self._Logger, ) for ppInfo in scenario.post_process_infos ] if not postprocessors: self._Logger.info('No post processing script selected') try: step = 0 while step < Steps: self.RunStep(step) if step == 0 and self.ResultContainer is not None: size = make_human_readable_size( self.ResultContainer.max_num_bytes()) self._Logger.info( 'Storage requirement estimation: %s, estimated based on first time step run.', size) for postprocessor in postprocessors: step = postprocessor.run(step, Steps) if self._increment_flag: step += 1 finally: if self._Options and self._Options['Exports']['Log Results']: # This is here to guarantee that DatasetBuffers aren't left # with any data in memory. self.ResultContainer.FlushData() if self._Options and self._Options['Exports']['Log Results']: self.ResultContainer.ExportResults(fileprefix="", ) self._Logger.info('Simulation completed in ' + str(time.time() - startTime) + ' seconds') self._Logger.info('End of simulation') def RunMCsimulation(self, project, scenario, samples): from PyDSS.Extensions.MonteCarlo import MonteCarloSim MC = MonteCarloSim(self._Options, self._dssPath, self._dssObjects, self._dssObjectsByClass) for i in range(samples): MC.Create_Scenario() self.RunSimulation(project, scenario, i) return def _UpdatePlots(self): for Plot in self._pyPlotObjects: self._pyPlotObjects[Plot].UpdatePlot() return def __del__(self): self._Logger.info('An instance of OpenDSS (' + str(self) + ') has been deleted.') if self._Options["Logging"]["Log to external file"]: handlers = list(self._Logger.handlers) for filehandler in handlers: filehandler.flush() filehandler.close() self._Logger.removeHandler(filehandler) return