#2 Dynamic find_by Methods

Shorten simple finds considerably and improve readability by using the dynamic find_all_by and find_by methods.

# tasks_controller.rb
def incomplete
@tasks = Task.find_all_by_complete(false)
end

def last_incomplete
@task = Task.find_by_complete(false, :order => 'created_at DESC')
end
"""Support for dynamic COM client support. Introduction Dynamic COM client support is the ability to use a COM server without prior knowledge of the server. This can be used to talk to almost all COM servers, including much of MS Office. In general, you should not use this module directly - see below. Example >>> import win32com.client >>> xl = win32com.client.Dispatch("Excel.Application") # The line above invokes the functionality of this class. # xl is now an object we can use to talk to Excel. >>> xl.Visible = 1 # The Excel window becomes visible. """ import traceback from itertools import chain from types import MethodType import pythoncom # Needed as code we eval() references it. import win32com.client import winerror from pywintypes import IIDType from . import build debugging = 0 # General debugging debugging_attr = 0 # Debugging dynamic attribute lookups. LCID = 0x0 # These errors generally mean the property or method exists, # but can&#39;t be used in this context - eg, property instead of a method, etc. # Used to determine if we have a real error or not. ERRORS_BAD_CONTEXT = [ winerror.DISP_E_MEMBERNOTFOUND, winerror.DISP_E_BADPARAMCOUNT, winerror.DISP_E_PARAMNOTOPTIONAL, winerror.DISP_E_TYPEMISMATCH, winerror.E_INVALIDARG, ] ALL_INVOKE_TYPES = [ pythoncom.INVOKE_PROPERTYGET, pythoncom.INVOKE_PROPERTYPUT, pythoncom.INVOKE_PROPERTYPUTREF, pythoncom.INVOKE_FUNC, ] def debug_print(*args): if debugging: for arg in args: print(arg, end=" ") print() def debug_attr_print(*args): if debugging_attr: for arg in args: print(arg, end=" ") print() # get the type objects for IDispatch and IUnknown PyIDispatchType = pythoncom.TypeIIDs[pythoncom.IID_IDispatch] PyIUnknownType = pythoncom.TypeIIDs[pythoncom.IID_IUnknown] _GoodDispatchTypes = (str, IIDType) def _GetGoodDispatch(IDispatch, clsctx=pythoncom.CLSCTX_SERVER): # quick return for most common case if isinstance(IDispatch, PyIDispatchType): return IDispatch if isinstance(IDispatch, _GoodDispatchTypes): try: IDispatch = pythoncom.connect(IDispatch) except pythoncom.ole_error: IDispatch = pythoncom.CoCreateInstance( IDispatch, None, clsctx, pythoncom.IID_IDispatch ) else: # may already be a wrapped class. IDispatch = getattr(IDispatch, "_oleobj_", IDispatch) return IDispatch def _GetGoodDispatchAndUserName(IDispatch, userName, clsctx): # Get a dispatch object, and a &#39;user name&#39; (ie, the name as # displayed to the user in repr() etc. if userName is None: if isinstance(IDispatch, str): userName = IDispatch ## ??? else userName remains None ??? else: userName = str(userName) return (_GetGoodDispatch(IDispatch, clsctx), userName) def _GetDescInvokeType(entry, invoke_type): # determine the wFlags argument passed as input to IDispatch::Invoke # Only ever called by __getattr__ and __setattr__ from dynamic objects! # * `entry` is a MapEntry with whatever typeinfo we have about the property we are getting/setting. # * `invoke_type` is either INVOKE_PROPERTYGET | INVOKE_PROPERTYSET and really just # means "called by __getattr__" or "called by __setattr__" if not entry or not entry.desc: return invoke_type if entry.desc.desckind == pythoncom.DESCKIND_VARDESC: return invoke_type # So it&#39;s a FUNCDESC - just use what it specifies. return entry.desc.invkind def Dispatch( IDispatch, userName=None, createClass=None, typeinfo=None, clsctx=pythoncom.CLSCTX_SERVER, ): IDispatch, userName = _GetGoodDispatchAndUserName(IDispatch, userName, clsctx) if createClass is None: createClass = CDispatch lazydata = None try: if typeinfo is None: typeinfo = IDispatch.GetTypeInfo() if typeinfo is not None: try: # try for a typecomp typecomp = typeinfo.GetTypeComp() lazydata = typeinfo, typecomp except pythoncom.com_error: pass except pythoncom.com_error: typeinfo = None olerepr = MakeOleRepr(IDispatch, typeinfo, lazydata) return createClass(IDispatch, olerepr, userName, lazydata=lazydata) def MakeOleRepr(IDispatch, typeinfo, typecomp): olerepr = None if typeinfo is not None: try: attr = typeinfo.GetTypeAttr() # If the type info is a special DUAL interface, magically turn it into # a DISPATCH typeinfo. if ( attr[5] == pythoncom.TKIND_INTERFACE and attr[11] & pythoncom.TYPEFLAG_FDUAL ): # Get corresponding Disp interface; # -1 is a special value which does this for us. href = typeinfo.GetRefTypeOfImplType(-1) typeinfo = typeinfo.GetRefTypeInfo(href) attr = typeinfo.GetTypeAttr() if typecomp is None: olerepr = build.DispatchItem(typeinfo, attr, None, 0) else: olerepr = build.LazyDispatchItem(attr, None) except pythoncom.ole_error: pass if olerepr is None: olerepr = build.DispatchItem() return olerepr def DumbDispatch( IDispatch, userName=None, createClass=None, clsctx=pythoncom.CLSCTX_SERVER, ): "Dispatch with no type info" IDispatch, userName = _GetGoodDispatchAndUserName(IDispatch, userName, clsctx) if createClass is None: createClass = CDispatch return createClass(IDispatch, build.DispatchItem(), userName) class CDispatch: def __init__(self, IDispatch, olerepr, userName=None, lazydata=None): if userName is None: userName = "<unknown>" self.__dict__["_oleobj_"] = IDispatch self.__dict__["_username_"] = userName self.__dict__["_olerepr_"] = olerepr self.__dict__["_mapCachedItems_"] = {} self.__dict__["_builtMethods_"] = {} self.__dict__["_enum_"] = None self.__dict__["_unicode_to_string_"] = None self.__dict__["_lazydata_"] = lazydata def __call__(self, *args): "Provide &#39;default dispatch&#39; COM functionality - allow instance to be called" if self._olerepr_.defaultDispatchName: invkind, dispid = self._find_dispatch_type_( self._olerepr_.defaultDispatchName ) else: invkind, dispid = ( pythoncom.DISPATCH_METHOD | pythoncom.DISPATCH_PROPERTYGET, pythoncom.DISPID_VALUE, ) if invkind is not None: allArgs = (dispid, LCID, invkind, 1) + args return self._get_good_object_( self._oleobj_.Invoke(*allArgs), self._olerepr_.defaultDispatchName, None ) raise TypeError("This dispatch object does not define a default method") def __bool__(self): return True # ie "if object:" should always be "true" - without this, __len__ is tried. # _Possibly_ want to defer to __len__ if available, but I&#39;m not sure this is # desirable??? def __repr__(self): return "<COMObject %s>" % (self._username_) def __str__(self): # __str__ is used when the user does "print(object)", so we gracefully # fall back to the __repr__ if the object has no default method. try: return str(self.__call__()) except pythoncom.com_error as details: if details.hresult not in ERRORS_BAD_CONTEXT: raise return self.__repr__() def __dir__(self): attributes = chain(self.__dict__, dir(self.__class__), self._dir_ole_()) try: attributes = chain(attributes, [p.Name for p in self.Properties_]) except AttributeError: pass return list(set(attributes)) def _dir_ole_(self): items_dict = {} for iTI in range(0, self._oleobj_.GetTypeInfoCount()): typeInfo = self._oleobj_.GetTypeInfo(iTI) self._UpdateWithITypeInfo_(items_dict, typeInfo) return list(items_dict) def _UpdateWithITypeInfo_(self, items_dict, typeInfo): typeInfos = [typeInfo] # suppress IDispatch and IUnknown methods inspectedIIDs = {pythoncom.IID_IDispatch: None} while len(typeInfos) > 0: typeInfo = typeInfos.pop() typeAttr = typeInfo.GetTypeAttr() if typeAttr.iid not in inspectedIIDs: inspectedIIDs[typeAttr.iid] = None for iFun in range(0, typeAttr.cFuncs): funDesc = typeInfo.GetFuncDesc(iFun) funName = typeInfo.GetNames(funDesc.memid)[0] if funName not in items_dict: items_dict[funName] = None # Inspect the type info of all implemented types # E.g. IShellDispatch5 implements IShellDispatch4 which implements IShellDispatch3 ... for iImplType in range(0, typeAttr.cImplTypes): iRefType = typeInfo.GetRefTypeOfImplType(iImplType) refTypeInfo = typeInfo.GetRefTypeInfo(iRefType) typeInfos.append(refTypeInfo) # Delegate comparison to the oleobjs, as they know how to do identity. def __eq__(self, other): other = getattr(other, "_oleobj_", other) return self._oleobj_ == other def __ne__(self, other): other = getattr(other, "_oleobj_", other) return self._oleobj_ != other def __int__(self): return int(self.__call__()) def __len__(self): invkind, dispid = self._find_dispatch_type_("Count") if invkind: return self._oleobj_.Invoke(dispid, LCID, invkind, 1) raise TypeError("This dispatch object does not define a Count method") def _NewEnum(self): try: invkind = pythoncom.DISPATCH_METHOD | pythoncom.DISPATCH_PROPERTYGET enum = self._oleobj_.InvokeTypes( pythoncom.DISPID_NEWENUM, LCID, invkind, (13, 10), () ) except pythoncom.com_error: return None # no enumerator for this object. from . import util return util.WrapEnum(enum, None) def __getitem__(self, index): # syver modified # Improved __getitem__ courtesy Syver Enstad # Must check _NewEnum before Item, to ensure b/w compat. if isinstance(index, int): if self.__dict__["_enum_"] is None: self.__dict__["_enum_"] = self._NewEnum() if self.__dict__["_enum_"] is not None: return self._get_good_object_(self._enum_.__getitem__(index)) # See if we have an "Item" method/property we can use (goes hand in hand with Count() above!) invkind, dispid = self._find_dispatch_type_("Item") if invkind is not None: return self._get_good_object_( self._oleobj_.Invoke(dispid, LCID, invkind, 1, index) ) raise TypeError("This object does not support enumeration") def __setitem__(self, index, *args): # XXX - todo - We should support calling Item() here too! # print("__setitem__ with", index, args) if self._olerepr_.defaultDispatchName: invkind, dispid = self._find_dispatch_type_( self._olerepr_.defaultDispatchName ) else: invkind, dispid = ( pythoncom.DISPATCH_PROPERTYPUT | pythoncom.DISPATCH_PROPERTYPUTREF, pythoncom.DISPID_VALUE, ) if invkind is not None: allArgs = (dispid, LCID, invkind, 0, index) + args return self._get_good_object_( self._oleobj_.Invoke(*allArgs), self._olerepr_.defaultDispatchName, None ) raise TypeError("This dispatch object does not define a default method") def _find_dispatch_type_(self, methodName): if methodName in self._olerepr_.mapFuncs: item = self._olerepr_.mapFuncs[methodName] return item.desc[4], item.dispid if methodName in self._olerepr_.propMapGet: item = self._olerepr_.propMapGet[methodName] return item.desc[4], item.dispid try: dispid = self._oleobj_.GetIDsOfNames(0, methodName) except: ### what error? return None, None return pythoncom.DISPATCH_METHOD | pythoncom.DISPATCH_PROPERTYGET, dispid def _ApplyTypes_(self, dispid, wFlags, retType, argTypes, user, resultCLSID, *args): result = self._oleobj_.InvokeTypes( *(dispid, LCID, wFlags, retType, argTypes) + args ) return self._get_good_object_(result, user, resultCLSID) def _wrap_dispatch_( self, ob, userName=None, returnCLSID=None, ): # Given a dispatch object, wrap it in a class return Dispatch(ob, userName) def _get_good_single_object_(self, ob, userName=None, ReturnCLSID=None): if isinstance(ob, PyIDispatchType): # make a new instance of (probably this) class. return self._wrap_dispatch_(ob, userName, ReturnCLSID) if isinstance(ob, PyIUnknownType): try: ob = ob.QueryInterface(pythoncom.IID_IDispatch) except pythoncom.com_error: # It is an IUnknown, but not an IDispatch, so just let it through. return ob return self._wrap_dispatch_(ob, userName, ReturnCLSID) return ob def _get_good_object_(self, ob, userName=None, ReturnCLSID=None): """Given an object (usually the retval from a method), make it a good object to return. Basically checks if it is a COM object, and wraps it up. Also handles the fact that a retval may be a tuple of retvals""" if ob is None: # Quick exit! return None elif isinstance(ob, tuple): return tuple( map( lambda o, s=self, oun=userName, rc=ReturnCLSID: s._get_good_single_object_(o, oun, rc), ob, ) ) else: return self._get_good_single_object_(ob) def _make_method_(self, name): "Make a method object - Assumes in olerepr funcmap" methodName = build.MakePublicAttributeName(name) # translate keywords etc. methodCodeList = self._olerepr_.MakeFuncMethod( self._olerepr_.mapFuncs[name], methodName, 0 ) methodCode = "\n".join(methodCodeList) try: # print(f"Method code for {self._username_} is:\n", methodCode) # self._print_details_() codeObject = compile(methodCode, "<COMObject %s>" % self._username_, "exec") # Exec the code object tempNameSpace = {} # "Dispatch" in the exec&#39;d code is win32com.client.Dispatch, not ours. globNameSpace = globals().copy() globNameSpace["Dispatch"] = win32com.client.Dispatch exec( codeObject, globNameSpace, tempNameSpace ) # self.__dict__, self.__dict__ name = methodName # Save the function in map. fn = self._builtMethods_[name] = tempNameSpace[name] return MethodType(fn, self) except: debug_print("Error building OLE definition for code ", methodCode) traceback.print_exc() return None def _Release_(self): """Cleanup object - like a close - to force cleanup when you don&#39;t want to rely on Python&#39;s reference counting.""" for childCont in self._mapCachedItems_.values(): childCont._Release_() self._mapCachedItems_ = {} if self._oleobj_: self._oleobj_.Release() self.__dict__["_oleobj_"] = None if self._olerepr_: self.__dict__["_olerepr_"] = None self._enum_ = None def _proc_(self, name, *args): """Call the named method as a procedure, rather than function. Mainly used by Word.Basic, which whinges about such things.""" try: item = self._olerepr_.mapFuncs[name] dispId = item.dispid return self._get_good_object_( self._oleobj_.Invoke(*(dispId, LCID, item.desc[4], 0) + (args)) ) except KeyError: raise AttributeError(name) def _print_details_(self): "Debug routine - dumps what it knows about an object." print("AxDispatch container", self._username_) try: print("Methods:") for method in self._olerepr_.mapFuncs: print("\t", method) print("Props:") for prop, entry in self._olerepr_.propMap.items(): print(f"\t{prop} = 0x{entry.dispid:x} - {entry!r}") print("Get Props:") for prop, entry in self._olerepr_.propMapGet.items(): print(f"\t{prop} = 0x{entry.dispid:x} - {entry!r}") print("Put Props:") for prop, entry in self._olerepr_.propMapPut.items(): print(f"\t{prop} = 0x{entry.dispid:x} - {entry!r}") except: traceback.print_exc() def __LazyMap__(self, attr): try: if self._LazyAddAttr_(attr): debug_attr_print( f"{self._username_}.__LazyMap__({attr}) added something" ) return 1 except AttributeError: return 0 # Using the typecomp, lazily create a new attribute definition. def _LazyAddAttr_(self, attr): if self._lazydata_ is None: return 0 res = 0 typeinfo, typecomp = self._lazydata_ olerepr = self._olerepr_ # We need to explicitly check each invoke type individually - simply # specifying &#39;0&#39; will bind to "any member", which may not be the one # we are actually after (ie, we may be after prop_get, but returned # the info for the prop_put.) for i in ALL_INVOKE_TYPES: try: x, t = typecomp.Bind(attr, i) # Support &#39;Get&#39; and &#39;Set&#39; properties - see # bug 1587023 if x == 0 and attr[:3] in ("Set", "Get"): x, t = typecomp.Bind(attr[3:], i) if x == pythoncom.DESCKIND_FUNCDESC: # it&#39;s a FUNCDESC r = olerepr._AddFunc_(typeinfo, t, 0) elif x == pythoncom.DESCKIND_VARDESC: # it&#39;s a VARDESC r = olerepr._AddVar_(typeinfo, t, 0) else: # not found or TYPEDESC/IMPLICITAPP r = None if not r is None: key, map = r[0], r[1] item = map[key] if map == olerepr.propMapPut: olerepr._propMapPutCheck_(key, item) elif map == olerepr.propMapGet: olerepr._propMapGetCheck_(key, item) res = 1 except: pass return res def _FlagAsMethod(self, *methodNames): """Flag these attribute names as being methods. Some objects do not correctly differentiate methods and properties, leading to problems when calling these methods. Specifically, trying to say: ob.SomeFunc() may yield an exception "None object is not callable" In this case, an attempt to fetch the *property* has worked and returned None, rather than indicating it is really a method. Calling: ob._FlagAsMethod("SomeFunc") should then allow this to work. """ for name in methodNames: details = build.MapEntry(self.__AttrToID__(name), (name,)) self._olerepr_.mapFuncs[name] = details def __AttrToID__(self, attr): debug_attr_print( "Calling GetIDsOfNames for property {} in Dispatch container {}".format( attr, self._username_ ) ) return self._oleobj_.GetIDsOfNames(0, attr) def __getattr__(self, attr): if attr == "__iter__": # We can&#39;t handle this as a normal method, as if the attribute # exists, then it must return an iterable object. try: invkind = pythoncom.DISPATCH_METHOD | pythoncom.DISPATCH_PROPERTYGET enum = self._oleobj_.InvokeTypes( pythoncom.DISPID_NEWENUM, LCID, invkind, (13, 10), () ) except pythoncom.com_error: raise AttributeError("This object can not function as an iterator") # We must return a callable object. class Factory: def __init__(self, ob): self.ob = ob def __call__(self): import win32com.client.util return win32com.client.util.Iterator(self.ob) return Factory(enum) if attr.startswith("_") and attr.endswith("_"): # Fast-track. raise AttributeError(attr) # If a known method, create new instance and return. try: return MethodType(self._builtMethods_[attr], self) except KeyError: pass # XXX - Note that we current are case sensitive in the method. # debug_attr_print("GetAttr called for %s on DispatchContainer %s" % (attr,self._username_)) # First check if it is in the method map. Note that an actual method # must not yet exist, (otherwise we would not be here). This # means we create the actual method object - which also means # this code will never be asked for that method name again. if attr in self._olerepr_.mapFuncs: return self._make_method_(attr) # Delegate to property maps/cached items retEntry = None if self._olerepr_ and self._oleobj_: # first check general property map, then specific "put" map. retEntry = self._olerepr_.propMap.get(attr) if retEntry is None: retEntry = self._olerepr_.propMapGet.get(attr) # Not found so far - See what COM says. if retEntry is None: try: if self.__LazyMap__(attr): if attr in self._olerepr_.mapFuncs: return self._make_method_(attr) retEntry = self._olerepr_.propMap.get(attr) if retEntry is None: retEntry = self._olerepr_.propMapGet.get(attr) if retEntry is None: retEntry = build.MapEntry(self.__AttrToID__(attr), (attr,)) except pythoncom.ole_error: pass # No prop by that name - retEntry remains None. if retEntry is not None: # see if in my cache try: ret = self._mapCachedItems_[retEntry.dispid] debug_attr_print("Cached items has attribute!", ret) return ret except (KeyError, AttributeError): debug_attr_print("Attribute %s not in cache" % attr) # If we are still here, and have a retEntry, get the OLE item if retEntry is not None: invoke_type = _GetDescInvokeType(retEntry, pythoncom.INVOKE_PROPERTYGET) debug_attr_print( "Getting property Id 0x%x from OLE object" % retEntry.dispid ) try: ret = self._oleobj_.Invoke(retEntry.dispid, 0, invoke_type, 1) except pythoncom.com_error as details: if details.hresult in ERRORS_BAD_CONTEXT: # May be a method. self._olerepr_.mapFuncs[attr] = retEntry return self._make_method_(attr) raise debug_attr_print("OLE returned ", ret) return self._get_good_object_(ret) # no where else to look. raise AttributeError(f"{self._username_}.{attr}") def __setattr__(self, attr, value): if ( attr in self.__dict__ ): # Fast-track - if already in our dict, just make the assignment. # XXX - should maybe check method map - if someone assigns to a method, # it could mean something special (not sure what, tho!) self.__dict__[attr] = value return # Allow property assignment. debug_attr_print( f"SetAttr called for {self._username_}.{attr}={value!r} on DispatchContainer" ) if self._olerepr_: # Check the "general" property map. if attr in self._olerepr_.propMap: entry = self._olerepr_.propMap[attr] invoke_type = _GetDescInvokeType(entry, pythoncom.INVOKE_PROPERTYPUT) self._oleobj_.Invoke(entry.dispid, 0, invoke_type, 0, value) return # Check the specific "put" map. if attr in self._olerepr_.propMapPut: entry = self._olerepr_.propMapPut[attr] invoke_type = _GetDescInvokeType(entry, pythoncom.INVOKE_PROPERTYPUT) self._oleobj_.Invoke(entry.dispid, 0, invoke_type, 0, value) return # Try the OLE Object if self._oleobj_: if self.__LazyMap__(attr): # Check the "general" property map. if attr in self._olerepr_.propMap: entry = self._olerepr_.propMap[attr] invoke_type = _GetDescInvokeType( entry, pythoncom.INVOKE_PROPERTYPUT ) self._oleobj_.Invoke(entry.dispid, 0, invoke_type, 0, value) return # Check the specific "put" map. if attr in self._olerepr_.propMapPut: entry = self._olerepr_.propMapPut[attr] invoke_type = _GetDescInvokeType( entry, pythoncom.INVOKE_PROPERTYPUT ) self._oleobj_.Invoke(entry.dispid, 0, invoke_type, 0, value) return try: entry = build.MapEntry(self.__AttrToID__(attr), (attr,)) except pythoncom.com_error: # No attribute of that name entry = None if entry is not None: try: invoke_type = _GetDescInvokeType( entry, pythoncom.INVOKE_PROPERTYPUT ) self._oleobj_.Invoke(entry.dispid, 0, invoke_type, 0, value) self._olerepr_.propMap[attr] = entry debug_attr_print( "__setattr__ property {} (id=0x{:x}) in Dispatch container {}".format( attr, entry.dispid, self._username_ ) ) return except pythoncom.com_error: pass raise AttributeError(f"Property &#39;{self._username_}.{attr}&#39; can not be set.") 结合这个文件
09-10
import pandas as pd import networkx as nx import numpy as np import itertools # --- Constants and Assumptions --- # These should be clearly stated and can be modified. VOLTAGE_KV = 10.0 # Line voltage in kV ROOT_3 = np.sqrt(3) BASE_DG_CAPACITY_KW = 300.0 # Initial capacity for each DG N_DG = 8 # Failure rates from problem description FAILURE_RATE_DG_PERCENT = 0.5 / 100.0 # FAILURE_RATE_USER_PERCENT = 0.5 / 100.0 # Not directly used in this simplified line-fault model for widespread outages # FAILURE_RATE_SWITCH_PERCENT = 0.2 / 100.0 # Assuming switch failures manifest as line failures or inability to operate tie FAILURE_RATE_LINE_PER_KM = 0.002 # Per km per year (assuming rates are annual) # Costs (placeholders - these are critical for actual risk values) # Value of Lost Load (VoLL) in monetary units per kW per hour. # For risk = P * C, if P is annual probability, C should be impact of one event. # Let&#39;s define C_loss as total kW unserved * a severity factor. # Or, if we want an annual risk cost: P_annual_fault * kW_unserved * hours_outage * cost_per_kWh # For simplicity, using $/kW of unserved load for the consequence C. COST_VOLL_PER_KW = 10.0 # Example: $10 per kW of unserved load AVG_OUTAGE_DURATION_H = 4 # Example: average hours for an outage, if converting to energy # Cost of Overload (Consequence C_over) # This can be complex: accelerated aging, tripping, damage. # Simplified: A penalty if any line is overloaded in a given state. COST_PENALTY_FOR_ANY_OVERLOAD = 1000.0 # Example: $1000 penalty if system is in an overloaded state # Or, a cost per MWh of overloaded energy, or per overloaded line. # Line and Feeder Capacities # Main feeder rated current from problem: 220A. # P_rated_feeder_kW = ROOT_3 * VOLTAGE_KV * FEEDER_RATED_CURRENT_A * 1.0 (pf=1) # = 1.732 * 10 * 220 = 3810.4 kW (approx 3.8 MW, problem says 2.2MW for 220A, implies lower pf or different basis) # Let&#39;s use current as the primary limit. FEEDER_RATED_CURRENT_A = 220.0 # Assumption for individual line segments: For this model, we&#39;ll assume all lines # have a rated current equal to the main feeder. This is a strong simplification. # A more detailed model would assign ratings based on conductor types or downstream load. LINE_RATED_CURRENT_A = 100.0 # More conservative assumption for individual segments than 220A. Needs proper engineering values. # For lines directly from substation, perhaps 220A is more appropriate. # Let&#39;s use a dictionary for specific line ratings if known, else default. DEFAULT_LINE_RATED_CURRENT_A = 100.0 # Tie Line Capacity TIE_LINE_RATED_CURRENT_A = 150.0 # Assumption, should be based on tie switch/line capacity # DG Locations (Node IDs from 1 to 62) - Based on Figure 1 interpretation DG_LOCATIONS_KW = { 6: BASE_DG_CAPACITY_KW, 10: BASE_DG_CAPACITY_KW, 15: BASE_DG_CAPACITY_KW, 27: BASE_DG_CAPACITY_KW, 31: BASE_DG_CAPACITY_KW, 37: BASE_DG_CAPACITY_KW, 50: BASE_DG_CAPACITY_KW, 58: BASE_DG_CAPACITY_KW } # Tie Switches: (node1, node2, switch_id_text) - normally open # Interpretation based on careful review of Figure 1: # S13-1: (13, 22) - Intra-Feeder 1 (Connects two branches of Feeder 1) # S29-2: (29, 42) - Intra-Feeder 2 (Connects two branches of Feeder 2) # S62-3: (62, 19) - Inter-Feeder (Connects Feeder 3 (node 62) to Feeder 1 (node 19)) TIE_SWITCHES_INFO = [ {&#39;nodes&#39;: (13, 22), &#39;id&#39;: &#39;S13-1&#39;, &#39;type&#39;: &#39;intra-F1&#39;, &#39;capacity_A&#39;: TIE_LINE_RATED_CURRENT_A}, {&#39;nodes&#39;: (29, 42), &#39;id&#39;: &#39;S29-2&#39;, &#39;type&#39;: &#39;intra-F2&#39;, &#39;capacity_A&#39;: TIE_LINE_RATED_CURRENT_A}, {&#39;nodes&#39;: (62, 19), &#39;id&#39;: &#39;S62-3&#39;, &#39;type&#39;: &#39;inter-F3_F1&#39;, &#39;capacity_A&#39;: TIE_LINE_RATED_CURRENT_A} ] # This interpretation means Feeder 2 cannot directly receive support from F1 or F3. # If problem implies all feeders can support each other, TIE_SWITCHES_INFO would need redefinition. # Substation connection points (source nodes for feeders) # CB1 -> Node 1, CB2 -> Node 23, CB3 -> Node 43 # Node 0 will represent the main grid / infinite source. SOURCE_NODE = 0 SUBSTATION_CONNECTIONS = { &#39;CB1&#39;: (SOURCE_NODE, 1), &#39;CB2&#39;: (SOURCE_NODE, 23), &#39;CB3&#39;: (SOURCE_NODE, 43) } # Capacity of connection from source to substation nodes (effectively feeder capacity) SUBSTATION_LINE_CAPACITY_A = FEEDER_RATED_CURRENT_A # --- Data Loading Functions --- def load_load_data(filename="C题附件:有源配电网62节点系统基本参数.xlsx - 表1 有源配电网62节点系统负荷参数.csv"): df = pd.read_csv(filename) df.columns = [&#39;node_id&#39;, &#39;load_kw&#39;] # Convert node_id to int if it&#39;s not already df[&#39;node_id&#39;] = df[&#39;node_id&#39;].astype(int) return df.set_index(&#39;node_id&#39;)[&#39;load_kw&#39;].to_dict() def load_topology_data(filename="C题附件:有源配电网62节点系统基本参数.xlsx - 表2 有源配电网62节点系统拓扑参数.csv"): df = pd.read_csv(filename) # Rename columns for easier access (assuming standard Chinese headers) df.columns = [&#39;line_num&#39;, &#39;from_node&#39;, &#39;to_node&#39;, &#39;length_km&#39;, &#39;resistance_ohm&#39;, &#39;reactance_ohm&#39;] # Convert relevant columns to numeric for col in [&#39;from_node&#39;, &#39;to_node&#39;, &#39;length_km&#39;, &#39;resistance_ohm&#39;, &#39;reactance_ohm&#39;]: df[col] = pd.to_numeric(df[col], errors=&#39;coerce&#39;) return df # --- Core Power Grid Model Class --- class PowerGridModel: def __init__(self, load_data, topology_data, dg_locations_kw, tie_switches_info, substation_connections): self.loads_kw = load_data self.topology_df = topology_data self.dg_kw = dg_locations_kw.copy() # Allow modification for different scenarios self.tie_switches_info = tie_switches_info self.substation_connections = substation_connections self.graph = self._build_graph() self.feeder_info = self._identify_feeders() def _build_graph(self): G = nx.Graph() # Use Graph for undirected, or DiGraph if flow direction is fixed by sources # Add nodes with load and DG info all_nodes = set(self.topology_df[&#39;from_node&#39;]) | set(self.topology_df[&#39;to_node&#39;]) for node_id in all_nodes: node_id = int(node_id) # Ensure int G.add_node(node_id, load_kw=self.loads_kw.get(node_id, 0), dg_kw=self.dg_kw.get(node_id, 0)) # Add lines from topology data for _, row in self.topology_df.iterrows(): u, v = int(row[&#39;from_node&#39;]), int(row[&#39;to_node&#39;]) G.add_edge(u, v, id=row[&#39;line_num&#39;], length_km=row[&#39;length_km&#39;], resistance_ohm=row[&#39;resistance_ohm&#39;], # reactance_ohm=row[&#39;reactance_ohm&#39;], # Ignoring reactance as per problem rated_current_a=DEFAULT_LINE_RATED_CURRENT_A, # Default, can be refined failed=False) # Add substation connections (virtual lines from a common source) # These represent the main feeder lines from CBs G.add_node(SOURCE_NODE, type=&#39;source&#39;) for cb_id, (src, dest_node) in self.substation_connections.items(): G.add_edge(src, dest_node, id=cb_id, length_km=0.01, resistance_ohm=0.001, # Minimal impedance rated_current_a=SUBSTATION_LINE_CAPACITY_A, type=&#39;substation_link&#39;, failed=False) return G def _get_subgraph_with_operational_lines(self, graph_to_copy, faulty_line_edge=None): """Creates a subgraph considering only non-failed lines and open tie switches.""" g_op = graph_to_copy.copy() # Remove failed lines lines_to_remove = [] if faulty_line_edge: # faulty_line_edge is (u,v) if g_op.has_edge(*faulty_line_edge): lines_to_remove.append(faulty_line_edge) for u, v, data in list(g_op.edges(data=True)): if data.get(&#39;failed&#39;, False): lines_to_remove.append((u,v)) g_op.remove_edges_from(lines_to_remove) # Normally, tie switches are open. For restoration, specific ones might be closed. # This base function assumes they are open unless explicitly handled by restoration logic. return g_op def _identify_feeders(self): """Identifies nodes belonging to each feeder under normal operation (tie switches open).""" g_normal = self._get_subgraph_with_operational_lines(self.graph) feeder_info = {} # {&#39;CB1&#39;: {nodes}, &#39;CB2&#39;: {nodes}, ...} for cb_id, (src_node, start_node) in self.substation_connections.items(): if g_normal.has_node(start_node) and g_normal.has_node(src_node) and nx.has_path(g_normal, src_node, start_node): # Find all nodes reachable from start_node without passing through another substation&#39;s start_node # or the main source node again, after removing other substation links. temp_g = g_normal.copy() other_cb_links = [] for other_cb, (s,d) in self.substation_connections.items(): if other_cb != cb_id and temp_g.has_edge(s,d): other_cb_links.append((s,d)) temp_g.remove_edges_from(other_cb_links) if nx.has_path(temp_g, src_node, start_node): # All nodes in the component connected to start_node, excluding the source itself component_nodes = nx.node_connected_component(temp_g.subgraph( [n for n in temp_g.nodes if n != src_node or n == start_node] # Consider start_node part of feeder ), start_node) feeder_info[cb_id] = component_nodes else: # Should not happen if graph is built correctly feeder_info[cb_id] = {start_node} if start_node in g_normal else set() else: feeder_info[cb_id] = {start_node} if start_node in g_normal else set() return feeder_info def _calculate_line_current_kw(self, power_kw): """Calculates current (A) given power (kW) at VOLTAGE_KV (line-to-line).""" if VOLTAGE_KV <= 0: return float(&#39;inf&#39;) return abs(power_kw) / (ROOT_3 * VOLTAGE_KV * 1.0) # Assumed PF=1 for current calculation from P def _get_downstream_info(self, G, line_u, line_v, source_nodes_for_feeder): """ Calculates total load and DG power downstream of a directed line (u,v), assuming v is further from the source_node for this path. G: graph to operate on (can be a faulted graph) source_nodes_for_feeder: list of possible source nodes for the current connected component. """ # Temporarily make graph directed from source to loads to find downstream nodes # This is tricky if the graph is not purely radial or has loops after closing ties. # A simpler approach for radial sections: # Check connectivity from sources to line_v, if line_u is removed. # If line_v is disconnected from all sources when (u,v) is cut, then everything # in the component of line_v is downstream. # Create a copy of G to modify temp_g = G.copy() if not temp_g.has_edge(line_u, line_v): return [], 0, 0 # Line doesn&#39;t exist temp_g.remove_edge(line_u, line_v) downstream_nodes = set() # Check which side (u or v) is disconnected from the source(s) # Assume u is closer to source, v is further. # If v is still connected to a source, then (u,v) might be part of a loop or fed from elsewhere. # A robust way: find path from source to v. If (u,v) is on all paths, then v is downstream of u via this line. # For radial feeders (normal operation): # If we consider (u,v) where u is parent of v: component_of_v = set() q = [line_v] visited = {line_u, line_v} # Start by marking u as visited (as if coming from u) # If line_v is connected to any source_node without passing through line_u v_connected_to_source_alt_path = False for src in source_nodes_for_feeder: if nx.has_path(temp_g, src, line_v): v_connected_to_source_alt_path = True break if v_connected_to_source_alt_path: # (u,v) is part of a loop or v is fed from elsewhere # This simple downstream logic is insufficient for meshed networks. # For now, assume radial for this part of flow calculation. # A more complex flow calculation (Newton-Raphson) would be needed for meshed. # Given problem constraints, assume feeders are normally radial. pass # This line might not have a clear "downstream" if looped. # If v is disconnected from source when (u,v) is cut, then its component is downstream. # Check connectivity for v in temp_g (where (u,v) is removed) v_still_connected = any(nx.has_path(temp_g, src, line_v) for src in source_nodes_for_feeder if src in temp_g) if not v_still_connected: # v is now isolated from source, so its component is downstream of (u,v) component_of_v = nx.node_connected_component(temp_g, line_v) else: # v is still connected, means (u,v) might be redundant or complex. # Try to determine direction based on distance from source dist_u = float(&#39;inf&#39;) dist_v = float(&#39;inf&#39;) for src in source_nodes_for_feeder: if src not in G: continue if nx.has_path(G, src, line_u): dist_u = min(dist_u, nx.shortest_path_length(G, src, line_u)) if nx.has_path(G, src, line_v): dist_v = min(dist_v, nx.shortest_path_length(G, src, line_v)) if dist_v > dist_u : # v is downstream of u # Find component of v if (u,v) is removed and v is not connected to source g_temp_removed_edge = G.copy() g_temp_removed_edge.remove_edge(line_u,line_v) is_v_conn_to_src = False for src_node_feeder in source_nodes_for_feeder: if src_node_feeder in g_temp_removed_edge and nx.has_path(g_temp_removed_edge, src_node_feeder, line_v): is_v_conn_to_src = True break if not is_v_conn_to_src: component_of_v = nx.node_connected_component(g_temp_removed_edge, line_v) else: # v is still connected, (u,v) is likely a loop closing line. Flow is complex. # For simplicity, this function will return 0 flow for loop lines if direction is ambiguous. return [], 0, 0 elif dist_u > dist_v: # u is downstream of v (swap them) # similar logic for u g_temp_removed_edge = G.copy() g_temp_removed_edge.remove_edge(line_u,line_v) is_u_conn_to_src = False for src_node_feeder in source_nodes_for_feeder: if src_node_feeder in g_temp_removed_edge and nx.has_path(g_temp_removed_edge, src_node_feeder, line_u): is_u_conn_to_src = True break if not is_u_conn_to_src: component_of_v = nx.node_connected_component(g_temp_removed_edge, line_u) # component_of_v is actually comp of u else: return [], 0, 0 else: # Equidistant or complex, cannot determine simple downstream for this line return [], 0, 0 downstream_nodes = component_of_v total_downstream_load_kw = sum(G.nodes[n][&#39;load_kw&#39;] for n in downstream_nodes) total_downstream_dg_kw = sum(G.nodes[n][&#39;dg_kw&#39;] for n in downstream_nodes if G.nodes[n][&#39;dg_kw&#39;] > 0) return list(downstream_nodes), total_downstream_load_kw, total_downstream_dg_kw def calculate_power_flows_and_currents(self, current_graph_state, active_dgs_kw): """ Simplified power flow for radial networks or parts of networks. Returns dict of line flows and currents, and substation powers. Flows: {(u,v): power_kw} where power_kw > 0 means u to v. Currents: {(u,v): current_A} Substation_powers: {&#39;CB1&#39;: power_kw_drawn_from_substation} """ line_flows_kw = {} line_currents_a = {} substation_powers_kw = {} # Update DG outputs in the graph state for node_id, dg_val in active_dgs_kw.items(): if node_id in current_graph_state: current_graph_state.nodes[node_id][&#39;dg_kw&#39;] = dg_val for node_id in current_graph_state.nodes(): # Reset others if not in active_dgs_kw if node_id not in active_dgs_kw and &#39;dg_kw&#39; in current_graph_state.nodes[node_id]: if current_graph_state.nodes[node_id].get(&#39;type&#39;) != &#39;source&#39;: # Don&#39;t zero out if it was never a DG current_graph_state.nodes[node_id][&#39;dg_kw&#39;] = 0 # Determine connected components and their sources # This is a very simplified load flow. It assumes power flows from sources (substations) # down to loads. DG power reduces the load seen by upstream sections. # It does not handle loops well without iterative methods (e.g. Hardy Cross or Newton-Raphson). # For each feeder, calculate flows assuming radial structure # This is an approximation. A full AC or DC power flow is more accurate. # Initialize all line flows to 0 for u, v in current_graph_state.edges(): line_flows_kw[(u,v)] = 0 line_flows_kw[(v,u)] = 0 # For bi-directional calculation needs line_currents_a[(u,v)] = 0 # Iterate multiple times for flow distribution in case of ties or complex paths # This is a placeholder for a proper iterative flow solution. # For now, a topological sort based flow for radial parts. processed_nodes_for_flow_calc = set() for cb_id, (src_node, start_node) in self.substation_connections.items(): if not current_graph_state.has_node(start_node) or not nx.is_connected(current_graph_state.subgraph([n for n in current_graph_state.nodes() if n != SOURCE_NODE])): # This feeder might be entirely down if start_node is disconnected from actual nodes substation_powers_kw[cb_id] = 0 continue # Get nodes for this feeder (dynamic based on current_graph_state) # Nodes connected to start_node, excluding SOURCE_NODE, if start_node is connected to SOURCE_NODE feeder_nodes_component = set() if current_graph_state.has_edge(src_node, start_node): temp_g_for_feeder = current_graph_state.copy() # Remove other substation links to isolate this feeder&#39;s component other_links_to_remove = [] for other_cb, (s,d) in self.substation_connections.items(): if other_cb != cb_id and temp_g_for_feeder.has_edge(s,d): other_links_to_remove.append((s,d)) temp_g_for_feeder.remove_edges_from(other_links_to_remove) if nx.has_path(temp_g_for_feeder, src_node, start_node): try: # Consider only the part of the graph reachable from start_node, not crossing back to SOURCE_NODE # except via the designated start_node path. search_nodes = [n for n in temp_g_for_feeder.nodes if n != src_node] sub_graph_feeder = temp_g_for_feeder.subgraph(search_nodes) if start_node in sub_graph_feeder: feeder_nodes_component = nx.node_connected_component(sub_graph_feeder, start_node) except nx.NetworkXError: # if start_node not in subgraph feeder_nodes_component = set() if not feeder_nodes_component: substation_powers_kw[cb_id] = 0 continue # Order nodes from furthest to closest to substation for power accumulation # This is for radial feeders. If loops exist, this is not sufficient. # Using BFS layers from start_node # Net load at each node (Load - DG) node_net_power_kw = {} for node in feeder_nodes_component: node_net_power_kw[node] = current_graph_state.nodes[node][&#39;load_kw&#39;] - current_graph_state.nodes[node][&#39;dg_kw&#39;] # Accumulate power up towards the substation # This requires a tree traversal (e.g., DFS post-order traversal) from leaves to root. # For simplicity, if the feeder is a tree rooted at start_node: if nx.is_tree(current_graph_state.subgraph(feeder_nodes_component | {start_node})): # Check if it&#39;s a tree # Create a directed tree towards the source for easier traversal # This part is complex if graph is not a tree. # For now, sum all net loads on the feeder as the substation power (approximation) total_feeder_net_load = sum(node_net_power_kw[n] for n in feeder_nodes_component) substation_powers_kw[cb_id] = total_feeder_net_load # Distribute this flow down the lines (highly simplified) # A proper method: for each line, sum net_power of all nodes in subtree rooted by that line. # This simplified flow calculation is a major placeholder. # For overload risk, we need per-line flows. # Simplified: assume current_graph_state is a tree rooted at start_node for this feeder # Use BFS to assign flow from start_node downwards # This is not a full power flow, but an estimation for line loading. # Build a directed graph for this feeder based on BFS from start_node # This is just for flow assignment direction. # Actual flow needs to sum up demands from downstream. # For each edge (u,v) in the feeder: # Determine parent (closer to start_node) and child # Power on (parent, child) = sum of net loads in subtree rooted at child. # This is complex to implement robustly here without a full flow algorithm. # Fallback: Use the _get_downstream_info logic if possible, iterate edges # This needs to be called carefully to avoid double counting or misdirection in non-radial. # For now, this function will primarily return substation_powers_kw and # leave detailed line_flows_kw and line_currents_a for a more robust implementation # or accept its high level of approximation. # Let&#39;s try a slightly better approximation for line flows on a tree: # For each edge (u,v) in the feeder tree (rooted at start_node) # Assume u is parent of v. Power(u,v) = sum of net_power for all nodes in subtree of v. if start_node in feeder_nodes_component: # Should be try: dfs_edges = list(nx.dfs_edges(nx.bfs_tree(current_graph_state.subgraph(feeder_nodes_component), start_node), source=start_node)) # Calculate power for each node including its children&#39;s power node_total_subtree_power = node_net_power_kw.copy() for u, v in reversed(dfs_edges): # From leaves up to root if u in node_total_subtree_power and v in node_total_subtree_power: node_total_subtree_power[u] += node_total_subtree_power[v] for u,v in dfs_edges: # From root down if v in node_total_subtree_power: flow = node_total_subtree_power[v] line_flows_kw[(u,v)] = flow line_flows_kw[(v,u)] = -flow # Convention for direction line_currents_a[(u,v)] = self._calculate_line_current_kw(flow) except Exception as e: # print(f"Warning: Could not perform tree-based flow for {cb_id} due to {e}") pass # Keep substation power as sum, line flows might be inaccurate else: # Not a tree, flow calculation is more complex. # print(f"Warning: Feeder {cb_id} is not a tree. Simplified flow may be inaccurate.") total_feeder_net_load = sum(node_net_power_kw[n] for n in feeder_nodes_component if n in node_net_power_kw) substation_powers_kw[cb_id] = total_feeder_net_load # Line flows in meshed networks require iterative solvers. # For now, this part will be very approximate for meshed sections. # Handle reverse power flow and inter-feeder DG adjustment # "分布式能源不得向上级电网倒送功率" # "可以在相邻馈线间进行调节" for cb_id in list(substation_powers_kw.keys()): if substation_powers_kw[cb_id] < 0: # Reverse power flow excess_dg_on_feeder = -substation_powers_kw[cb_id] # Try to transfer to other feeders via inter-feeder tie lines # This logic is complex and needs careful state management. # For Q1, a simpler approach might be DG curtailment on that feeder. # Find inter-feeder tie switches connected to this feeder # Example: S62-3 connects Feeder 3 (node 62) to Feeder 1 (node 19) # If Feeder 1 has excess_dg, it might try to send to Feeder 3 via (19,62) # This is an advanced feature. For now, assume DG curtailment if倒送. # To implement curtailment: identify DGs on this feeder, reduce their output # proportionally until substation_powers_kw[cb_id] >= 0. # This would require re-calculating flows. # For now, just flag it. # print(f"Warning: Reverse power flow on {cb_id} of {substation_powers_kw[cb_id]} kW. DG curtailment or transfer needed.") # A simple curtailment: dG_on_feeder_nodes = [n for n in self.feeder_info.get(cb_id, set()) if n in active_dgs_kw and active_dgs_kw[n] > 0] total_dg_cap_on_feeder = sum(active_dgs_kw[n] for n in dG_on_feeder_nodes) if total_dg_cap_on_feeder > 0: curtail_ratio = min(1.0, excess_dg_on_feeder / total_dg_cap_on_feeder) if total_dg_cap_on_feeder >0 else 0 for dg_node in dG_on_feeder_nodes: active_dgs_kw[dg_node] *= (1 - curtail_ratio) # Flows need to be recalculated after curtailment. This suggests an iterative solution. # For this submission, we&#39;ll assume this check is done *before* final flow calc, # or simply note the violation. # To avoid recursion here, this function should ideally take DGs as fixed input. # The adjustment logic should be outside or iterative. pass return line_flows_kw, line_currents_a, substation_powers_kw def calculate_overload_risk(self): """ Calculates overload risk for the current DG setup. Assumes DGs are at their BASE_DG_CAPACITY_KW. """ # Get current operational graph (no faults, ties normally open) g_op = self._get_subgraph_with_operational_lines(self.graph) # Calculate power flows and currents # Need to handle DG outputs properly. current_dg_outputs = self.dg_kw.copy() # Use the model&#39;s current DG settings # Iterative step for DG curtailment if reverse power flow: # This is a simplified loop. A more robust solution uses optimization or better heuristics. for _iter in range(3): # Max 3 iterations for adjustment line_flows, line_currents, substation_powers = self.calculate_power_flows_and_currents(g_op, current_dg_outputs) reverse_power_detected = False for cb_id, power_kw in substation_powers.items(): if power_kw < -1e-3: # Small threshold for倒送 reverse_power_detected = True # print(f"Info: Reverse power on {cb_id} ({power_kw:.2f} kW). Attempting curtailment.") excess_dg_on_feeder = abs(power_kw) feeder_nodes_for_cb = self.feeder_info.get(cb_id, set()) dG_on_feeder_nodes = [n for n in feeder_nodes_for_cb if n in current_dg_outputs and current_dg_outputs[n] > 0] total_dg_cap_on_feeder = sum(current_dg_outputs[n] for n in dG_on_feeder_nodes) if total_dg_cap_on_feeder > 1e-3 : # Avoid division by zero curtail_amount_total = excess_dg_on_feeder for dg_node in dG_on_feeder_nodes: # Proportional curtailment proportion = current_dg_outputs[dg_node] / total_dg_cap_on_feeder curtail_this_dg = proportion * curtail_amount_total current_dg_outputs[dg_node] = max(0, current_dg_outputs[dg_node] - curtail_this_dg) else: # No DG to curtail, reverse power might be from other sources or model issue pass if not reverse_power_detected: break # Final flows after potential curtailment line_flows, line_currents, substation_powers = self.calculate_power_flows_and_currents(g_op, current_dg_outputs) overloaded_lines = [] for u, v, data in g_op.edges(data=True): if data.get(&#39;type&#39;) == &#39;substation_link&#39;: continue # Don&#39;t check substation links themselves for overload here current = line_currents.get((u,v), 0) # If flow is from v to u, current might be stored as current_uv = -current_vu # Take absolute value of flow for current calculation, or ensure current is always positive. # The _calculate_line_current_kw uses abs(power_kw) so current should be positive. rated_current = data.get(&#39;rated_current_a&#39;, DEFAULT_LINE_RATED_CURRENT_A) if current > 1.1 * rated_current: overloaded_lines.append({&#39;edge&#39;: (u,v), &#39;current&#39;: current, &#39;rated&#39;: rated_current, &#39;over_by_%&#39;: (current/(1.1*rated_current)-1)*100 if rated_current>0 else float(&#39;inf&#39;)}) if overloaded_lines: # print(f"System Overload Detected. Overloaded lines: {overloaded_lines}") # P_over = 1 (for this deterministic scenario) # C_over = fixed penalty or sum of penalties risk_overload = 1.0 * COST_PENALTY_FOR_ANY_OVERLOAD # Or, sum of consequences for each overloaded line, if C_over is per line. # risk_overload = sum(some_cost_function(ol[&#39;over_by_%&#39;]) for ol in overloaded_lines) else: # print("System is NOT overloaded in the base case.") risk_overload = 0.0 return risk_overload, overloaded_lines, substation_powers, current_dg_outputs def calculate_load_loss_risk(self): """ Calculates total load loss risk by considering single line faults. R_loss = sum(P_fault_i * C_loss_i) P_fault_i = annual probability of fault i C_loss_i = consequence of fault i (e.g., unserved_load_kw * COST_VOLL_PER_KW) """ total_load_loss_risk = 0.0 detailed_fault_impacts = [] # Iterate through all operational lines (excluding substation virtual links for fault simulation) original_edges = [ (u,v,data) for u,v,data in self.graph.edges(data=True) if data.get(&#39;type&#39;) != &#39;substation_link&#39; and not data.get(&#39;is_tie&#39;, False)] for u_fault, v_fault, line_data_faulted in original_edges: faulty_edge = (u_fault, v_fault) line_length_km = line_data_faulted.get(&#39;length_km&#39;, 0) # Probability of this specific line failing (annual) # Assuming failure rates are independent and this is the probability of this line being the one to fail. prob_line_fault = line_length_km * FAILURE_RATE_LINE_PER_KM if prob_line_fault == 0: continue # --- Simulate fault --- g_faulted = self.graph.copy() if not g_faulted.has_edge(*faulty_edge): continue g_faulted.edges[faulty_edge][&#39;failed&#39;] = True # Mark as failed g_after_fault_isolation = self._get_subgraph_with_operational_lines(g_faulted, faulty_edge=faulty_edge) # --- Identify initial load loss --- initial_unserved_load_kw = 0 disconnected_load_nodes = {} # {node: load_kw} # Check connectivity for all load nodes for node_id, load_kw in self.loads_kw.items(): if load_kw <= 0: continue is_connected_to_source = False for cb_id, (src_node, start_node) in self.substation_connections.items(): if nx.has_path(g_after_fault_isolation, src_node, node_id): is_connected_to_source = True break if not is_connected_to_source: initial_unserved_load_kw += load_kw disconnected_load_nodes[node_id] = load_kw if initial_unserved_load_kw == 0: # Fault does not cause load loss (e.g. redundant line) detailed_fault_impacts.append({&#39;fault&#39;: faulty_edge, &#39;unserved_kw_initial&#39;: 0, &#39;unserved_kw_final&#39;:0, &#39;restored_kw&#39;:0, &#39;risk_contrib&#39;:0}) continue # --- Attempt restoration via tie lines --- # This is a complex part. Needs to: # 1. Identify disconnected areas and loads. # 2. Identify available tie switches that can connect these areas to healthy feeders. # 3. Check capacity of tie lines and the supporting feeder. # 4. Prioritize restoration (e.g., maximize load restored). # For this model, a simplified restoration: # Iterate over available tie switches. If closing one helps, simulate it. # This should be greedy or more optimized. restored_load_kw_total_for_this_fault = 0 # Create a graph state for restoration attempts g_for_restoration = g_after_fault_isolation.copy() # Sort disconnected loads by size (optional, for prioritization) sorted_disconnected_loads = sorted(disconnected_load_nodes.items(), key=lambda item: item[1], reverse=True) # Try closing tie switches one by one (if they connect a live part to a dead part) # This is a very simplified greedy approach. # A proper approach would evaluate all combinations or use optimization. # Identify current live sources/feeders live_feeder_sources = [] # (cb_id, start_node_of_live_feeder) for cb_id, (src,start) in self.substation_connections.items(): if nx.has_path(g_for_restoration, src, start): # Check if substation itself is connected live_feeder_sources.append(start) for tie in self.tie_switches_info: tie_n1, tie_n2 = tie[&#39;nodes&#39;] tie_capacity_a = tie[&#39;capacity_A&#39;] if not g_for_restoration.has_node(tie_n1) or not g_for_restoration.has_node(tie_n2): continue if g_for_restoration.has_edge(tie_n1, tie_n2): continue # Already closed or part of main graph (should not be for ties) # Check if one end is live and other is dead (or part of the disconnected component) tie_n1_is_live = any(nx.has_path(g_for_restoration, src, tie_n1) for src in live_feeder_sources) tie_n2_is_live = any(nx.has_path(g_for_restoration, src, tie_n2) for src in live_feeder_sources) if tie_n1_is_live == tie_n2_is_live: continue # Both live or both dead, closing doesn&#39;t bridge outage for now live_tie_node, dead_tie_node = (tie_n1, tie_n2) if tie_n1_is_live else (tie_n2, tie_n1) # Check if dead_tie_node is part of the current outage we are trying to fix # This requires knowing which component dead_tie_node belongs to. # For now, assume if it&#39;s not live, it&#39;s part of some outage. # Simulate closing this tie switch g_for_restoration.add_edge(live_tie_node, dead_tie_node, id=tie[&#39;id&#39;], type=&#39;tie_closed&#39;, rated_current_a=tie_capacity_a, resistance_ohm=0.001, length_km=0.01) # Check how much load can be restored through this tie without overloading tie or new path # This requires a flow calculation on g_for_restoration. # Simplified: Check loads now connected. newly_restored_load_kw_this_tie = 0 temp_restored_nodes_this_tie = [] for node_id, load_val in disconnected_load_nodes.items(): if node_id not in g_for_restoration: continue # Should not happen # Check if this node is now connected to ANY source is_now_connected = any(nx.has_path(g_for_restoration, src, node_id) for src in live_feeder_sources) if is_now_connected and node_id not in temp_restored_nodes_this_tie: # And not already counted as restored by previous ties # More checks needed: # 1. Tie line capacity: Power through (live_tie_node, dead_tie_node) <= tie_capacity_a # 2. Path capacity on the live feeder. # This is where the simplified flow becomes a bottleneck. # For now, assume if connected, it can be restored up to a certain limit. # This is a MAJOR simplification. newly_restored_load_kw_this_tie += load_val temp_restored_nodes_this_tie.append(node_id) # Here, we&#39;d need to check if adding newly_restored_load_kw_this_tie overloads the tie or feeder. # If current_through_tie > tie_capacity_a, then not all of this load can be restored. # This part needs a proper constrained flow allocation. # For now, let&#39;s assume a fraction can be restored if connected, or all if small. # This is a placeholder for a more robust restoration algorithm. # Let&#39;s assume, for now, if connected, it&#39;s restored. If this overloads things, # the overload risk model should capture it (but that&#39;s for normal state). # Here, the goal is to minimize unserved load. # If this tie leads to overload, we shouldn&#39;t use it or only partially. # For now, naively accept all newly connected load. if newly_restored_load_kw_this_tie > 0: restored_load_kw_total_for_this_fault += newly_restored_load_kw_this_tie # Update disconnected_load_nodes: for r_node in temp_restored_nodes_this_tie: if r_node in disconnected_load_nodes: del disconnected_load_nodes[r_node] # No longer disconnected else: # Closing this tie didn&#39;t help, revert if g_for_restoration.has_edge(live_tie_node, dead_tie_node): g_for_restoration.remove_edge(live_tie_node, dead_tie_node) # Final unserved load for this fault scenario final_unserved_load_kw = initial_unserved_load_kw - restored_load_kw_total_for_this_fault final_unserved_load_kw = max(0, final_unserved_load_kw) # Cannot be negative consequence_c_loss = final_unserved_load_kw * COST_VOLL_PER_KW risk_contribution = prob_line_fault * consequence_c_loss total_load_loss_risk += risk_contribution detailed_fault_impacts.append({ &#39;fault_type&#39;: &#39;line&#39;, &#39;component_id&#39;: faulty_edge, &#39;prob_fault&#39;: prob_line_fault, &#39;unserved_kw_initial&#39;: initial_unserved_load_kw, &#39;restored_kw&#39;: restored_load_kw_total_for_this_fault, &#39;unserved_kw_final&#39;: final_unserved_load_kw, &#39;consequence_c_loss&#39;: consequence_c_loss, &#39;risk_contribution&#39;: risk_contribution }) # TODO: Add DG faults, Switch faults, User faults if they cause wider outages. # For DG faults: prob_dg_fault = FAILURE_RATE_DG_PERCENT # A DG fault primarily impacts system&#39;s ability to meet load or avoid overload. # It doesn&#39;t directly cause load loss unless it&#39;s islanded and the DG is the only source. # The problem implies grid-connected DGs. return total_load_loss_risk, detailed_fault_impacts # --- Main Execution --- if __name__ == &#39;__main__&#39;: print("--- 配电网风险评估模型 Q1 ---") # 1. Load Data print("\n1. 加载数据...") loads = load_load_data() topology = load_topology_data() # print(f"负荷数据: {len(loads)} 点") # print(f"拓扑数据: {len(topology)} 条线路") # 2. Initialize Power Grid Model print("\n2. 初始化电网模型...") grid = PowerGridModel(loads, topology, DG_LOCATIONS_KW, TIE_SWITCHES_INFO, SUBSTATION_CONNECTIONS) # print(f"电网图: {grid.graph.number_of_nodes()} 个节点, {grid.graph.number_of_edges()} 条边") # print(f"馈线信息: {grid.feeder_info}") # --- 问题1: 失负荷风险和过负荷风险计算模型 --- print("\n--- 问题1: 风险计算 ---") # A. 过负荷风险模型 (R_over = P_over * C_over) # For Q1, DGs are at BASE_DG_CAPACITY_KW. This is a deterministic check for this state. # P_over = 1 if overload occurs, 0 otherwise. C_over is the penalty. print("\nA. 计算过负荷风险...") # Note: The calculate_power_flows_and_currents is highly simplified. # Results for overload depend heavily on its accuracy and line ratings. try: risk_overload, overloaded_lines_details, substation_p, final_dg_out = grid.calculate_overload_risk() print(f" 计算得到的过负荷风险 (R_over): ${risk_overload:.2f}") if overloaded_lines_details: print(f" 检测到过负荷线路 ({len(overloaded_lines_details)} 条):") # for ol in overloaded_lines_details[:3]: # Print first 3 # print(f" - 线路 {ol[&#39;edge&#39;]}, 电流: {ol[&#39;current&#39;]:.2f}A, 额定: {ol[&#39;rated&#39;]:.2f}A, 超出: {ol[&#39;over_by_%&#39;]:.2f}%") else: print(" 在当前DG配置下,未检测到线路过负荷。") # print(f" 变电站出口功率 (kW): {substation_p}") # print(f" 最终DG出力 (kW) (可能经过削减): {final_dg_out}") except Exception as e: print(f" 计算过负荷风险时发生错误: {e}") risk_overload = -1 # Indicate error # B. 失负荷风险模型 (R_loss = sum(P_fault_i * C_loss_i)) print("\nB. 计算失负荷风险...") # Note: Restoration logic is simplified. try: total_r_loss, fault_details = grid.calculate_load_loss_risk() print(f" 计算得到的总失负荷风险 (R_loss): ${total_r_loss:.2f} (基于所选成本)") # print("\n 部分故障场景详情:") # for fd in fault_details[:3]: # Print first 3 # print(f" - 故障线路: {fd.get(&#39;component_id&#39;)}, " # f"初始失负荷: {fd.get(&#39;unserved_kw_initial&#39;):.2f} kW, " # f"最终失负荷: {fd.get(&#39;unserved_kw_final&#39;):.2f} kW, " # f"风险贡献: ${fd.get(&#39;risk_contribution&#39;):.2f}") except Exception as e: print(f" 计算失负荷风险时发生错误: {e}") total_r_loss = -1 # Indicate error print("\n--- 模型执行完毕 ---") print("注意: 此模型包含多项简化和假设 (如线路额定电流, 成本参数, 潮流计算简化, 恢复逻辑简化).") print("结果的准确性取决于这些假设的合理性和参数的精确性。") 对此代码进行分析
05-12
基于51单片机,实现对直流电机的调速、测速以及正反转控制。项目包含完整的仿真文件、源程序、原理图和PCB设计文件,适合学习和实践51单片机在电机控制方面的应用。 功能特点 调速控制:通过按键调整PWM占空比,实现电机的速度调节。 测速功能:采用霍尔传感器非接触式测速,实时显示电机转速。 正反转控制:通过按键切换电机的正转和反转状态。 LCD显示:使用LCD1602液晶显示屏,显示当前的转速和PWM占空比。 硬件组成 主控制器:STC89C51/52单片机(与AT89S51/52、AT89C51/52通用)。 测速传感器:霍尔传感器,用于非接触式测速。 显示模块:LCD1602液晶显示屏,显示转速和占空比。 电机驱动:采用双H桥电路,控制电机的正反转和调速。 软件设计 编程语言:C语言。 开发环境:Keil uVision。 仿真工具:Proteus。 使用说明 液晶屏显示: 第一行显示电机转速(单位:转/分)。 第二行显示PWM占空比(0~100%)。 按键功能: 1键:加速键,短按占空比加1,长按连续加。 2键:减速键,短按占空比减1,长按连续减。 3键:反转切换键,按下后电机反转。 4键:正转切换键,按下后电机正转。 5键:开始暂停键,按一下开始,再按一下暂停。 注意事项 磁铁和霍尔元件的距离应保持在2mm左右,过近可能会在电机转动时碰到霍尔元件,过远则可能导致霍尔元件无法检测到磁铁。 资源文件 仿真文件:Proteus仿真文件,用于模拟电机控制系统的运行。 源程序:Keil uVision项目文件,包含完整的C语言源代码。 原理图:电路设计原理图,详细展示了各模块的连接方式。 PCB设计:PCB布局文件,可用于实际电路板的制作。
【四旋翼无人机】具备螺旋桨倾斜机构的全驱动四旋翼无人机:建模与控制研究(Matlab代码、Simulink仿真实现)内容概要:本文围绕具备螺旋桨倾斜机构的全驱动四旋翼无人机展开研究,重点进行了系统建模与控制策略的设计与仿真验证。通过引入螺旋桨倾斜机构,该无人机能够实现全向力矢量控制,从而具备更强的姿态调节能力和六自由度全驱动特性,克服传统四旋翼欠驱动限制。研究内容涵盖动力学建模、控制系统设计(如PID、MPC等)、Matlab/Simulink环境下的仿真验证,并可能涉及轨迹跟踪、抗干扰能力及稳定性分析,旨在提升无人机在复杂环境下的机动性与控制精度。; 适合人群:具备一定控制理论基础和Matlab/Simulink仿真能力的研究生、科研人员及从事无人机系统开发的工程师,尤其适合研究先进无人机控制算法的技术人员。; 使用场景及目标:①深入理解全驱动四旋翼无人机的动力学建模方法;②掌握基于Matlab/Simulink的无人机控制系统设计与仿真流程;③复现硕士论文级别的研究成果,为科研项目或学术论文提供技术支持与参考。; 阅读建议:建议结合提供的Matlab代码与Simulink模型进行实践操作,重点关注建模推导过程与控制器参数调优,同时可扩展研究不同控制算法的性能对比,以深化对全驱动系统控制机制的理解。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值