diff --git a/ams/__init__.py b/ams/__init__.py index 379d7b01..5223e865 100644 --- a/ams/__init__.py +++ b/ams/__init__.py @@ -2,7 +2,6 @@ __version__ = _version.get_versions()['version'] from ams import opt # NOQA -from ams import benchmarks # NOQA from ams.main import config_logger, load, run # NOQA from ams.system import System # NOQA diff --git a/ams/cases/5bus/pjm5bus_demo.xlsx b/ams/cases/5bus/pjm5bus_demo.xlsx index 0c406687..120cf65a 100644 Binary files a/ams/cases/5bus/pjm5bus_demo.xlsx and b/ams/cases/5bus/pjm5bus_demo.xlsx differ diff --git a/ams/cases/5bus/pjm5bus_jumper.xlsx b/ams/cases/5bus/pjm5bus_jumper.xlsx index ce398420..c445cad4 100644 Binary files a/ams/cases/5bus/pjm5bus_jumper.xlsx and b/ams/cases/5bus/pjm5bus_jumper.xlsx differ diff --git a/ams/cases/5bus/pjm5bus_uced.json b/ams/cases/5bus/pjm5bus_uced.json index a9285b13..15da1833 100644 --- a/ams/cases/5bus/pjm5bus_uced.json +++ b/ams/cases/5bus/pjm5bus_uced.json @@ -514,7 +514,7 @@ "name": 3 } ], - "Region": [ + "Zone": [ { "idx": "ZONE_1", "u": 1.0, diff --git a/ams/cases/5bus/pjm5bus_uced.xlsx b/ams/cases/5bus/pjm5bus_uced.xlsx index 69865865..28b19554 100644 Binary files a/ams/cases/5bus/pjm5bus_uced.xlsx and b/ams/cases/5bus/pjm5bus_uced.xlsx differ diff --git a/ams/cases/5bus/pjm5bus_uced_esd1.xlsx b/ams/cases/5bus/pjm5bus_uced_esd1.xlsx index 18b90430..6c69ecf4 100644 Binary files a/ams/cases/5bus/pjm5bus_uced_esd1.xlsx and b/ams/cases/5bus/pjm5bus_uced_esd1.xlsx differ diff --git a/ams/cases/5bus/pjm5bus_uced_ev.xlsx b/ams/cases/5bus/pjm5bus_uced_ev.xlsx index 57355608..98dfc67a 100644 Binary files a/ams/cases/5bus/pjm5bus_uced_ev.xlsx and b/ams/cases/5bus/pjm5bus_uced_ev.xlsx differ diff --git a/ams/cases/ieee14/ieee14_uced.xlsx b/ams/cases/ieee14/ieee14_uced.xlsx index a484cfb9..e1e2cf47 100644 Binary files a/ams/cases/ieee14/ieee14_uced.xlsx and b/ams/cases/ieee14/ieee14_uced.xlsx differ diff --git a/ams/cases/ieee39/ieee39.xlsx b/ams/cases/ieee39/ieee39.xlsx index eeb6d52f..c8df360f 100644 Binary files a/ams/cases/ieee39/ieee39.xlsx and b/ams/cases/ieee39/ieee39.xlsx differ diff --git a/ams/cases/ieee39/ieee39_uced.xlsx b/ams/cases/ieee39/ieee39_uced.xlsx index 1bb21eab..7f1e0c92 100644 Binary files a/ams/cases/ieee39/ieee39_uced.xlsx and b/ams/cases/ieee39/ieee39_uced.xlsx differ diff --git a/ams/cases/ieee39/ieee39_uced_esd1.xlsx b/ams/cases/ieee39/ieee39_uced_esd1.xlsx index 362ca5be..e15fa7ec 100644 Binary files a/ams/cases/ieee39/ieee39_uced_esd1.xlsx and b/ams/cases/ieee39/ieee39_uced_esd1.xlsx differ diff --git a/ams/cases/ieee39/ieee39_uced_pvd1.xlsx b/ams/cases/ieee39/ieee39_uced_pvd1.xlsx index 2710b4d4..f2a334ed 100644 Binary files a/ams/cases/ieee39/ieee39_uced_pvd1.xlsx and b/ams/cases/ieee39/ieee39_uced_pvd1.xlsx differ diff --git a/ams/cases/ieee39/ieee39_uced_vis.xlsx b/ams/cases/ieee39/ieee39_uced_vis.xlsx index 95475120..dfba4423 100644 Binary files a/ams/cases/ieee39/ieee39_uced_vis.xlsx and b/ams/cases/ieee39/ieee39_uced_vis.xlsx differ diff --git a/ams/cases/npcc/npcc_uced.xlsx b/ams/cases/npcc/npcc_uced.xlsx index 09cdf7af..11583ff7 100644 Binary files a/ams/cases/npcc/npcc_uced.xlsx and b/ams/cases/npcc/npcc_uced.xlsx differ diff --git a/ams/cases/wecc/wecc_uced.xlsx b/ams/cases/wecc/wecc_uced.xlsx index c2d3d54b..f27cdfd9 100644 Binary files a/ams/cases/wecc/wecc_uced.xlsx and b/ams/cases/wecc/wecc_uced.xlsx differ diff --git a/ams/core/model.py b/ams/core/model.py index 34fbabb3..77655cc1 100644 --- a/ams/core/model.py +++ b/ams/core/model.py @@ -212,17 +212,17 @@ def set(self, src, idx, attr, value): self.__dict__[src].__dict__[attr][uid] = value return True - def alter(self, src, idx, value): + def alter(self, src, idx, value, attr='v'): """ Alter values of input parameters or constant service. If the method operates on an input parameter, the new data should be in - the same base as that in the input file. This function will convert the - new value to per unit in the system base. + the same base as that in the input file. This function will convert + ``value`` to per unit in the system base whenever necessary. - The values for storing the input data, i.e., the ``vin`` field of the - parameter, will be overwritten, thus the update will be reflected in the - dumped case file. + The values for storing the input data, i.e., the parameter's ``vin`` + field, will be overwritten. As a result, altered values will be + reflected in the dumped case file. Parameters ---------- @@ -232,14 +232,31 @@ def alter(self, src, idx, value): The device to alter value : float The desired value + attr : str + The attribute to alter, default is 'v'. + + Notes + ----- + New in version 0.9.14: Added the signature `attr` to alter specific attributes. + This feature is useful when you need to manipulate parameter values in the system + base and ensure that these changes are reflected in the dumped case file. """ + instance = self.__dict__[src] if hasattr(instance, 'vin') and (instance.vin is not None): - self.set(src, idx, 'vin', value) - instance.v[:] = instance.vin * instance.pu_coeff - else: + uid = self.idx2uid(idx) + if attr == 'vin': + self.set(src, idx, 'vin', value / instance.pu_coeff[uid]) + self.set(src, idx, 'v', value=value) + else: + self.set(src, idx, 'vin', value) + self.set(src, idx, 'v', value * instance.pu_coeff[uid]) + elif not hasattr(instance, 'vin') and attr == 'vin': + logger.warning(f"{self.class_name}.{src} has no `vin` attribute, changing `v`.") self.set(src, idx, 'v', value) + else: + self.set(src, idx, attr=attr, value=value) def idx2uid(self, idx): """ diff --git a/ams/core/service.py b/ams/core/service.py index aa50a638..d016d6ab 100644 --- a/ams/core/service.py +++ b/ams/core/service.py @@ -221,7 +221,7 @@ def v(self): ue = self.u.owner.get(src='u', attr='v', idx=u_idx) u_bus = self.u.owner.get(src='bus', attr='v', idx=u_idx) u_zone = sys.Bus.get(src='zone', attr='v', idx=u_bus) - u_yloc = np.array(sys.Region.idx2uid(u_zone)) + u_yloc = np.array(sys.Zone.idx2uid(u_zone)) p0s = np.multiply(self.sd.v[:, u_yloc].transpose(), (ue * self.u.v)[:, np.newaxis]) if self.sparse: @@ -587,12 +587,12 @@ def v0(self): class ZonalSum(NumOp): """ Build zonal sum matrix for a vector in the shape of collection model, - ``Area`` or ``Region``. + ``Area`` or ``Zone``. The value array is in the shape of (nr, nc), where nr is the length of rid instance idx, and nc is the length of the cid value. In an IEEE-14 Bus system, we have the zonal definition by the - ``Region`` model. Suppose in it we have two regions, "ZONE1" and + ``Zone`` model. Suppose in it we have two regions, "ZONE1" and "ZONE2". Follwing it, we have a zonal SFR requirement model ``SFR`` that @@ -606,7 +606,7 @@ class ZonalSum(NumOp): In the `RTED` model, we have the Vars ``pru`` and ``prd`` in the shape of generators. - Then, the Region model has idx ['ZONE1', 'ZONE2'], and the ``gsm`` value + Then, the Zone model has idx ['ZONE1', 'ZONE2'], and the ``gsm`` value will be [[1, 1, 0, 0, 1], [0, 0, 1, 1, 0]]. Finally, the zonal reserve requirements can be formulated as @@ -621,7 +621,7 @@ class ZonalSum(NumOp): u : Callable Input. zone : str - Zonal model name, e.g., "Area" or "Region". + Zonal model name, e.g., "Area" or "Zone". name : str Instance name. tex_name : str diff --git a/ams/interface.py b/ams/interface.py index 6789051c..012ade1a 100644 --- a/ams/interface.py +++ b/ams/interface.py @@ -45,7 +45,7 @@ 'rea': 'RenAerodynamics', 'rep': 'RenPitch', 'busf': 'BusFreq', - 'zone': 'Region', + 'zone': 'Zone', 'gen': 'StaticGen', 'pq': 'PQ', } diff --git a/ams/io/matpower.py b/ams/io/matpower.py index 5e461e84..c1cf3364 100644 --- a/ams/io/matpower.py +++ b/ams/io/matpower.py @@ -231,11 +231,11 @@ def mpc2system(mpc: dict, system) -> bool: c2=c2, c1=c1, c0=c0 ) - # --- region --- + # --- Zone --- zone_id = np.unique(system.Bus.zone.v).astype(int) for zone in zone_id: zone_idx = f'ZONE_{zone}' - system.add('Region', idx=zone_idx, name=None) + system.add('Zone', idx=zone_idx, name=None) bus_zone = system.Bus.zone.v bus_zone = [f'ZONE_{int(zone)}' for zone in bus_zone] system.Bus.zone.v = bus_zone @@ -313,7 +313,7 @@ def system2mpc(system) -> dict: bus[:, 12] = system.Bus.vmin.v # --- zone --- - ZONE_I = system.Region.idx.v + ZONE_I = system.Zone.idx.v if len(ZONE_I) > 0: mapping = {busi0: i for i, busi0 in enumerate(ZONE_I)} bus[:, 10] = np.array([mapping[busi0] for busi0 in system.Bus.zone.v]) diff --git a/ams/models/__init__.py b/ams/models/__init__.py index 354bb2da..9a3fb377 100644 --- a/ams/models/__init__.py +++ b/ams/models/__init__.py @@ -14,7 +14,7 @@ ('distributed', ['PVD1', 'ESD1', 'EV1', 'EV2']), ('renewable', ['REGCA1', 'REGCV1', 'REGCV2']), ('area', ['Area']), - ('region', ['Region']), + ('zone', ['Zone']), ('reserve', ['SFR', 'SR', 'NSR', 'VSGR']), ('cost', ['GCost', 'SFRCost', 'SRCost', 'NSRCost', 'VSGCost']), ('cost', ['DCost']), diff --git a/ams/models/bus.py b/ams/models/bus.py index a9428cf5..69c5dff4 100644 --- a/ams/models/bus.py +++ b/ams/models/bus.py @@ -21,10 +21,10 @@ def __init__(self, system, config): Model.__init__(self, system, config) self.group = 'ACTopology' - # NOTE: in ANDES, self.zone is defined to trace a non-existing model "Region" + # NOTE: in ANDES, self.zone is defined to trace a non-existing model "Zone" # in AMS, model "Zone" is developed, # so we need to change the model name of IdxParam self.zone - self.zone.model = 'Region' + self.zone.model = 'Zone' self.type = NumParam(name='type', info='bus type, 1=PQ, 2=PV, 3=ref, 4=isolated (place holder)', diff --git a/ams/models/group.py b/ams/models/group.py index e762cf3a..70f398de 100644 --- a/ams/models/group.py +++ b/ams/models/group.py @@ -1,12 +1,12 @@ -import logging # NOQA +import logging -from andes.models.group import GroupBase as andes_GroupBase +from andes.models.group import GroupBase as adGroupBase from andes.core.service import BackRef logger = logging.getLogger(__name__) -class GroupBase(andes_GroupBase): +class GroupBase(adGroupBase): """ Base class for groups. diff --git a/ams/models/timeslot.py b/ams/models/timeslot.py index a0f3b451..605e1e3d 100644 --- a/ams/models/timeslot.py +++ b/ams/models/timeslot.py @@ -39,7 +39,7 @@ class EDTSlot(TimeSlot): `sd` is the zonal load scaling factor. Cells in `sd` should have `nz` values seperated by comma, - where `nz` is the number of `Region` in the system. + where `nz` is the number of `Zone` in the system. `ug` is the unit commitment decisions. Cells in `ug` should have `ng` values seperated by comma, @@ -62,7 +62,7 @@ class UCTSlot(TimeSlot): `sd` is the zonal load scaling factor. Cells in `sd` should have `nz` values seperated by comma, - where `nz` is the number of `Region` in the system. + where `nz` is the number of `Zone` in the system. """ def __init__(self, system=None, config=None): diff --git a/ams/models/region.py b/ams/models/zone.py similarity index 75% rename from ams/models/region.py rename to ams/models/zone.py index bfc87a88..d0b4da65 100644 --- a/ams/models/region.py +++ b/ams/models/zone.py @@ -9,24 +9,21 @@ logger = logging.getLogger(__name__) -class RegionData(ModelData): - def __init__(self): - super().__init__() - - -class Region(RegionData, Model): +class Zone(ModelData, Model): """ - Region model for zonal vars. + Zone model for zonal items. + + An ``area`` can have multiple zones. Notes ----- - 1. Region is a collection of buses. - 2. Model ``Region`` is not actually defined in ANDES. + 1. Zone is a collection of buses. + 2. Model ``Zone`` is not actually defined in ANDES. """ def __init__(self, system, config): - RegionData.__init__(self) + ModelData.__init__(self) Model.__init__(self, system, config) self.group = 'Collection' diff --git a/ams/opt/constraint.py b/ams/opt/constraint.py index 8ab5a508..6335a3ff 100644 --- a/ams/opt/constraint.py +++ b/ams/opt/constraint.py @@ -64,6 +64,12 @@ def __init__(self, self.dual = None self.code = None + def get_idx(self): + raise NotImplementedError + + def get_all_idxes(self): + raise NotImplementedError + @ensure_symbols def parse(self): """ diff --git a/ams/opt/exprcalc.py b/ams/opt/exprcalc.py index 430d8fef..5a27f7e5 100644 --- a/ams/opt/exprcalc.py +++ b/ams/opt/exprcalc.py @@ -35,23 +35,11 @@ def __init__(self, model: Optional[str] = None, src: Optional[str] = None, ): - OptzBase.__init__(self, name=name, info=info, unit=unit) + OptzBase.__init__(self, name=name, info=info, unit=unit, model=model) self.optz = None self.e_str = e_str self.code = None - self.model = model - self.owner = None self.src = src - self.is_group = False - - def get_idx(self): - if self.is_group: - return self.owner.get_idx() - elif self.owner is None: - logger.info(f'ExpressionCalc <{self.name}> has no owner.') - return None - else: - return self.owner.idx.v @ensure_symbols def parse(self): diff --git a/ams/opt/expression.py b/ams/opt/expression.py index d7d43c10..38706c19 100644 --- a/ams/opt/expression.py +++ b/ams/opt/expression.py @@ -57,26 +57,14 @@ def __init__(self, vtype: Optional[str] = float, horizon: Optional[str] = None, ): - OptzBase.__init__(self, name=name, info=info, unit=unit) + OptzBase.__init__(self, name=name, info=info, unit=unit, model=model) self.tex_name = tex_name self.e_str = e_str self.optz = None self.code = None - self.model = model - self.owner = None self.src = src - self.is_group = False self.horizon = horizon - def get_idx(self): - if self.is_group: - return self.owner.get_idx() - elif self.owner is None: - logger.info(f'ExpressionCalc <{self.name}> has no owner.') - return None - else: - return self.owner.idx.v - @ensure_symbols def parse(self): """ diff --git a/ams/opt/optbase.py b/ams/opt/optbase.py index 56940242..310de8eb 100644 --- a/ams/opt/optbase.py +++ b/ams/opt/optbase.py @@ -88,6 +88,7 @@ def __init__(self, name: Optional[str] = None, info: Optional[str] = None, unit: Optional[str] = None, + model: Optional[str] = None, ): self.om = None self.name = name @@ -97,6 +98,9 @@ def __init__(self, self.rtn = None self.optz = None # corresponding optimization element self.code = None + self.model = model # indicate if this element belongs to a model or group + self.owner = None # instance of the owner model or group + self.is_group = False @ensure_symbols def parse(self): @@ -153,3 +157,12 @@ def size(self): def __repr__(self): return f'{self.__class__.__name__}: {self.name}' + + def get_idx(self): + if self.is_group: + return self.owner.get_idx() + elif self.owner is None: + logger.info(f'{self.class_name} <{self.name}> has no owner.') + return None + else: + return self.owner.idx.v diff --git a/ams/opt/var.py b/ams/opt/var.py index ac13a8cc..8c7c03fc 100644 --- a/ams/opt/var.py +++ b/ams/opt/var.py @@ -115,11 +115,8 @@ def __init__(self, self.tex_name = tex_name if tex_name else name # variable internal index inside a model (assigned in run time) self.id = None - OptzBase.__init__(self, name=name, info=info, unit=unit) + OptzBase.__init__(self, name=name, info=info, unit=unit, model=model) self.src = src - self.is_group = False - self.model = model # indicate if this variable is a group variable - self.owner = None # instance of the owner model or group self.v0 = v0 self.horizon = horizon self._shape = shape @@ -166,15 +163,6 @@ def v(self, value): else: self.optz.value = value - def get_idx(self): - if self.is_group: - return self.owner.get_idx() - elif self.owner is None: - logger.info(f'Variable <{self.name}> has no owner.') - return None - else: - return self.owner.idx.v - @ensure_symbols def parse(self): """ diff --git a/ams/report.py b/ams/report.py index b0f7adb0..8aa6695e 100644 --- a/ams/report.py +++ b/ams/report.py @@ -61,7 +61,7 @@ def update(self): 'Lines': system.Line.n, 'Transformers': np.count_nonzero(system.Line.trans.v == 1), 'Areas': system.Area.n, - 'Regions': system.Region.n, + 'Zones': system.Zone.n, }) def collect(self, rtn, horizon=None): @@ -324,7 +324,7 @@ def collect_owners(rtn): # initialize data section by model owners_all = ['Bus', 'Line', 'StaticGen', 'PV', 'Slack', 'RenGen', - 'DG', 'ESD1', 'PVD1', + 'DG', 'ESD1', 'PVD1', 'VSG', 'StaticLoad'] # Filter owners that exist in the system diff --git a/ams/routines/ed.py b/ams/routines/ed.py index e7e5db40..4aa2affa 100644 --- a/ams/routines/ed.py +++ b/ams/routines/ed.py @@ -101,6 +101,8 @@ def __init__(self) -> None: self.pg.info = '2D Gen power' self.aBus.horizon = self.timeslot self.aBus.info = '2D Bus angle' + self.vBus.horizon = self.timeslot + self.vBus.info = '2D Bus voltage' self.pi.horizon = self.timeslot diff --git a/ams/routines/routine.py b/ams/routines/routine.py index d58dd175..e4c6e66f 100644 --- a/ams/routines/routine.py +++ b/ams/routines/routine.py @@ -1028,6 +1028,6 @@ def collect_data(rtn: RoutineBase, data_dict: Dict, items: Dict, attr: str): data_v = rtn.get(src=key, attr=attr, idx=idx_v, horizon=rtn.timeslot.v if hasattr(rtn, 'timeslot') else None).round(6) except Exception as e: - logger.error(f"Error collecting data for '{key}': {e}") + logger.debug(f"Error with collecting data for '{key}': {e}") data_v = [np.nan] * len(idx_v) data_dict.update(OrderedDict(zip([f'{key} {dev}' for dev in idx_v], data_v))) diff --git a/ams/routines/rted.py b/ams/routines/rted.py index 37601fb4..908a93b9 100644 --- a/ams/routines/rted.py +++ b/ams/routines/rted.py @@ -20,7 +20,7 @@ class RTEDBase: """ def __init__(self): - # --- region --- + # --- zone --- self.zg = RParam(info='Gen zone', name='zg', tex_name='z_{one,g}', model='StaticGen', src='zone', @@ -29,11 +29,11 @@ def __init__(self): name='zd', tex_name='z_{one,d}', model='StaticLoad', src='zone', no_parse=True) - self.gs = ZonalSum(u=self.zg, zone='Region', + self.gs = ZonalSum(u=self.zg, zone='Zone', name='gs', tex_name=r'S_{g}', info='Sum Gen vars vector in shape of zone', no_parse=True, sparse=True) - self.ds = ZonalSum(u=self.zd, zone='Region', + self.ds = ZonalSum(u=self.zd, zone='Zone', name='ds', tex_name=r'S_{d}', info='Sum pd vector in shape of zone', no_parse=True,) @@ -215,19 +215,11 @@ def dc2ac(self, kloss=1.0, **kwargs): self.system.StaticLoad.set(src='q0', idx=pq_idx, attr='v', value=qd0) if not ACOPF.exit_code == 0: logger.warning(' did not converge, conversion failed.') - # NOTE: mock results to fit interface with ANDES - self.vBus = ACOPF.vBus self.vBus.optz.value = np.ones(self.system.Bus.n) self.aBus.optz.value = np.zeros(self.system.Bus.n) return False - self.pg.optz.value = ACOPF.pg.v - # NOTE: mock results to fit interface with ANDES - self.addVars(name='vBus', - info='Bus voltage', unit='p.u.', - model='Bus', src='v',) - self.vBus.parse() - self.vBus.evaluate() + self.pg.optz.value = ACOPF.pg.v self.vBus.optz.value = ACOPF.vBus.v self.aBus.optz.value = ACOPF.aBus.v self.exec_time = exec_time @@ -243,54 +235,6 @@ def dc2ac(self, kloss=1.0, **kwargs): logger.warning(f'<{self.class_name}> converted to AC.') return True - def run(self, no_code=True, **kwargs): - """ - Run the routine. - - Parameters - ---------- - no_code : bool, optional - If True, print the generated CVXPY code. Defaults to False. - - Other Parameters - ---------------- - solver: str, optional - The solver to use. For example, 'GUROBI', 'ECOS', 'SCS', or 'OSQP'. - verbose : bool, optional - Overrides the default of hiding solver output and prints logging - information describing CVXPY's compilation process. - gp : bool, optional - If True, parses the problem as a disciplined geometric program - instead of a disciplined convex program. - qcp : bool, optional - If True, parses the problem as a disciplined quasiconvex program - instead of a disciplined convex program. - requires_grad : bool, optional - Makes it possible to compute gradients of a solution with respect to Parameters - by calling problem.backward() after solving, or to compute perturbations to the variables - given perturbations to Parameters by calling problem.derivative(). - Gradients are only supported for DCP and DGP problems, not quasiconvex problems. - When computing gradients (i.e., when this argument is True), the problem must satisfy the DPP rules. - enforce_dpp : bool, optional - When True, a DPPError will be thrown when trying to solve a - non-DPP problem (instead of just a warning). - Only relevant for problems involving Parameters. Defaults to False. - ignore_dpp : bool, optional - When True, DPP problems will be treated as non-DPP, which may speed up compilation. Defaults to False. - method : function, optional - A custom solve method to use. - kwargs : keywords, optional - Additional solver specific arguments. See CVXPY documentation for details. - - Notes - ----- - 1. remove ``vBus`` if has been converted with ``dc2ac`` - """ - if self.converted: - delattr(self, 'vBus') - self.converted = False - return super().run(**kwargs) - class DGBase: """ @@ -523,7 +467,7 @@ def __init__(self) -> None: model='VSG', src='D', nonneg=True,) - self.gvsg = ZonalSum(u=self.zvsg, zone='Region', + self.gvsg = ZonalSum(u=self.zvsg, zone='Zone', name='gvsg', tex_name=r'S_{g}', info='Sum VSG vars vector in shape of zone', no_parse=True) diff --git a/docs/source/modeling/routine.rst b/docs/source/modeling/routine.rst index a257bebe..28082831 100644 --- a/docs/source/modeling/routine.rst +++ b/docs/source/modeling/routine.rst @@ -19,7 +19,7 @@ A simplified code snippet for RTED is shown below as an example. name='R10', tex_name=r'R_{10}', model='StaticGen', src='R10', unit='p.u./h',) - self.gs = ZonalSum(u=self.zg, zone='Region', + self.gs = ZonalSum(u=self.zg, zone='Zone', name='gs', tex_name=r'S_{g}', info='Sum Gen vars vector in shape of zone', no_parse=True, sparse=True) diff --git a/docs/source/release-notes.rst b/docs/source/release-notes.rst index 933ac41d..6644f441 100644 --- a/docs/source/release-notes.rst +++ b/docs/source/release-notes.rst @@ -9,6 +9,14 @@ The APIs before v3.0.0 are in beta and may change without prior notice. Pre-v1.0.0 ========== +v0.9.14 (2024-xx-xx) +-------------------- + +- **Breaking Change**: rename model ``Region`` to ``Zone`` for clarity. Prior case + files without modification can run into error. +- Fix bugs in ``RTED.dc2ac`` +- Minor refacot ``OptzBase.get_idx`` to reduce duplication + v0.9.13 (2024-12-05) -------------------- diff --git a/examples/ex6.ipynb b/examples/ex6.ipynb index 737a41d5..5034a77f 100644 --- a/examples/ex6.ipynb +++ b/examples/ex6.ipynb @@ -92,19 +92,21 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## Reginonal Design" + "## Zonal Design" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "The disaptch models in AMS has develoepd with regional structure, and it can be inspected in device ``Region``." + "The scheduling models in AMS has develoepd with zonal structure, and it can be inspected in device ``Zone``.\n", + "\n", + "**Note**: Since version 0.9.14, model ``Region`` is renamed to ``Zone`` for clarity." ] }, { "cell_type": "code", - "execution_count": 5, + "execution_count": null, "metadata": {}, "outputs": [ { @@ -169,7 +171,7 @@ } ], "source": [ - "sp.Region.as_df()" + "sp.Zone.as_df()" ] }, { diff --git a/tests/test_case.py b/tests/test_case.py index 6f65d5f2..382da734 100644 --- a/tests/test_case.py +++ b/tests/test_case.py @@ -34,7 +34,7 @@ def test_essential(self): self.assertEqual(self.ss.PV.n, 3) self.assertEqual(self.ss.Slack.n, 1) self.assertEqual(self.ss.Line.n, 7) - self.assertEqual(self.ss.Region.n, 2) + self.assertEqual(self.ss.Zone.n, 2) self.assertEqual(self.ss.SFR.n, 2) self.assertEqual(self.ss.SR.n, 2) self.assertEqual(self.ss.NSR.n, 2) diff --git a/tests/test_group.py b/tests/test_group.py index e9deee7a..fa1d3d7e 100644 --- a/tests/test_group.py +++ b/tests/test_group.py @@ -74,3 +74,10 @@ def test_group_access(self): # --- get group idx --- self.assertListEqual(ss.DG.get_idx(), ss.ESD1.idx.v) + + def test_group_repr(self): + """ + Test `Group.__repr__()` method. + """ + for grp in self.ss.groups.items(): + print(grp.__repr__()) diff --git a/tests/test_matp.py b/tests/test_matp.py index 93ae31a3..0f1b9999 100644 --- a/tests/test_matp.py +++ b/tests/test_matp.py @@ -20,7 +20,7 @@ class TestMatProcessorBasic(unittest.TestCase): def setUp(self) -> None: self.ss = ams.load(ams.get_case("matpower/case300.m"), default_config=True, no_output=True) - self.nR = self.ss.Region.n + self.nR = self.ss.Zone.n self.nb = self.ss.Bus.n self.nl = self.ss.Line.n self.ng = self.ss.StaticGen.n diff --git a/tests/test_model.py b/tests/test_model.py index 577908e7..d79e7bbf 100644 --- a/tests/test_model.py +++ b/tests/test_model.py @@ -9,37 +9,46 @@ class TestModelMethods(unittest.TestCase): Test methods of Model. """ - def test_model_set(self): - """ - Test `Model.set()` method. - """ - - ss = ams.run( + def setUp(self): + self.ss = ams.run( ams.get_case("ieee14/ieee14.json"), default_config=True, no_output=True, ) + def test_model_set(self): + """ + Test `Model.set()` method. + """ + # set a single value - ss.PQ.set("p0", "PQ_1", "v", 0.25) - self.assertEqual(ss.PQ.p0.v[0], 0.25) + self.ss.PQ.set("p0", "PQ_1", "v", 0.25) + self.assertEqual(self.ss.PQ.p0.v[0], 0.25) # set a list of values - ss.PQ.set("p0", ["PQ_1", "PQ_2"], "v", [0.26, 0.51]) - np.testing.assert_equal(ss.PQ.p0.v[[0, 1]], [0.26, 0.51]) + self.ss.PQ.set("p0", ["PQ_1", "PQ_2"], "v", [0.26, 0.51]) + np.testing.assert_equal(self.ss.PQ.p0.v[[0, 1]], [0.26, 0.51]) # set a list of values - ss.PQ.set("p0", ["PQ_3", "PQ_5"], "v", [0.52, 0.16]) - np.testing.assert_equal(ss.PQ.p0.v[[2, 4]], [0.52, 0.16]) + self.ss.PQ.set("p0", ["PQ_3", "PQ_5"], "v", [0.52, 0.16]) + np.testing.assert_equal(self.ss.PQ.p0.v[[2, 4]], [0.52, 0.16]) # set a list of idxes with a single element to an array of values - ss.PQ.set("p0", ["PQ_4"], "v", np.array([0.086])) - np.testing.assert_equal(ss.PQ.p0.v[3], 0.086) + self.ss.PQ.set("p0", ["PQ_4"], "v", np.array([0.086])) + np.testing.assert_equal(self.ss.PQ.p0.v[3], 0.086) # set an array of idxes with a single element to an array of values - ss.PQ.set("p0", np.array(["PQ_4"]), "v", np.array([0.096])) - np.testing.assert_equal(ss.PQ.p0.v[3], 0.096) + self.ss.PQ.set("p0", np.array(["PQ_4"]), "v", np.array([0.096])) + np.testing.assert_equal(self.ss.PQ.p0.v[3], 0.096) # set an array of idxes with a list of single value - ss.PQ.set("p0", np.array(["PQ_4"]), "v", 0.097) - np.testing.assert_equal(ss.PQ.p0.v[3], 0.097) + self.ss.PQ.set("p0", np.array(["PQ_4"]), "v", 0.097) + np.testing.assert_equal(self.ss.PQ.p0.v[3], 0.097) + + def test_model_repr(self): + """ + Test `Model.__repr__()` method. + """ + + for mdl in self.ss.models.items(): + print(mdl.__repr__()) diff --git a/tests/test_service.py b/tests/test_service.py index 33d4030c..284e6152 100644 --- a/tests/test_service.py +++ b/tests/test_service.py @@ -15,7 +15,7 @@ def setUp(self) -> None: self.ss = ams.load(ams.get_case("ieee39/ieee39_uced_esd1.xlsx"), default_config=True, no_output=True,) - self.nR = self.ss.Region.n + self.nR = self.ss.Zone.n self.nB = self.ss.Bus.n self.nL = self.ss.Line.n self.nD = self.ss.StaticLoad.n # number of static loads @@ -63,7 +63,7 @@ def test_ZonalSum(self): """ Test `ZonalSum`. """ - ds = ZonalSum(u=self.ss.RTED.zd, zone="Region", + ds = ZonalSum(u=self.ss.RTED.zd, zone="Zone", name="ds", tex_name=r"S_{d}", info="Sum pl vector in shape of zone",) ds.rtn = self.ss.RTED