From 0854a79585ef1221386d1d2c441ed0f1a0b74450 Mon Sep 17 00:00:00 2001 From: Muhammad Yasirroni Date: Fri, 16 Jan 2026 19:06:18 +1100 Subject: [PATCH 01/17] clean test and notebook ReservesFrames --- data/ex_case3a.m | 75 ++ matpowercaseframes/__init__.py | 2 +- matpowercaseframes/core.py | 2 + notebooks/load_most_ex_case3a.ipynb | 920 +----------------- tests/results/case118/branch.csv | 2 +- tests/results/case118/gen.csv | 2 +- tests/results/case118/gencost.csv | 2 +- tests/results/case118/schema/branch.csv | 2 +- tests/results/case118/schema/gen.csv | 2 +- tests/results/case118/schema/gencost.csv | 2 +- .../case118_prefix_suffix/mpc.branch_test.csv | 2 +- .../case118_prefix_suffix/mpc.gen_test.csv | 2 +- .../mpc.gencost_test.csv | 2 +- tests/results/case9/branch.csv | 2 +- tests/results/case9/bus.csv | 2 +- tests/results/case9/gen.csv | 2 +- tests/results/case9/gencost.csv | 2 +- tests/results/case9/schema/branch.csv | 2 +- tests/results/case9/schema/bus.csv | 2 +- tests/results/case9/schema/gen.csv | 2 +- tests/results/case9/schema/gencost.csv | 2 +- .../case9_prefix_suffix/mpc.branch_test.csv | 2 +- .../case9_prefix_suffix/mpc.bus_test.csv | 2 +- .../case9_prefix_suffix/mpc.gen_test.csv | 2 +- .../case9_prefix_suffix/mpc.gencost_test.csv | 2 +- tests/test_read_matpower_cases.py | 43 +- 26 files changed, 147 insertions(+), 937 deletions(-) create mode 100644 data/ex_case3a.m diff --git a/data/ex_case3a.m b/data/ex_case3a.m new file mode 100644 index 0000000..8bdbf11 --- /dev/null +++ b/data/ex_case3a.m @@ -0,0 +1,75 @@ +function mpc = ex_case3a +% ex_case3a - Three bus example system. +% +% Please see caseformat for details on the case file format. + +% MOST +% Copyright (c) 2015-2024, Power Systems Engineering Research Center (PSERC) +% by Ray Zimmerman, PSERC Cornell +% +% This file is part of MOST. +% Covered by the 3-clause BSD License (see LICENSE file for details). +% See https://github.com/MATPOWER/most for more info. + +%% MATPOWER Case Format : Version 2 +mpc.version = '2'; + +%%----- Power Flow Data -----%% +%% system MVA base +mpc.baseMVA = 100; + +%% bus data +% bus_i type Pd Qd Gs Bs area Vm Va baseKV zone Vmax Vmin +mpc.bus = [ + 1 3 0 0 0 0 1 1 0 135 1 1.05 0.95; + 2 2 0 0 0 0 1 1 0 135 1 1.05 0.95; + 3 2 0 0 0 0 1 1 0 135 1 1.05 0.95; +]; + +%% generator data +% bus Pg Qg Qmax Qmin Vg mBase status Pmax Pmin Pc1 Pc2 Qc1min Qc1max Qc2min Qc2max ramp_agc ramp_10 ramp_30 ramp_q apf +mpc.gen = [ + 1 125 0 25 -25 1 100 1 200 0 0 0 0 0 0 0 0 250 250 0 0; + 1 125 0 25 -25 1 100 1 200 0 0 0 0 0 0 0 0 250 250 0 0; + 2 200 0 50 -50 1 100 1 500 0 0 0 0 0 0 0 0 600 600 0 0; + 3 -450 0 0 0 1 100 1 0 -450 0 0 0 0 0 0 0 500 500 0 0; +]; + +%% branch data +% fbus tbus r x b rateA rateB rateC ratio angle status angmin angmax +mpc.branch = [ + 1 2 0.005 0.01 0 300 300 300 0 0 1 -360 360; + 1 3 0.005 0.01 0 240 240 240 0 0 1 -360 360; + 2 3 0.005 0.01 0 300 300 300 0 0 1 -360 360; +]; + +%%----- OPF Data -----%% +%% generator cost data +% 1 startup shutdown n x1 y1 ... xn yn +% 2 startup shutdown n c(n-1) ... c0 +mpc.gencost = [ + 2 0 0 3 0.1 0 0; + 2 0 0 3 0.1 0 0; + 2 0 0 3 0.1 0 0; + 2 0 0 3 0 1000 0; +]; + +%%----- Reserve Data -----%% +%% reserve zones, element i, j is 1 if gen j is in zone i, 0 otherwise +mpc.reserves.zones = [ + 1 1 1 0; +]; + +%% reserve requirements for each zone in MW +mpc.reserves.req = 150; + +%% reserve costs in $/MW for each gen that belongs to at least 1 zone +%% (same order as gens, but skipping any gen that does not belong to any zone) +% mpc.reserves.cost = [ 5; 5; 21; ]; +% mpc.reserves.cost = [ 5; 5; 16.25; ]; +% mpc.reserves.cost = [ 0; 0; 11.25; ]; +mpc.reserves.cost = [ 1; 3; 5; ]; + +%% OPTIONAL max reserve quantities for each gen that belongs to at least 1 zone +%% (same order as gens, but skipping any gen that does not belong to any zone) +mpc.reserves.qty = [ 100; 100; 200; ]; diff --git a/matpowercaseframes/__init__.py b/matpowercaseframes/__init__.py index bccb7e7..2564f78 100644 --- a/matpowercaseframes/__init__.py +++ b/matpowercaseframes/__init__.py @@ -1,2 +1,2 @@ -from .core import CaseFrames # noqa: F401 +from .core import CaseFrames, ReservesFrames # noqa: F401 from .version import __version__ # noqa: F401 diff --git a/matpowercaseframes/core.py b/matpowercaseframes/core.py index 11f5517..c48ea12 100644 --- a/matpowercaseframes/core.py +++ b/matpowercaseframes/core.py @@ -820,6 +820,7 @@ def to_dict(self): Returns: dict: Dictionary with attribute names as keys and their data as values. """ + # TODO: support mpc = cf.to_dict() with reserves data data = { "version": getattr(self, "version", None), "baseMVA": getattr(self, "baseMVA", None), @@ -844,6 +845,7 @@ def to_mpc(self): Returns: dict: MATPOWER-compatible dictionary with data. """ + # TODO: support mpc = cf.to_mpc() with reserves data return self.to_dict() def to_schema(self, path, prefix="", suffix=""): diff --git a/notebooks/load_most_ex_case3a.ipynb b/notebooks/load_most_ex_case3a.ipynb index 126ee8e..d167134 100644 --- a/notebooks/load_most_ex_case3a.ipynb +++ b/notebooks/load_most_ex_case3a.ipynb @@ -60,7 +60,6 @@ "source": [ "import os\n", "\n", - "import pandas as pd\n", "from matpower import path_matpower, start_instance\n", "\n", "from matpowercaseframes import CaseFrames" @@ -88,858 +87,7 @@ }, { "cell_type": "code", - "execution_count": 6, - "id": "6d93dd06", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'version': '2',\n", - " 'baseMVA': 100.0,\n", - " 'bus': array([[ 1. , 3. , 0. , 0. , 0. , 0. , 1. , 1. ,\n", - " 0. , 135. , 1. , 1.05, 0.95],\n", - " [ 2. , 2. , 0. , 0. , 0. , 0. , 1. , 1. ,\n", - " 0. , 135. , 1. , 1.05, 0.95],\n", - " [ 3. , 2. , 0. , 0. , 0. , 0. , 1. , 1. ,\n", - " 0. , 135. , 1. , 1.05, 0.95]]),\n", - " 'gen': array([[ 1., 125., 0., 25., -25., 1., 100., 1., 200.,\n", - " 0., 0., 0., 0., 0., 0., 0., 0., 250.,\n", - " 250., 0., 0.],\n", - " [ 1., 125., 0., 25., -25., 1., 100., 1., 200.,\n", - " 0., 0., 0., 0., 0., 0., 0., 0., 250.,\n", - " 250., 0., 0.],\n", - " [ 2., 200., 0., 50., -50., 1., 100., 1., 500.,\n", - " 0., 0., 0., 0., 0., 0., 0., 0., 600.,\n", - " 600., 0., 0.],\n", - " [ 3., -450., 0., 0., 0., 1., 100., 1., 0.,\n", - " -450., 0., 0., 0., 0., 0., 0., 0., 500.,\n", - " 500., 0., 0.]]),\n", - " 'branch': array([[ 1.0e+00, 2.0e+00, 5.0e-03, 1.0e-02, 0.0e+00, 3.0e+02,\n", - " 3.0e+02, 3.0e+02, 0.0e+00, 0.0e+00, 1.0e+00, -3.6e+02,\n", - " 3.6e+02],\n", - " [ 1.0e+00, 3.0e+00, 5.0e-03, 1.0e-02, 0.0e+00, 2.4e+02,\n", - " 2.4e+02, 2.4e+02, 0.0e+00, 0.0e+00, 1.0e+00, -3.6e+02,\n", - " 3.6e+02],\n", - " [ 2.0e+00, 3.0e+00, 5.0e-03, 1.0e-02, 0.0e+00, 3.0e+02,\n", - " 3.0e+02, 3.0e+02, 0.0e+00, 0.0e+00, 1.0e+00, -3.6e+02,\n", - " 3.6e+02]]),\n", - " 'gencost': array([[2.e+00, 0.e+00, 0.e+00, 3.e+00, 1.e-01, 0.e+00, 0.e+00],\n", - " [2.e+00, 0.e+00, 0.e+00, 3.e+00, 1.e-01, 0.e+00, 0.e+00],\n", - " [2.e+00, 0.e+00, 0.e+00, 3.e+00, 1.e-01, 0.e+00, 0.e+00],\n", - " [2.e+00, 0.e+00, 0.e+00, 3.e+00, 0.e+00, 1.e+03, 0.e+00]]),\n", - " 'reserves': {'zones': array([[1., 1., 1., 0.]]),\n", - " 'req': 150.0,\n", - " 'cost': array([[1.],\n", - " [3.],\n", - " [5.]]),\n", - " 'qty': array([[100.],\n", - " [100.],\n", - " [200.]])}}" - ] - }, - "execution_count": 6, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "# load single most example case\n", - "mpc = m.loadcase(os.path.join(path_most_ex_cases, \"ex_case3a.m\"))\n", - "mpc" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "id": "b6bbbce4", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "dict_keys(['version', 'baseMVA', 'bus', 'gen', 'branch', 'gencost', 'reserves'])" - ] - }, - "execution_count": 7, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "mpc.keys()" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "id": "ea66211a", - "metadata": {}, - "outputs": [], - "source": [ - "from matpowercaseframes.constants import COLUMNS" - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "id": "a911808f", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "dict_keys(['bus', 'gen', 'branch', 'dcline', 'if', 'gencost', 'dclinecost', 'bus_name', 'branch_name', 'gen_name', 'case'])" - ] - }, - "execution_count": 9, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "COLUMNS.keys()" - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "id": "f63470e2", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'baseMVA', 'reserves', 'version'}" - ] - }, - "execution_count": 10, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "mpc_keys = set(mpc.keys())\n", - "columns_keys = set(COLUMNS.keys())\n", - "missing_keys = mpc_keys - columns_keys\n", - "missing_keys" - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "id": "8577fe89", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'zones': array([[1., 1., 1., 0.]]),\n", - " 'req': 150.0,\n", - " 'cost': array([[1.],\n", - " [3.],\n", - " [5.]]),\n", - " 'qty': array([[100.],\n", - " [100.],\n", - " [200.]])}" - ] - }, - "execution_count": 11, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "mpc.reserves" - ] - }, - { - "cell_type": "markdown", - "id": "f46abaea", - "metadata": {}, - "source": [ - "Reserve Data\n", - "\n", - "reserve zones, element i, j is 1 if gen j is in zone i, 0 otherwise.\n", - "dimension (zone, gen)\n", - "\n", - "```octave\n", - "mpc.reserves.zones = [\n", - "\t1\t1\t1\t0;\n", - "];\n", - "```\n", - "\n", - "Mapped to `DataFrame` with index follows zone index (from 1), and columns follows\n", - "generator index (cf.gen.index)." - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "id": "aaf701d3", - "metadata": {}, - "outputs": [], - "source": [ - "n_zones, n_gens = mpc.reserves.zones.shape" - ] - }, - { - "cell_type": "code", - "execution_count": 13, - "id": "370d5e7c", - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
gen1234
zone
11.01.01.00.0
\n", - "
" - ], - "text/plain": [ - "gen 1 2 3 4\n", - "zone \n", - "1 1.0 1.0 1.0 0.0" - ] - }, - "execution_count": 13, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "df_reserves_zone = pd.DataFrame(\n", - " mpc.reserves.zones,\n", - " index=pd.RangeIndex(start=1, stop=n_zones + 1, name=\"zone\"),\n", - " columns=pd.RangeIndex(start=1, stop=n_gens + 1, name=\"gen\"),\n", - ")\n", - "df_reserves_zone" - ] - }, - { - "cell_type": "markdown", - "id": "ded7b892", - "metadata": {}, - "source": [ - "reserve requirements for each zone in MW\n", - "\n", - "```octave\n", - "mpc.reserves.req = [60; 20];\n", - "```\n", - "\n", - "or\n", - "\n", - "```octave\n", - "mpc.reserves.req = 150;\n", - "```\n", - "\n", - "Mapped to `DataFrame` with index follows zone index (from 1), and single column 'PREQ'." - ] - }, - { - "cell_type": "code", - "execution_count": 14, - "id": "b817c54f", - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
PREQ
zone
1150.0
\n", - "
" - ], - "text/plain": [ - " PREQ\n", - "zone \n", - "1 150.0" - ] - }, - "execution_count": 14, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "df_reserves_req = pd.DataFrame(\n", - " mpc.reserves.req,\n", - " index=pd.RangeIndex(start=1, stop=n_zones + 1, name=\"zone\"),\n", - " columns=[\"PREQ\"],\n", - ")\n", - "df_reserves_req" - ] - }, - { - "cell_type": "markdown", - "id": "82107817", - "metadata": {}, - "source": [ - "reserve costs in $/MW for each gen that belongs to at least 1 zone. (same order as gens,\n", - "but skipping any gen that does not belong to any zone)\n", - "\n", - "```octave\n", - "mpc.reserves.cost = [\t1.9;\t2;\t3;\t4;\t5;\t5.5\t];\n", - "```" - ] - }, - { - "cell_type": "code", - "execution_count": 15, - "id": "fb21239e", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "RangeIndex(start=1, stop=4, step=1, name='gen')" - ] - }, - "execution_count": 15, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "df_reserves_zone_sum = df_reserves_zone.sum(axis=0)\n", - "idx_gen_with_reserves = df_reserves_zone_sum[df_reserves_zone_sum > 0].index\n", - "idx_gen_with_reserves" - ] - }, - { - "cell_type": "code", - "execution_count": 16, - "id": "ebe8a6de", - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
C1
gen
11.0
23.0
35.0
\n", - "
" - ], - "text/plain": [ - " C1\n", - "gen \n", - "1 1.0\n", - "2 3.0\n", - "3 5.0" - ] - }, - "execution_count": 16, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "df_reserves_cost = pd.DataFrame(\n", - " mpc.reserves.cost, index=idx_gen_with_reserves, columns=[\"C1\"]\n", - ")\n", - "df_reserves_cost" - ] - }, - { - "cell_type": "markdown", - "id": "a2b1e889", - "metadata": {}, - "source": [ - "max reserve quantities for each gen that belongs to at least 1 zone. (same order as\n", - "gens, but skipping any gen that does not belong to any zone)\n", - "\n", - "```octave\n", - "mpc.reserves.qty = [\t25;\t25;\t25;\t25;\t25;\t25\t];\n", - "```" - ] - }, - { - "cell_type": "code", - "execution_count": 17, - "id": "4e486ee1", - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
PQTY
gen
1100.0
2100.0
3200.0
\n", - "
" - ], - "text/plain": [ - " PQTY\n", - "gen \n", - "1 100.0\n", - "2 100.0\n", - "3 200.0" - ] - }, - "execution_count": 17, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "df_reserves_qty = pd.DataFrame(\n", - " mpc.reserves.qty, index=idx_gen_with_reserves, columns=[\"PQTY\"]\n", - ")\n", - "df_reserves_qty" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "5389d132", - "metadata": {}, - "outputs": [], - "source": [ - "def reserves_data_to_dataframes(reserves):\n", - " \"\"\"\n", - " Convert all mpc.reserves data to DataFrames.\n", - "\n", - " Args:\n", - " reserves: mpc.reserves object from MATPOWER\n", - "\n", - " Returns:\n", - " Dictionary containing:\n", - " - 'zones': Reserve zones DataFrame\n", - " - 'req': Reserve requirements DataFrame\n", - " - 'cost': Reserve costs DataFrame (if exists)\n", - " - 'qty': Reserve quantities DataFrame (if exists)\n", - " \"\"\"\n", - " dfs = {}\n", - " n_zones, n_gens = reserves.zones.shape\n", - " dfs[\"zones\"] = pd.DataFrame(\n", - " reserves.zones,\n", - " index=pd.RangeIndex(start=1, stop=n_zones + 1, name=\"zone\"),\n", - " columns=pd.RangeIndex(start=1, stop=n_gens + 1, name=\"gen\"),\n", - " )\n", - " zone_sum = dfs[\"zones\"].sum(axis=0)\n", - " idx_gen_with_reserves = zone_sum[zone_sum > 0].index\n", - " dfs[\"req\"] = pd.DataFrame(\n", - " reserves.req,\n", - " index=pd.RangeIndex(start=1, stop=n_zones + 1, name=\"zone\"),\n", - " columns=[\"PREQ\"],\n", - " )\n", - " if hasattr(reserves, \"cost\"):\n", - " dfs[\"cost\"] = pd.DataFrame(\n", - " reserves.cost, index=idx_gen_with_reserves, columns=[\"C1\"]\n", - " )\n", - " if hasattr(reserves, \"qty\"):\n", - " dfs[\"qty\"] = pd.DataFrame(\n", - " reserves.qty, index=idx_gen_with_reserves, columns=[\"PQTY\"]\n", - " )\n", - "\n", - " return dfs" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "a686e0ab", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "zones\n" - ] - }, - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
gen123456
zone
11.01.01.00.00.00.0
\n", - "
" - ], - "text/plain": [ - "gen 1 2 3 4 5 6\n", - "zone \n", - "1 1.0 1.0 1.0 0.0 0.0 0.0" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "req\n" - ] - }, - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
PREQ
zone
1150.0
\n", - "
" - ], - "text/plain": [ - " PREQ\n", - "zone \n", - "1 150.0" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "cost\n" - ] - }, - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
C1
gen
11.0
23.0
35.0
\n", - "
" - ], - "text/plain": [ - " C1\n", - "gen \n", - "1 1.0\n", - "2 3.0\n", - "3 5.0" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "qty\n" - ] - }, - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
PQTY
gen
1100.0
2100.0
3200.0
\n", - "
" - ], - "text/plain": [ - " PQTY\n", - "gen \n", - "1 100.0\n", - "2 100.0\n", - "3 200.0" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "dfs = reserves_data_to_dataframes(mpc.reserves)\n", - "for key, value in dfs.items():\n", - " print(key)\n", - " display(value)" - ] - }, - { - "cell_type": "code", - "execution_count": 10, + "execution_count": null, "id": "87bc9631", "metadata": {}, "outputs": [ @@ -1194,69 +342,13 @@ }, { "cell_type": "code", - "execution_count": 11, - "id": "71fd2c3b", + "execution_count": 13, + "id": "c96fa0ff", "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
gen1234
zone
11.01.01.00.0
\n", - "
" - ], - "text/plain": [ - "gen 1 2 3 4\n", - "zone \n", - "1 1.0 1.0 1.0 0.0" - ] - }, - "execution_count": 11, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ - "cf.reserves.zones" + "# TODO: support mpc = cf.to_mpc() with reserves data\n", + "mpc = m.loadcase(os.path.join(path_most_ex_cases, \"ex_case3a.m\"))" ] }, { diff --git a/tests/results/case118/branch.csv b/tests/results/case118/branch.csv index 7eb9ef8..e9fa9fd 100644 --- a/tests/results/case118/branch.csv +++ b/tests/results/case118/branch.csv @@ -1,4 +1,4 @@ -,F_BUS,T_BUS,BR_R,BR_X,BR_B,RATE_A,RATE_B,RATE_C,TAP,SHIFT,BR_STATUS,ANGMIN,ANGMAX +branch,F_BUS,T_BUS,BR_R,BR_X,BR_B,RATE_A,RATE_B,RATE_C,TAP,SHIFT,BR_STATUS,ANGMIN,ANGMAX 1,1.0,2.0,0.0303,0.0999,0.0254,0.0,0.0,0.0,0.0,0.0,1.0,-360.0,360.0 2,1.0,3.0,0.0129,0.0424,0.01082,0.0,0.0,0.0,0.0,0.0,1.0,-360.0,360.0 3,4.0,5.0,0.00176,0.00798,0.0021,0.0,0.0,0.0,0.0,0.0,1.0,-360.0,360.0 diff --git a/tests/results/case118/gen.csv b/tests/results/case118/gen.csv index 9479aa1..05e45df 100644 --- a/tests/results/case118/gen.csv +++ b/tests/results/case118/gen.csv @@ -1,4 +1,4 @@ -,GEN_BUS,PG,QG,QMAX,QMIN,VG,MBASE,GEN_STATUS,PMAX,PMIN,PC1,PC2,QC1MIN,QC1MAX,QC2MIN,QC2MAX,RAMP_AGC,RAMP_10,RAMP_30,RAMP_Q,APF +gen,GEN_BUS,PG,QG,QMAX,QMIN,VG,MBASE,GEN_STATUS,PMAX,PMIN,PC1,PC2,QC1MIN,QC1MAX,QC2MIN,QC2MAX,RAMP_AGC,RAMP_10,RAMP_30,RAMP_Q,APF 1,1.0,0.0,0.0,15.0,-5.0,0.955,100.0,1.0,100.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0 2,4.0,0.0,0.0,300.0,-300.0,0.998,100.0,1.0,100.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0 3,6.0,0.0,0.0,50.0,-13.0,0.99,100.0,1.0,100.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0 diff --git a/tests/results/case118/gencost.csv b/tests/results/case118/gencost.csv index 6e4f978..d9c09e9 100644 --- a/tests/results/case118/gencost.csv +++ b/tests/results/case118/gencost.csv @@ -1,4 +1,4 @@ -,MODEL,STARTUP,SHUTDOWN,NCOST,C2,C1,C0 +gen,MODEL,STARTUP,SHUTDOWN,NCOST,C2,C1,C0 1,2.0,0.0,0.0,3.0,0.01,40.0,0.0 2,2.0,0.0,0.0,3.0,0.01,40.0,0.0 3,2.0,0.0,0.0,3.0,0.01,40.0,0.0 diff --git a/tests/results/case118/schema/branch.csv b/tests/results/case118/schema/branch.csv index 7eb9ef8..e9fa9fd 100644 --- a/tests/results/case118/schema/branch.csv +++ b/tests/results/case118/schema/branch.csv @@ -1,4 +1,4 @@ -,F_BUS,T_BUS,BR_R,BR_X,BR_B,RATE_A,RATE_B,RATE_C,TAP,SHIFT,BR_STATUS,ANGMIN,ANGMAX +branch,F_BUS,T_BUS,BR_R,BR_X,BR_B,RATE_A,RATE_B,RATE_C,TAP,SHIFT,BR_STATUS,ANGMIN,ANGMAX 1,1.0,2.0,0.0303,0.0999,0.0254,0.0,0.0,0.0,0.0,0.0,1.0,-360.0,360.0 2,1.0,3.0,0.0129,0.0424,0.01082,0.0,0.0,0.0,0.0,0.0,1.0,-360.0,360.0 3,4.0,5.0,0.00176,0.00798,0.0021,0.0,0.0,0.0,0.0,0.0,1.0,-360.0,360.0 diff --git a/tests/results/case118/schema/gen.csv b/tests/results/case118/schema/gen.csv index 9479aa1..05e45df 100644 --- a/tests/results/case118/schema/gen.csv +++ b/tests/results/case118/schema/gen.csv @@ -1,4 +1,4 @@ -,GEN_BUS,PG,QG,QMAX,QMIN,VG,MBASE,GEN_STATUS,PMAX,PMIN,PC1,PC2,QC1MIN,QC1MAX,QC2MIN,QC2MAX,RAMP_AGC,RAMP_10,RAMP_30,RAMP_Q,APF +gen,GEN_BUS,PG,QG,QMAX,QMIN,VG,MBASE,GEN_STATUS,PMAX,PMIN,PC1,PC2,QC1MIN,QC1MAX,QC2MIN,QC2MAX,RAMP_AGC,RAMP_10,RAMP_30,RAMP_Q,APF 1,1.0,0.0,0.0,15.0,-5.0,0.955,100.0,1.0,100.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0 2,4.0,0.0,0.0,300.0,-300.0,0.998,100.0,1.0,100.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0 3,6.0,0.0,0.0,50.0,-13.0,0.99,100.0,1.0,100.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0 diff --git a/tests/results/case118/schema/gencost.csv b/tests/results/case118/schema/gencost.csv index 6e4f978..d9c09e9 100644 --- a/tests/results/case118/schema/gencost.csv +++ b/tests/results/case118/schema/gencost.csv @@ -1,4 +1,4 @@ -,MODEL,STARTUP,SHUTDOWN,NCOST,C2,C1,C0 +gen,MODEL,STARTUP,SHUTDOWN,NCOST,C2,C1,C0 1,2.0,0.0,0.0,3.0,0.01,40.0,0.0 2,2.0,0.0,0.0,3.0,0.01,40.0,0.0 3,2.0,0.0,0.0,3.0,0.01,40.0,0.0 diff --git a/tests/results/case118_prefix_suffix/mpc.branch_test.csv b/tests/results/case118_prefix_suffix/mpc.branch_test.csv index 7eb9ef8..e9fa9fd 100644 --- a/tests/results/case118_prefix_suffix/mpc.branch_test.csv +++ b/tests/results/case118_prefix_suffix/mpc.branch_test.csv @@ -1,4 +1,4 @@ -,F_BUS,T_BUS,BR_R,BR_X,BR_B,RATE_A,RATE_B,RATE_C,TAP,SHIFT,BR_STATUS,ANGMIN,ANGMAX +branch,F_BUS,T_BUS,BR_R,BR_X,BR_B,RATE_A,RATE_B,RATE_C,TAP,SHIFT,BR_STATUS,ANGMIN,ANGMAX 1,1.0,2.0,0.0303,0.0999,0.0254,0.0,0.0,0.0,0.0,0.0,1.0,-360.0,360.0 2,1.0,3.0,0.0129,0.0424,0.01082,0.0,0.0,0.0,0.0,0.0,1.0,-360.0,360.0 3,4.0,5.0,0.00176,0.00798,0.0021,0.0,0.0,0.0,0.0,0.0,1.0,-360.0,360.0 diff --git a/tests/results/case118_prefix_suffix/mpc.gen_test.csv b/tests/results/case118_prefix_suffix/mpc.gen_test.csv index 9479aa1..05e45df 100644 --- a/tests/results/case118_prefix_suffix/mpc.gen_test.csv +++ b/tests/results/case118_prefix_suffix/mpc.gen_test.csv @@ -1,4 +1,4 @@ -,GEN_BUS,PG,QG,QMAX,QMIN,VG,MBASE,GEN_STATUS,PMAX,PMIN,PC1,PC2,QC1MIN,QC1MAX,QC2MIN,QC2MAX,RAMP_AGC,RAMP_10,RAMP_30,RAMP_Q,APF +gen,GEN_BUS,PG,QG,QMAX,QMIN,VG,MBASE,GEN_STATUS,PMAX,PMIN,PC1,PC2,QC1MIN,QC1MAX,QC2MIN,QC2MAX,RAMP_AGC,RAMP_10,RAMP_30,RAMP_Q,APF 1,1.0,0.0,0.0,15.0,-5.0,0.955,100.0,1.0,100.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0 2,4.0,0.0,0.0,300.0,-300.0,0.998,100.0,1.0,100.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0 3,6.0,0.0,0.0,50.0,-13.0,0.99,100.0,1.0,100.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0 diff --git a/tests/results/case118_prefix_suffix/mpc.gencost_test.csv b/tests/results/case118_prefix_suffix/mpc.gencost_test.csv index 6e4f978..d9c09e9 100644 --- a/tests/results/case118_prefix_suffix/mpc.gencost_test.csv +++ b/tests/results/case118_prefix_suffix/mpc.gencost_test.csv @@ -1,4 +1,4 @@ -,MODEL,STARTUP,SHUTDOWN,NCOST,C2,C1,C0 +gen,MODEL,STARTUP,SHUTDOWN,NCOST,C2,C1,C0 1,2.0,0.0,0.0,3.0,0.01,40.0,0.0 2,2.0,0.0,0.0,3.0,0.01,40.0,0.0 3,2.0,0.0,0.0,3.0,0.01,40.0,0.0 diff --git a/tests/results/case9/branch.csv b/tests/results/case9/branch.csv index de5dab1..91f24da 100644 --- a/tests/results/case9/branch.csv +++ b/tests/results/case9/branch.csv @@ -1,4 +1,4 @@ -,F_BUS,T_BUS,BR_R,BR_X,BR_B,RATE_A,RATE_B,RATE_C,TAP,SHIFT,BR_STATUS,ANGMIN,ANGMAX +branch,F_BUS,T_BUS,BR_R,BR_X,BR_B,RATE_A,RATE_B,RATE_C,TAP,SHIFT,BR_STATUS,ANGMIN,ANGMAX 1,1.0,4.0,0.0,0.0576,0.0,250.0,250.0,250.0,0.0,0.0,1.0,-360.0,360.0 2,4.0,5.0,0.017,0.092,0.158,250.0,250.0,250.0,0.0,0.0,1.0,-360.0,360.0 3,5.0,6.0,0.039,0.17,0.358,150.0,150.0,150.0,0.0,0.0,1.0,-360.0,360.0 diff --git a/tests/results/case9/bus.csv b/tests/results/case9/bus.csv index 28052e6..f7a71a2 100644 --- a/tests/results/case9/bus.csv +++ b/tests/results/case9/bus.csv @@ -1,4 +1,4 @@ -,BUS_I,BUS_TYPE,PD,QD,GS,BS,BUS_AREA,VM,VA,BASE_KV,ZONE,VMAX,VMIN +bus,BUS_I,BUS_TYPE,PD,QD,GS,BS,BUS_AREA,VM,VA,BASE_KV,ZONE,VMAX,VMIN 1,1.0,3.0,0.0,0.0,0.0,0.0,1.0,1.0,0.0,345.0,1.0,1.1,0.9 2,2.0,2.0,0.0,0.0,0.0,0.0,1.0,1.0,0.0,345.0,1.0,1.1,0.9 3,3.0,2.0,0.0,0.0,0.0,0.0,1.0,1.0,0.0,345.0,1.0,1.1,0.9 diff --git a/tests/results/case9/gen.csv b/tests/results/case9/gen.csv index cfd26c4..311596a 100644 --- a/tests/results/case9/gen.csv +++ b/tests/results/case9/gen.csv @@ -1,4 +1,4 @@ -,GEN_BUS,PG,QG,QMAX,QMIN,VG,MBASE,GEN_STATUS,PMAX,PMIN,PC1,PC2,QC1MIN,QC1MAX,QC2MIN,QC2MAX,RAMP_AGC,RAMP_10,RAMP_30,RAMP_Q,APF +gen,GEN_BUS,PG,QG,QMAX,QMIN,VG,MBASE,GEN_STATUS,PMAX,PMIN,PC1,PC2,QC1MIN,QC1MAX,QC2MIN,QC2MAX,RAMP_AGC,RAMP_10,RAMP_30,RAMP_Q,APF 1,1.0,72.3,27.03,300.0,-300.0,1.04,100.0,1.0,250.0,10.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0 2,2.0,163.0,6.54,300.0,-300.0,1.025,100.0,1.0,300.0,10.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0 3,3.0,85.0,-10.95,300.0,-300.0,1.025,100.0,1.0,270.0,10.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0 diff --git a/tests/results/case9/gencost.csv b/tests/results/case9/gencost.csv index b618f70..9458641 100644 --- a/tests/results/case9/gencost.csv +++ b/tests/results/case9/gencost.csv @@ -1,4 +1,4 @@ -,MODEL,STARTUP,SHUTDOWN,NCOST,C2,C1,C0 +gen,MODEL,STARTUP,SHUTDOWN,NCOST,C2,C1,C0 1,2.0,1500.0,0.0,3.0,0.11,5.0,150.0 2,2.0,2000.0,0.0,3.0,0.085,1.2,600.0 3,2.0,3000.0,0.0,3.0,0.1225,1.0,335.0 diff --git a/tests/results/case9/schema/branch.csv b/tests/results/case9/schema/branch.csv index de5dab1..91f24da 100644 --- a/tests/results/case9/schema/branch.csv +++ b/tests/results/case9/schema/branch.csv @@ -1,4 +1,4 @@ -,F_BUS,T_BUS,BR_R,BR_X,BR_B,RATE_A,RATE_B,RATE_C,TAP,SHIFT,BR_STATUS,ANGMIN,ANGMAX +branch,F_BUS,T_BUS,BR_R,BR_X,BR_B,RATE_A,RATE_B,RATE_C,TAP,SHIFT,BR_STATUS,ANGMIN,ANGMAX 1,1.0,4.0,0.0,0.0576,0.0,250.0,250.0,250.0,0.0,0.0,1.0,-360.0,360.0 2,4.0,5.0,0.017,0.092,0.158,250.0,250.0,250.0,0.0,0.0,1.0,-360.0,360.0 3,5.0,6.0,0.039,0.17,0.358,150.0,150.0,150.0,0.0,0.0,1.0,-360.0,360.0 diff --git a/tests/results/case9/schema/bus.csv b/tests/results/case9/schema/bus.csv index 28052e6..f7a71a2 100644 --- a/tests/results/case9/schema/bus.csv +++ b/tests/results/case9/schema/bus.csv @@ -1,4 +1,4 @@ -,BUS_I,BUS_TYPE,PD,QD,GS,BS,BUS_AREA,VM,VA,BASE_KV,ZONE,VMAX,VMIN +bus,BUS_I,BUS_TYPE,PD,QD,GS,BS,BUS_AREA,VM,VA,BASE_KV,ZONE,VMAX,VMIN 1,1.0,3.0,0.0,0.0,0.0,0.0,1.0,1.0,0.0,345.0,1.0,1.1,0.9 2,2.0,2.0,0.0,0.0,0.0,0.0,1.0,1.0,0.0,345.0,1.0,1.1,0.9 3,3.0,2.0,0.0,0.0,0.0,0.0,1.0,1.0,0.0,345.0,1.0,1.1,0.9 diff --git a/tests/results/case9/schema/gen.csv b/tests/results/case9/schema/gen.csv index cfd26c4..311596a 100644 --- a/tests/results/case9/schema/gen.csv +++ b/tests/results/case9/schema/gen.csv @@ -1,4 +1,4 @@ -,GEN_BUS,PG,QG,QMAX,QMIN,VG,MBASE,GEN_STATUS,PMAX,PMIN,PC1,PC2,QC1MIN,QC1MAX,QC2MIN,QC2MAX,RAMP_AGC,RAMP_10,RAMP_30,RAMP_Q,APF +gen,GEN_BUS,PG,QG,QMAX,QMIN,VG,MBASE,GEN_STATUS,PMAX,PMIN,PC1,PC2,QC1MIN,QC1MAX,QC2MIN,QC2MAX,RAMP_AGC,RAMP_10,RAMP_30,RAMP_Q,APF 1,1.0,72.3,27.03,300.0,-300.0,1.04,100.0,1.0,250.0,10.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0 2,2.0,163.0,6.54,300.0,-300.0,1.025,100.0,1.0,300.0,10.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0 3,3.0,85.0,-10.95,300.0,-300.0,1.025,100.0,1.0,270.0,10.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0 diff --git a/tests/results/case9/schema/gencost.csv b/tests/results/case9/schema/gencost.csv index b618f70..9458641 100644 --- a/tests/results/case9/schema/gencost.csv +++ b/tests/results/case9/schema/gencost.csv @@ -1,4 +1,4 @@ -,MODEL,STARTUP,SHUTDOWN,NCOST,C2,C1,C0 +gen,MODEL,STARTUP,SHUTDOWN,NCOST,C2,C1,C0 1,2.0,1500.0,0.0,3.0,0.11,5.0,150.0 2,2.0,2000.0,0.0,3.0,0.085,1.2,600.0 3,2.0,3000.0,0.0,3.0,0.1225,1.0,335.0 diff --git a/tests/results/case9_prefix_suffix/mpc.branch_test.csv b/tests/results/case9_prefix_suffix/mpc.branch_test.csv index de5dab1..91f24da 100644 --- a/tests/results/case9_prefix_suffix/mpc.branch_test.csv +++ b/tests/results/case9_prefix_suffix/mpc.branch_test.csv @@ -1,4 +1,4 @@ -,F_BUS,T_BUS,BR_R,BR_X,BR_B,RATE_A,RATE_B,RATE_C,TAP,SHIFT,BR_STATUS,ANGMIN,ANGMAX +branch,F_BUS,T_BUS,BR_R,BR_X,BR_B,RATE_A,RATE_B,RATE_C,TAP,SHIFT,BR_STATUS,ANGMIN,ANGMAX 1,1.0,4.0,0.0,0.0576,0.0,250.0,250.0,250.0,0.0,0.0,1.0,-360.0,360.0 2,4.0,5.0,0.017,0.092,0.158,250.0,250.0,250.0,0.0,0.0,1.0,-360.0,360.0 3,5.0,6.0,0.039,0.17,0.358,150.0,150.0,150.0,0.0,0.0,1.0,-360.0,360.0 diff --git a/tests/results/case9_prefix_suffix/mpc.bus_test.csv b/tests/results/case9_prefix_suffix/mpc.bus_test.csv index 28052e6..f7a71a2 100644 --- a/tests/results/case9_prefix_suffix/mpc.bus_test.csv +++ b/tests/results/case9_prefix_suffix/mpc.bus_test.csv @@ -1,4 +1,4 @@ -,BUS_I,BUS_TYPE,PD,QD,GS,BS,BUS_AREA,VM,VA,BASE_KV,ZONE,VMAX,VMIN +bus,BUS_I,BUS_TYPE,PD,QD,GS,BS,BUS_AREA,VM,VA,BASE_KV,ZONE,VMAX,VMIN 1,1.0,3.0,0.0,0.0,0.0,0.0,1.0,1.0,0.0,345.0,1.0,1.1,0.9 2,2.0,2.0,0.0,0.0,0.0,0.0,1.0,1.0,0.0,345.0,1.0,1.1,0.9 3,3.0,2.0,0.0,0.0,0.0,0.0,1.0,1.0,0.0,345.0,1.0,1.1,0.9 diff --git a/tests/results/case9_prefix_suffix/mpc.gen_test.csv b/tests/results/case9_prefix_suffix/mpc.gen_test.csv index cfd26c4..311596a 100644 --- a/tests/results/case9_prefix_suffix/mpc.gen_test.csv +++ b/tests/results/case9_prefix_suffix/mpc.gen_test.csv @@ -1,4 +1,4 @@ -,GEN_BUS,PG,QG,QMAX,QMIN,VG,MBASE,GEN_STATUS,PMAX,PMIN,PC1,PC2,QC1MIN,QC1MAX,QC2MIN,QC2MAX,RAMP_AGC,RAMP_10,RAMP_30,RAMP_Q,APF +gen,GEN_BUS,PG,QG,QMAX,QMIN,VG,MBASE,GEN_STATUS,PMAX,PMIN,PC1,PC2,QC1MIN,QC1MAX,QC2MIN,QC2MAX,RAMP_AGC,RAMP_10,RAMP_30,RAMP_Q,APF 1,1.0,72.3,27.03,300.0,-300.0,1.04,100.0,1.0,250.0,10.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0 2,2.0,163.0,6.54,300.0,-300.0,1.025,100.0,1.0,300.0,10.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0 3,3.0,85.0,-10.95,300.0,-300.0,1.025,100.0,1.0,270.0,10.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0 diff --git a/tests/results/case9_prefix_suffix/mpc.gencost_test.csv b/tests/results/case9_prefix_suffix/mpc.gencost_test.csv index b618f70..9458641 100644 --- a/tests/results/case9_prefix_suffix/mpc.gencost_test.csv +++ b/tests/results/case9_prefix_suffix/mpc.gencost_test.csv @@ -1,4 +1,4 @@ -,MODEL,STARTUP,SHUTDOWN,NCOST,C2,C1,C0 +gen,MODEL,STARTUP,SHUTDOWN,NCOST,C2,C1,C0 1,2.0,1500.0,0.0,3.0,0.11,5.0,150.0 2,2.0,2000.0,0.0,3.0,0.085,1.2,600.0 3,2.0,3000.0,0.0,3.0,0.1225,1.0,335.0 diff --git a/tests/test_read_matpower_cases.py b/tests/test_read_matpower_cases.py index 8f11a60..2e799d1 100644 --- a/tests/test_read_matpower_cases.py +++ b/tests/test_read_matpower_cases.py @@ -3,7 +3,7 @@ import pandas as pd from matpower import path_matpower, start_instance -from matpowercaseframes import CaseFrames +from matpowercaseframes import CaseFrames, ReservesFrames from .__init__ import assert_cf_equal @@ -116,3 +116,44 @@ def test_read_allow_any_keys(): cf = CaseFrames(CASE_NAME, allow_any_keys=True) assert "load" in cf.attributes + + +def test_read_case_reserve(): + m = start_instance() + CASE_NAME = "data/ex_case3a.m" + cf = CaseFrames(CASE_NAME, load_case_engine=m) + + assert "reserves" in cf.attributes + assert hasattr(cf, "reserves") + + assert isinstance(cf.reserves, ReservesFrames) + + assert "zones" in cf.reserves.attributes + assert isinstance(cf.reserves.zones, pd.DataFrame) + assert cf.reserves.zones.index.name == "zone" + assert cf.reserves.zones.columns.name == "gen" + assert cf.reserves.zones.shape == (cf.reserves.req.shape[0], cf.gen.shape[0]) + + assert "req" in cf.reserves.attributes + assert isinstance(cf.reserves.req, pd.DataFrame) + assert cf.reserves.req.index.name == "zone" + assert "PREQ" in cf.reserves.req.columns + + if "cost" in cf.reserves.attributes: + assert isinstance(cf.reserves.cost, pd.DataFrame) + assert "C1" in cf.reserves.cost.columns + + if "qty" in cf.reserves.attributes: + assert isinstance(cf.reserves.qty, pd.DataFrame) + assert "PQTY" in cf.reserves.qty.columns + + expected_attrs = ["zones", "req"] + if hasattr(cf.reserves, "cost"): + expected_attrs.append("cost") + if hasattr(cf.reserves, "qty"): + expected_attrs.append("qty") + + for attr in expected_attrs: + assert attr in cf.reserves.attributes + + m.exit() From c9d40eb937f23bee9816ae7b1aa0b82758ba39e6 Mon Sep 17 00:00:00 2001 From: Muhammad Yasirroni Date: Sat, 17 Jan 2026 06:28:31 +1100 Subject: [PATCH 02/17] initial pass test --- matpowercaseframes/core.py | 60 +++- matpowercaseframes/testing.py | 122 ++++++++ matpowercaseframes/utils.py | 16 + notebooks/load_most_ex_case3a.ipynb | 441 +++++++++++++++++++++++++++- tests/__init__.py | 18 -- tests/test_core.py | 3 +- tests/test_read_matpower_cases.py | 37 ++- 7 files changed, 636 insertions(+), 61 deletions(-) create mode 100644 matpowercaseframes/testing.py diff --git a/matpowercaseframes/core.py b/matpowercaseframes/core.py index c48ea12..05a3ac5 100644 --- a/matpowercaseframes/core.py +++ b/matpowercaseframes/core.py @@ -11,6 +11,7 @@ from .constants import ATTRIBUTES, COLUMNS from .reader import find_attributes, find_name, parse_file +from .utils import get_attr, has_attr try: import matpower @@ -647,10 +648,16 @@ def reset_index(self): Returns: None: The CaseFrames object is modified in place. """ + # store original gen index mapping before resetting, used in reserves + gen_map = {v: k for k, v in enumerate(self.gen.index)} + for attribute in self._attributes: df = getattr(self, attribute) if isinstance(df, pd.DataFrame): + idx_name = df.index.name df.reset_index(drop=True, inplace=True) + df.index.name = idx_name + bus_map = {v: k for k, v in enumerate(self.bus["BUS_I"])} self.bus["BUS_I"] = self.bus.index self.branch[["F_BUS", "T_BUS"]] = self.branch[["F_BUS", "T_BUS"]].replace( @@ -658,9 +665,22 @@ def reset_index(self): ) self.gen["GEN_BUS"] = self.gen["GEN_BUS"].replace(bus_map) - # TODO: - # Since mpc.reserves.zones columns use cf.gen.index, don't forget to update - # the columns of mpc.reserves.zones if exists. + if hasattr(self, "reserves"): + reserves = self.reserves + for attribute in ["zones", "req"]: + df = getattr(reserves, attribute) + if isinstance(df, pd.DataFrame): + idx_name = df.index.name + df.reset_index(drop=True, inplace=True) + df.index.name = idx_name + self.reserves.zones.columns = self.gen.index + + if hasattr(self.reserves, "cost"): + self.reserves.cost = self.reserves.cost.rename(index=gen_map) + self.reserves.cost.index.name = "gen" + if hasattr(self.reserves, "qty"): + self.reserves.qty = self.reserves.qty.rename(index=gen_map) + self.reserves.qty.index.name = "gen" def add_schema_case(self, F=None): # add case to follow casefromat/schema @@ -831,6 +851,15 @@ def to_dict(self): elif attribute in ["bus_name", "branch_name", "gen_name"]: # NOTE: must be in 2D Cell or 2D np.array data[attribute] = np.atleast_2d(getattr(self, attribute).values).T + elif attribute == "reserves": + reserves = {} + reserves["zones"] = getattr(self, attribute).zones.values.tolist() + reserves["req"] = getattr(self, attribute).req.values.tolist() + if hasattr(getattr(self, attribute), "cost"): + reserves["cost"] = getattr(self, attribute).cost.values.tolist() + if hasattr(getattr(self, attribute), "qty"): + reserves["qty"] = getattr(self, attribute).qty.values.tolist() + data[attribute] = reserves else: data[attribute] = getattr(self, attribute).values.tolist() return data @@ -869,35 +898,42 @@ def reserves_data_to_dataframes(reserves): Convert all mpc.reserves struct data to DataFrames. Args: - reserves: Octave struct or dictionary of mpc.reserves object from MATPOWER + reserves: dict or oct2py.io.Struct of mpc.reserves object from MATPOWER Returns: - Dictionary containing: + dict or oct2py.io.Struct containing: - 'zones': Reserve zones DataFrame - 'req': Reserve requirements DataFrame - 'cost': Reserve costs DataFrame (if exists) - 'qty': Reserve quantities DataFrame (if exists) """ dfs = {} - n_zones, n_gens = reserves.zones.shape + + zones_data = get_attr(reserves, "zones") + n_zones, n_gens = np.array(zones_data).shape dfs["zones"] = pd.DataFrame( - reserves.zones, + zones_data, index=pd.RangeIndex(start=1, stop=n_zones + 1, name="zone"), columns=pd.RangeIndex(start=1, stop=n_gens + 1, name="gen"), ) + zone_sum = dfs["zones"].sum(axis=0) idx_gen_with_reserves = zone_sum[zone_sum > 0].index + dfs["req"] = pd.DataFrame( - reserves.req, + get_attr(reserves, "req"), index=pd.RangeIndex(start=1, stop=n_zones + 1, name="zone"), columns=["PREQ"], ) - if hasattr(reserves, "cost"): + + if has_attr(reserves, "cost"): dfs["cost"] = pd.DataFrame( - reserves.cost, index=idx_gen_with_reserves, columns=["C1"] + get_attr(reserves, "cost"), index=idx_gen_with_reserves, columns=["C1"] ) - if hasattr(reserves, "qty"): + + if has_attr(reserves, "qty"): dfs["qty"] = pd.DataFrame( - reserves.qty, index=idx_gen_with_reserves, columns=["PQTY"] + get_attr(reserves, "qty"), index=idx_gen_with_reserves, columns=["PQTY"] ) + return dfs diff --git a/matpowercaseframes/testing.py b/matpowercaseframes/testing.py new file mode 100644 index 0000000..f806be0 --- /dev/null +++ b/matpowercaseframes/testing.py @@ -0,0 +1,122 @@ +import pandas as pd +from pandas.testing import assert_frame_equal, assert_index_equal + +from .core import ReservesFrames + + +def assert_attributes_equal(obj1, obj2): + if set(obj1.attributes) != set(obj2.attributes): + missing_in_obj2 = set(obj1.attributes) - set(obj2.attributes) + missing_in_obj1 = set(obj2.attributes) - set(obj1.attributes) + msg = "CaseFrames have different attributes:\n" + if missing_in_obj2: + msg += f" Missing in obj2: {missing_in_obj2}\n" + if missing_in_obj1: + msg += f" Missing in obj1: {missing_in_obj1}\n" + raise AssertionError(msg) + + +def assert_cf_equal(cf1, cf2): + """ + Assert that two CaseFrames objects are equal. + + Args: + cf1: First CaseFrames object + cf2: Second CaseFrames object + + Raises: + AssertionError: If the CaseFrames objects are not equal + """ + assert_attributes_equal(cf1, cf2) + + for attribute in cf1.attributes: + try: + df1 = getattr(cf1, attribute) + df2 = getattr(cf2, attribute) + + if isinstance(df1, pd.DataFrame): + try: + assert_frame_equal(df1, df2) + except AssertionError as e: + print(f" ✗ DataFrame '{attribute}' does not match:") + print(f" cf1.{attribute} shape: {df1.shape}") + print(f" cf2.{attribute} shape: {df2.shape}") + print(f" cf1.{attribute} dtypes:\n{df1.dtypes}") + print(f" cf2.{attribute} dtypes:\n{df2.dtypes}") + print(f" cf1.{attribute}:\n{df1}") + print(f" cf2.{attribute}:\n{df2}") + raise AssertionError( + f"DataFrame '{attribute}' mismatch:\n{str(e)}" + ) from e + + elif isinstance(df1, pd.Index): + try: + assert_index_equal(df1, df2) + except AssertionError as e: + print(f" ✗ Index '{attribute}' does not match:") + print(f" cf1.{attribute}: {df1.tolist()}") + print(f" cf2.{attribute}: {df2.tolist()}") + raise AssertionError( + f"Index '{attribute}' mismatch:\n{str(e)}" + ) from e + + elif isinstance(df1, ReservesFrames): + try: + assert_reserves_equal(df1, df2) + except AssertionError as e: + raise AssertionError( + f"ReservesFrames '{attribute}' mismatch:\n{str(e)}" + ) from e + + else: + # for scalar values (version, baseMVA, etc.) + try: + assert df1 == df2 + except (AssertionError, ValueError) as e: + print(f" ✗ Value '{attribute}' does not match:") + print(f" cf1.{attribute}: {df1} (type: {type(df1)})") + print(f" cf2.{attribute}: {df2} (type: {type(df2)})") + raise AssertionError( + f"Value '{attribute}' mismatch: {df1} != {df2}" + ) from e + + except Exception as e: + print(f" ✗ Error comparing attribute '{attribute}': {e}") + raise + + +def assert_reserves_equal(reserves1, reserves2): + """ + Assert that two ReservesFrames objects are equal. + + Args: + reserves1: First ReservesFrames object + reserves2: Second ReservesFrames object + attribute_name: Name of the reserves attribute for error messages + verbose: If True, print detailed error messages + + Raises: + AssertionError: If the ReservesFrames objects are not equal + """ + if set(reserves1.attributes) != set(reserves2.attributes): + missing_in_reserves2 = set(reserves1.attributes) - set(reserves2.attributes) + missing_in_reserves1 = set(reserves2.attributes) - set(reserves1.attributes) + msg = "" + if missing_in_reserves2: + msg += f" Missing in reserves2: {missing_in_reserves2}\n" + if missing_in_reserves1: + msg += f" Missing in reserves1: {missing_in_reserves1}\n" + raise AssertionError(msg) + + for attr in reserves1.attributes: + df1 = getattr(reserves1, attr) + df2 = getattr(reserves2, attr) + try: + assert_frame_equal(df1, df2) + except AssertionError as e: + print(f" ✗ reserves.{attr} does not match:") + print(f" reserves1.{attr} shape: {df1.shape}") + print(f" reserves2.{attr} shape: {df2.shape}") + print(f" reserves1.{attr}:\n{df1}") + print(f" reserves2.{attr}:\n{df2}") + raise AssertionError(f"reserves.{attr} mismatch:\n{str(e)}") from e diff --git a/matpowercaseframes/utils.py b/matpowercaseframes/utils.py index ddff57b..7a09228 100644 --- a/matpowercaseframes/utils.py +++ b/matpowercaseframes/utils.py @@ -9,3 +9,19 @@ def int_else_float_except_string(s): return f except ValueError: return s + + +def get_attr(obj, key): + """Get attribute from dict or struct.""" + if isinstance(obj, dict): + return obj.get(key) + else: + return getattr(obj, key, None) + + +def has_attr(obj, key): + """Check if attribute exists in dict or struct.""" + if isinstance(obj, dict): + return key in obj + else: + return hasattr(obj, key) diff --git a/notebooks/load_most_ex_case3a.ipynb b/notebooks/load_most_ex_case3a.ipynb index d167134..69d9539 100644 --- a/notebooks/load_most_ex_case3a.ipynb +++ b/notebooks/load_most_ex_case3a.ipynb @@ -87,7 +87,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 6, "id": "87bc9631", "metadata": {}, "outputs": [ @@ -342,7 +342,263 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 7, + "id": "e4acefc6", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "zones\n" + ] + }, + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
gen1234
zone
11.01.01.00.0
\n", + "
" + ], + "text/plain": [ + "gen 1 2 3 4\n", + "zone \n", + "1 1.0 1.0 1.0 0.0" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "req\n" + ] + }, + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
PREQ
zone
1150.0
\n", + "
" + ], + "text/plain": [ + " PREQ\n", + "zone \n", + "1 150.0" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "cost\n" + ] + }, + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
C1
gen
11.0
23.0
35.0
\n", + "
" + ], + "text/plain": [ + " C1\n", + "gen \n", + "1 1.0\n", + "2 3.0\n", + "3 5.0" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "qty\n" + ] + }, + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
PQTY
gen
1100.0
2100.0
3200.0
\n", + "
" + ], + "text/plain": [ + " PQTY\n", + "gen \n", + "1 100.0\n", + "2 100.0\n", + "3 200.0" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "mpc = cf.to_mpc()\n", + "cf2 = CaseFrames(mpc)\n", + "for attribute in cf2.reserves._attributes:\n", + " print(attribute)\n", + " display(getattr(cf2.reserves, attribute))" + ] + }, + { + "cell_type": "code", + "execution_count": null, "id": "c96fa0ff", "metadata": {}, "outputs": [], @@ -351,6 +607,163 @@ "mpc = m.loadcase(os.path.join(path_most_ex_cases, \"ex_case3a.m\"))" ] }, + { + "cell_type": "code", + "execution_count": null, + "id": "b3ef44f6", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'version': '2',\n", + " 'baseMVA': 100.0,\n", + " 'bus': [[1.0, 3.0, 0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 0.0, 135.0, 1.0, 1.05, 0.95],\n", + " [2.0, 2.0, 0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 0.0, 135.0, 1.0, 1.05, 0.95],\n", + " [3.0, 2.0, 0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 0.0, 135.0, 1.0, 1.05, 0.95]],\n", + " 'gen': [[1.0,\n", + " 125.0,\n", + " 0.0,\n", + " 25.0,\n", + " -25.0,\n", + " 1.0,\n", + " 100.0,\n", + " 1.0,\n", + " 200.0,\n", + " 0.0,\n", + " 0.0,\n", + " 0.0,\n", + " 0.0,\n", + " 0.0,\n", + " 0.0,\n", + " 0.0,\n", + " 0.0,\n", + " 250.0,\n", + " 250.0,\n", + " 0.0,\n", + " 0.0],\n", + " [1.0,\n", + " 125.0,\n", + " 0.0,\n", + " 25.0,\n", + " -25.0,\n", + " 1.0,\n", + " 100.0,\n", + " 1.0,\n", + " 200.0,\n", + " 0.0,\n", + " 0.0,\n", + " 0.0,\n", + " 0.0,\n", + " 0.0,\n", + " 0.0,\n", + " 0.0,\n", + " 0.0,\n", + " 250.0,\n", + " 250.0,\n", + " 0.0,\n", + " 0.0],\n", + " [2.0,\n", + " 200.0,\n", + " 0.0,\n", + " 50.0,\n", + " -50.0,\n", + " 1.0,\n", + " 100.0,\n", + " 1.0,\n", + " 500.0,\n", + " 0.0,\n", + " 0.0,\n", + " 0.0,\n", + " 0.0,\n", + " 0.0,\n", + " 0.0,\n", + " 0.0,\n", + " 0.0,\n", + " 600.0,\n", + " 600.0,\n", + " 0.0,\n", + " 0.0],\n", + " [3.0,\n", + " -450.0,\n", + " 0.0,\n", + " 0.0,\n", + " 0.0,\n", + " 1.0,\n", + " 100.0,\n", + " 1.0,\n", + " 0.0,\n", + " -450.0,\n", + " 0.0,\n", + " 0.0,\n", + " 0.0,\n", + " 0.0,\n", + " 0.0,\n", + " 0.0,\n", + " 0.0,\n", + " 500.0,\n", + " 500.0,\n", + " 0.0,\n", + " 0.0]],\n", + " 'branch': [[1.0,\n", + " 2.0,\n", + " 0.005,\n", + " 0.01,\n", + " 0.0,\n", + " 300.0,\n", + " 300.0,\n", + " 300.0,\n", + " 0.0,\n", + " 0.0,\n", + " 1.0,\n", + " -360.0,\n", + " 360.0],\n", + " [1.0,\n", + " 3.0,\n", + " 0.005,\n", + " 0.01,\n", + " 0.0,\n", + " 240.0,\n", + " 240.0,\n", + " 240.0,\n", + " 0.0,\n", + " 0.0,\n", + " 1.0,\n", + " -360.0,\n", + " 360.0],\n", + " [2.0,\n", + " 3.0,\n", + " 0.005,\n", + " 0.01,\n", + " 0.0,\n", + " 300.0,\n", + " 300.0,\n", + " 300.0,\n", + " 0.0,\n", + " 0.0,\n", + " 1.0,\n", + " -360.0,\n", + " 360.0]],\n", + " 'gencost': [[2.0, 0.0, 0.0, 3.0, 0.1, 0.0, 0.0],\n", + " [2.0, 0.0, 0.0, 3.0, 0.1, 0.0, 0.0],\n", + " [2.0, 0.0, 0.0, 3.0, 0.1, 0.0, 0.0],\n", + " [2.0, 0.0, 0.0, 3.0, 0.0, 1000.0, 0.0]],\n", + " 'reserves': {'zones': [[1.0, 1.0, 1.0, 0.0]],\n", + " 'req': [[150.0]],\n", + " 'cost': [[1.0], [3.0], [5.0]],\n", + " 'qty': [[100.0], [100.0], [200.0]]}}" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "mpc = cf.to_mpc()\n", + "mpc" + ] + }, { "cell_type": "markdown", "id": "8789a89c", @@ -363,7 +776,7 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": null, "id": "65bf6c72", "metadata": {}, "outputs": [ @@ -440,7 +853,7 @@ " [1.]])}" ] }, - "execution_count": 18, + "execution_count": 9, "metadata": {}, "output_type": "execute_result" } @@ -452,7 +865,7 @@ }, { "cell_type": "code", - "execution_count": 19, + "execution_count": null, "id": "9688f94d", "metadata": {}, "outputs": [ @@ -598,7 +1011,7 @@ " [1.]])}]" ] }, - "execution_count": 19, + "execution_count": 10, "metadata": {}, "output_type": "execute_result" } @@ -610,7 +1023,7 @@ }, { "cell_type": "code", - "execution_count": 20, + "execution_count": null, "id": "676057c0", "metadata": {}, "outputs": [ @@ -636,7 +1049,7 @@ " [0.95]])}" ] }, - "execution_count": 20, + "execution_count": 11, "metadata": {}, "output_type": "execute_result" } @@ -648,7 +1061,7 @@ }, { "cell_type": "code", - "execution_count": 21, + "execution_count": null, "id": "2bcf5d64", "metadata": {}, "outputs": [ @@ -664,7 +1077,7 @@ " values" ] }, - "execution_count": 21, + "execution_count": 12, "metadata": {}, "output_type": "execute_result" } @@ -676,7 +1089,7 @@ }, { "cell_type": "code", - "execution_count": 22, + "execution_count": null, "id": "ee0cec81", "metadata": {}, "outputs": [], @@ -686,7 +1099,7 @@ }, { "cell_type": "code", - "execution_count": 23, + "execution_count": null, "id": "c9726cc1", "metadata": {}, "outputs": [ @@ -874,7 +1287,7 @@ " 'MaxStorageLevel': 200.0}]" ] }, - "execution_count": 23, + "execution_count": 14, "metadata": {}, "output_type": "execute_result" } @@ -886,7 +1299,7 @@ }, { "cell_type": "code", - "execution_count": 24, + "execution_count": null, "id": "159b9137", "metadata": {}, "outputs": [], diff --git a/tests/__init__.py b/tests/__init__.py index 8d53ad0..e69de29 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -1,18 +0,0 @@ -import pandas as pd -from pandas.testing import assert_frame_equal, assert_index_equal - - -def assert_cf_equal(cf1, cf2): - for attribute in cf1.attributes: - df1 = getattr(cf1, attribute) - df2 = getattr(cf2, attribute) - if isinstance(df1, pd.DataFrame): - assert_frame_equal(df1, df2) - elif isinstance(df1, pd.Index): - assert_index_equal(df1, df2) - else: - try: - assert df1 == df2 - except ValueError as e: - print(df1, df2) - raise ValueError(e) diff --git a/tests/test_core.py b/tests/test_core.py index caa51f1..5e9588e 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -6,8 +6,7 @@ from matpowercaseframes import CaseFrames from matpowercaseframes.idx import BUS_I, BUS_TYPE - -from .__init__ import assert_cf_equal +from matpowercaseframes.testing import assert_cf_equal """ pytest -n auto -rA --lf -c pyproject.toml --cov-report term-missing --cov=matpowercaseframes tests/ diff --git a/tests/test_read_matpower_cases.py b/tests/test_read_matpower_cases.py index 2e799d1..92131a6 100644 --- a/tests/test_read_matpower_cases.py +++ b/tests/test_read_matpower_cases.py @@ -4,8 +4,7 @@ from matpower import path_matpower, start_instance from matpowercaseframes import CaseFrames, ReservesFrames - -from .__init__ import assert_cf_equal +from matpowercaseframes.testing import assert_cf_equal """ pytest -n auto -rA --cov-report term --cov=matpowercaseframes tests/ @@ -139,21 +138,29 @@ def test_read_case_reserve(): assert cf.reserves.req.index.name == "zone" assert "PREQ" in cf.reserves.req.columns - if "cost" in cf.reserves.attributes: - assert isinstance(cf.reserves.cost, pd.DataFrame) - assert "C1" in cf.reserves.cost.columns - - if "qty" in cf.reserves.attributes: - assert isinstance(cf.reserves.qty, pd.DataFrame) - assert "PQTY" in cf.reserves.qty.columns + assert isinstance(cf.reserves.cost, pd.DataFrame) + assert "C1" in cf.reserves.cost.columns + assert cf.reserves.cost.index.name == "gen" - expected_attrs = ["zones", "req"] - if hasattr(cf.reserves, "cost"): - expected_attrs.append("cost") - if hasattr(cf.reserves, "qty"): - expected_attrs.append("qty") + assert isinstance(cf.reserves.qty, pd.DataFrame) + assert "PQTY" in cf.reserves.qty.columns + assert cf.reserves.qty.index.name == "gen" - for attr in expected_attrs: + for attr in ["zones", "req", "cost", "qty"]: assert attr in cf.reserves.attributes + cf2 = CaseFrames(cf.to_mpc()) + assert_cf_equal(cf, cf2) + + cf.reset_index() + assert cf.reserves.zones.index.name == "zone" + assert cf.reserves.zones.columns.name == "gen" + assert cf.reserves.zones.shape == (cf.reserves.req.shape[0], cf.gen.shape[0]) + assert "C1" in cf.reserves.cost.columns + assert cf.reserves.cost.index.name == "gen" + for idx in cf.reserves.cost.index: + assert idx in cf.gen.index + assert "PQTY" in cf.reserves.qty.columns + assert cf.reserves.qty.index.equals(cf.reserves.cost.index) + m.exit() From 8dc00984fb35cf341ac56d5266e13d5e26050e1f Mon Sep 17 00:00:00 2001 From: Muhammad Yasirroni Date: Sat, 17 Jan 2026 06:30:07 +1100 Subject: [PATCH 03/17] use DataFramesStruct --- matpowercaseframes/core.py | 79 +++++++++++++++++++------------------- 1 file changed, 40 insertions(+), 39 deletions(-) diff --git a/matpowercaseframes/core.py b/matpowercaseframes/core.py index 05a3ac5..e857612 100644 --- a/matpowercaseframes/core.py +++ b/matpowercaseframes/core.py @@ -21,7 +21,42 @@ MATPOWER_EXIST = False -class ReservesFrames: +class DataFramesStruct: + """Base class for struct-like containers with DataFrames.""" + + def __init__(self): + """Initialize the base struct with an empty attributes list.""" + self._attributes = [] + + def setattr(self, name, value): + """ + Set attribute and track it in _attributes list. + + Args: + name (str): Attribute name. + value: Attribute value. + """ + if name not in self._attributes: + self._attributes.append(name) + self.__setattr__(name, value) + + @property + def attributes(self): + """ + List of attributes in this struct object. + + Returns: + list: List of attribute names. + """ + return self._attributes + + def __repr__(self): + """String representation of the struct.""" + attrs = ", ".join(self._attributes) + return f"{self.__class__.__name__}(attributes=[{attrs}])" + + +class ReservesFrames(DataFramesStruct): """A struct-like container for reserves data, similar to CaseFrames.""" def __init__(self, data=None): @@ -32,8 +67,7 @@ def __init__(self, data=None): data (dict, optional): Dictionary containing reserves DataFrames. Expected keys: 'zones', 'req', 'cost', 'qty' """ - self._attributes = [] - + super().__init__() if data is not None: if isinstance(data, dict): for key, value in data.items(): @@ -41,19 +75,8 @@ def __init__(self, data=None): else: raise TypeError(f"ReservesFrames data must be a dict, got {type(data)}") - def setattr(self, name, value): - """Set attribute and track it in _attributes list.""" - if name not in self._attributes: - self._attributes.append(name) - self.__setattr__(name, value) - @property - def attributes(self): - """List of attributes in this ReservesFrames object.""" - return self._attributes - - -class CaseFrames: +class CaseFrames(DataFramesStruct): def __init__( self, data=None, @@ -97,8 +120,9 @@ def __init__( TypeError: If the input data format is unsupported. FileNotFoundError: If the specified file cannot be found. """ - # TODO: support read directory containing csv # TODO: support Path object + + super().__init__() if columns_templates is None: self.columns_templates = copy.deepcopy(COLUMNS) else: @@ -187,7 +211,6 @@ def _read_data( ) elif data is None: self.name = "" - self._attributes = [] else: message = ( f"Not supported source type {type(data)}. Data must be a str path to" @@ -207,11 +230,6 @@ def setattr_as_df(self, name, value, columns_template=None): df = self._get_dataframe(name, value, columns_template=columns_template) self.setattr(name, df) - def setattr(self, name, value): - if name not in self._attributes: - self._attributes.append(name) - self.__setattr__(name, value) - def update_columns_templates(self, columns_templates): self.columns_templates.update(columns_templates) @@ -276,7 +294,6 @@ def _read_matpower(self, filepath, allow_any_keys=False): string = f.read() self.name = find_name(string) - self._attributes = [] for attribute in find_attributes(string): if attribute not in ATTRIBUTES and not allow_any_keys: @@ -304,7 +321,6 @@ def _read_oct2py_struct(self, struct, allow_any_keys=False): Data in structured dictionary or Octave's oct2py struct format. """ self.name = "" - self._attributes = [] for attribute, list_ in struct.items(): if attribute not in ATTRIBUTES and not allow_any_keys: @@ -334,7 +350,6 @@ def _read_numpy_struct(self, array, allow_any_keys=False): array (np.ndarray): Structured NumPy array with named fields. """ self.name = "" - self._attributes = [] for attribute in array.dtype.names: if attribute not in ATTRIBUTES and not allow_any_keys: continue @@ -361,8 +376,6 @@ def _read_excel(self, filepath, prefix="", suffix="", allow_any_keys=False): """ sheets = pd.read_excel(filepath, index_col=0, sheet_name=None) - self._attributes = [] - # info sheet to extract general metadata info_sheet_name = f"{prefix}info{suffix}" if info_sheet_name in sheets: @@ -422,8 +435,6 @@ def _read_csv_dir(self, dirpath, prefix="", suffix="", allow_any_keys=False): csv_data[attribute] = os.path.join(dirpath, csv_file) - self._attributes = [] - # info CSV to extract general metadata info_name = "info" if info_name in csv_data: @@ -522,16 +533,6 @@ def _get_dataframe(self, attribute, data, n_cols=None, columns_template=None): return pd.DataFrame(data, columns=columns) - @property - def attributes(self): - """ - List of attributes that have been parsed from the input data. - - Returns: - list: List of attribute names. - """ - return self._attributes - def _update_index(self, allow_any_keys=False): """ Update the index of the bus, branch, and generator tables based on naming. If From 6241a5a437abf7e9e279ee6d8a63b5ff72bc0575 Mon Sep 17 00:00:00 2001 From: Muhammad Yasirroni Date: Sat, 17 Jan 2026 06:42:02 +1100 Subject: [PATCH 04/17] refactor to assert_frames_struct_equal --- matpowercaseframes/testing.py | 114 +++++++++++------------------- tests/test_core.py | 6 +- tests/test_read_matpower_cases.py | 12 ++-- 3 files changed, 49 insertions(+), 83 deletions(-) diff --git a/matpowercaseframes/testing.py b/matpowercaseframes/testing.py index f806be0..7e9612b 100644 --- a/matpowercaseframes/testing.py +++ b/matpowercaseframes/testing.py @@ -1,122 +1,88 @@ import pandas as pd from pandas.testing import assert_frame_equal, assert_index_equal -from .core import ReservesFrames +from .core import DataFramesStruct -def assert_attributes_equal(obj1, obj2): - if set(obj1.attributes) != set(obj2.attributes): - missing_in_obj2 = set(obj1.attributes) - set(obj2.attributes) - missing_in_obj1 = set(obj2.attributes) - set(obj1.attributes) +def assert_attributes_equal(struct1, struct2): + if set(struct1.attributes) != set(struct2.attributes): + missing_in_struct2 = set(struct1.attributes) - set(struct2.attributes) + missing_in_struct1 = set(struct2.attributes) - set(struct1.attributes) msg = "CaseFrames have different attributes:\n" - if missing_in_obj2: - msg += f" Missing in obj2: {missing_in_obj2}\n" - if missing_in_obj1: - msg += f" Missing in obj1: {missing_in_obj1}\n" + if missing_in_struct2: + msg += f" Missing in struct2: {missing_in_struct2}\n" + if missing_in_struct1: + msg += f" Missing in struct1: {missing_in_struct1}\n" raise AssertionError(msg) -def assert_cf_equal(cf1, cf2): +def assert_frames_struct_equal(struct1, struct2): """ - Assert that two CaseFrames objects are equal. + Assert that two DataFramesStruct objects are equal. + + Recursively compares nested DataFramesStruct objects. Args: - cf1: First CaseFrames object - cf2: Second CaseFrames object + struct1: First DataFramesStruct object + struct2: Second DataFramesStruct object Raises: - AssertionError: If the CaseFrames objects are not equal + AssertionError: If the DataFramesStruct objects are not equal """ - assert_attributes_equal(cf1, cf2) + assert_attributes_equal(struct1, struct2) - for attribute in cf1.attributes: + for attribute in struct1.attributes: try: - df1 = getattr(cf1, attribute) - df2 = getattr(cf2, attribute) + value1 = getattr(struct1, attribute) + value2 = getattr(struct2, attribute) - if isinstance(df1, pd.DataFrame): + if isinstance(value1, pd.DataFrame): try: - assert_frame_equal(df1, df2) + assert_frame_equal(value1, value2) except AssertionError as e: print(f" ✗ DataFrame '{attribute}' does not match:") - print(f" cf1.{attribute} shape: {df1.shape}") - print(f" cf2.{attribute} shape: {df2.shape}") - print(f" cf1.{attribute} dtypes:\n{df1.dtypes}") - print(f" cf2.{attribute} dtypes:\n{df2.dtypes}") - print(f" cf1.{attribute}:\n{df1}") - print(f" cf2.{attribute}:\n{df2}") + print(f" struct1.{attribute} shape: {value1.shape}") + print(f" struct2.{attribute} shape: {value2.shape}") + print(f" struct1.{attribute} dtypes:\n{value1.dtypes}") + print(f" struct2.{attribute} dtypes:\n{value2.dtypes}") + print(f" struct1.{attribute}:\n{value1}") + print(f" struct2.{attribute}:\n{value2}") raise AssertionError( f"DataFrame '{attribute}' mismatch:\n{str(e)}" ) from e - elif isinstance(df1, pd.Index): + elif isinstance(value1, pd.Index): try: - assert_index_equal(df1, df2) + assert_index_equal(value1, value2) except AssertionError as e: print(f" ✗ Index '{attribute}' does not match:") - print(f" cf1.{attribute}: {df1.tolist()}") - print(f" cf2.{attribute}: {df2.tolist()}") + print(f" struct1.{attribute}: {value1.tolist()}") + print(f" struct2.{attribute}: {value2.tolist()}") raise AssertionError( f"Index '{attribute}' mismatch:\n{str(e)}" ) from e - elif isinstance(df1, ReservesFrames): + elif isinstance(value1, DataFramesStruct): + # recursive for DataFramesStruct objects, for example ReservesFrames try: - assert_reserves_equal(df1, df2) + assert_frames_struct_equal(value1, value2) except AssertionError as e: raise AssertionError( - f"ReservesFrames '{attribute}' mismatch:\n{str(e)}" + f"DataFramesStruct '{attribute}' mismatch:\n{str(e)}" ) from e else: # for scalar values (version, baseMVA, etc.) try: - assert df1 == df2 + assert value1 == value2 except (AssertionError, ValueError) as e: print(f" ✗ Value '{attribute}' does not match:") - print(f" cf1.{attribute}: {df1} (type: {type(df1)})") - print(f" cf2.{attribute}: {df2} (type: {type(df2)})") + print(f" struct1.{attribute}: {value1} (type: {type(value1)})") + print(f" struct2.{attribute}: {value2} (type: {type(value2)})") raise AssertionError( - f"Value '{attribute}' mismatch: {df1} != {df2}" + f"Value '{attribute}' mismatch: {value1} != {value2}" ) from e except Exception as e: print(f" ✗ Error comparing attribute '{attribute}': {e}") raise - - -def assert_reserves_equal(reserves1, reserves2): - """ - Assert that two ReservesFrames objects are equal. - - Args: - reserves1: First ReservesFrames object - reserves2: Second ReservesFrames object - attribute_name: Name of the reserves attribute for error messages - verbose: If True, print detailed error messages - - Raises: - AssertionError: If the ReservesFrames objects are not equal - """ - if set(reserves1.attributes) != set(reserves2.attributes): - missing_in_reserves2 = set(reserves1.attributes) - set(reserves2.attributes) - missing_in_reserves1 = set(reserves2.attributes) - set(reserves1.attributes) - msg = "" - if missing_in_reserves2: - msg += f" Missing in reserves2: {missing_in_reserves2}\n" - if missing_in_reserves1: - msg += f" Missing in reserves1: {missing_in_reserves1}\n" - raise AssertionError(msg) - - for attr in reserves1.attributes: - df1 = getattr(reserves1, attr) - df2 = getattr(reserves2, attr) - try: - assert_frame_equal(df1, df2) - except AssertionError as e: - print(f" ✗ reserves.{attr} does not match:") - print(f" reserves1.{attr} shape: {df1.shape}") - print(f" reserves2.{attr} shape: {df2.shape}") - print(f" reserves1.{attr}:\n{df1}") - print(f" reserves2.{attr}:\n{df2}") - raise AssertionError(f"reserves.{attr} mismatch:\n{str(e)}") from e diff --git a/tests/test_core.py b/tests/test_core.py index 5e9588e..8b78a4d 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -6,7 +6,7 @@ from matpowercaseframes import CaseFrames from matpowercaseframes.idx import BUS_I, BUS_TYPE -from matpowercaseframes.testing import assert_cf_equal +from matpowercaseframes.testing import assert_frames_struct_equal """ pytest -n auto -rA --lf -c pyproject.toml --cov-report term-missing --cov=matpowercaseframes tests/ @@ -291,9 +291,9 @@ def test_reset_index_and_infer_numpy_case9(): assert cf_reset.branch["F_BUS"].between(0, len(cf_reset.bus) - 1).all() assert cf_reset.branch["T_BUS"].between(0, len(cf_reset.bus) - 1).all() assert cf_reset.gen["GEN_BUS"].between(0, len(cf_reset.bus) - 1).all() - assert_cf_equal(cf, cf_reset) + assert_frames_struct_equal(cf, cf_reset) # reset multiple times should not change anything cf_reset.reset_index() cf_reset.reset_index() - assert_cf_equal(cf, cf_reset) + assert_frames_struct_equal(cf, cf_reset) diff --git a/tests/test_read_matpower_cases.py b/tests/test_read_matpower_cases.py index 92131a6..50928fb 100644 --- a/tests/test_read_matpower_cases.py +++ b/tests/test_read_matpower_cases.py @@ -4,7 +4,7 @@ from matpower import path_matpower, start_instance from matpowercaseframes import CaseFrames, ReservesFrames -from matpowercaseframes.testing import assert_cf_equal +from matpowercaseframes.testing import assert_frames_struct_equal """ pytest -n auto -rA --cov-report term --cov=matpowercaseframes tests/ @@ -41,8 +41,8 @@ def test_case118(): m.exit() - assert_cf_equal(cf, cf_lc) - assert_cf_equal(cf, cf_mpc) + assert_frames_struct_equal(cf, cf_lc) + assert_frames_struct_equal(cf, cf_mpc) def test_case_RTS_GMLC(): @@ -80,7 +80,7 @@ def test_case_RTS_GMLC(): assert cf.gencost.columns.equals(cols) assert cf_lc.gencost.columns.equals(cols) - assert_cf_equal(cf, cf_lc) + assert_frames_struct_equal(cf, cf_lc) m.exit() @@ -105,7 +105,7 @@ def test_read_without_ext(): CASE_NAME = "case9" cf_no_ext = CaseFrames(CASE_NAME) - assert_cf_equal(cf, cf_no_ext) + assert_frames_struct_equal(cf, cf_no_ext) def test_read_allow_any_keys(): @@ -150,7 +150,7 @@ def test_read_case_reserve(): assert attr in cf.reserves.attributes cf2 = CaseFrames(cf.to_mpc()) - assert_cf_equal(cf, cf2) + assert_frames_struct_equal(cf, cf2) cf.reset_index() assert cf.reserves.zones.index.name == "zone" From 70edb531412dd786842ac6b6d6032b0ea567abfe Mon Sep 17 00:00:00 2001 From: Muhammad Yasirroni Date: Sat, 17 Jan 2026 07:06:14 +1100 Subject: [PATCH 05/17] simplify to_dict and infer_numpy --- matpowercaseframes/core.py | 128 +++++++++++++++++++++---------------- 1 file changed, 72 insertions(+), 56 deletions(-) diff --git a/matpowercaseframes/core.py b/matpowercaseframes/core.py index e857612..e1425ff 100644 --- a/matpowercaseframes/core.py +++ b/matpowercaseframes/core.py @@ -38,7 +38,67 @@ def setattr(self, name, value): """ if name not in self._attributes: self._attributes.append(name) - self.__setattr__(name, value) + super().__setattr__(name, value) + + def to_dict(self): + """ + Convert the DataFramesStruct data into a dictionary. + + + Returns: + dict: Dictionary with attribute names as keys and their data as values. + """ + # TODO: support mpc = cf.to_dict() with reserves data + data = {} + for attribute in self._attributes: + value = getattr(self, attribute) + if isinstance(value, pd.DataFrame): + data[attribute] = value.values.tolist() + elif isinstance(value, DataFramesStruct): + data[attribute] = value.to_dict() + else: + data[attribute] = value + + return data + + def infer_numpy(self): + """ + Infer and convert data types in all DataFrames to appropriate NumPy-compatible + types. + """ + for attribute in self._attributes: + df = getattr(self, attribute) + if isinstance(df, pd.DataFrame): + df = self._infer_numpy(df) + setattr(self, attribute, df) + elif isinstance(df, DataFramesStruct): + df.infer_numpy() + + @staticmethod + def _infer_numpy(df): + """ + Infer and convert the data types of a DataFrame to NumPy-compatible types. + + Args: + df (pd.DataFrame): DataFrame to be processed. + + Returns: + pd.DataFrame: DataFrame with updated data types. + """ + df = df.convert_dtypes() + + columns = df.select_dtypes(include=["integer"]).columns + df[columns] = df[columns].astype(int, errors="ignore") + + columns = df.select_dtypes(include=["float"]).columns + df[columns] = df[columns].astype(float, errors="ignore") + + columns = df.select_dtypes(include=["string"]).columns + df[columns] = df[columns].astype(str) + + columns = df.select_dtypes(include=["boolean"]).columns + df[columns] = df[columns].astype(bool) + return df @property def attributes(self): @@ -594,43 +654,6 @@ def _update_index_any(self): inplace=True, ) - def infer_numpy(self): - """ - Infer and convert data types in all DataFrames to appropriate NumPy-compatible - types. - """ - for attribute in self._attributes: - df = getattr(self, attribute) - if isinstance(df, pd.DataFrame): - df = self._infer_numpy(df) - setattr(self, attribute, df) - - @staticmethod - def _infer_numpy(df): - """ - Infer and convert the data types of a DataFrame to NumPy-compatible types. - - Args: - df (pd.DataFrame): DataFrame to be processed. - - Returns: - pd.DataFrame: DataFrame with updated data types. - """ - df = df.convert_dtypes() - - columns = df.select_dtypes(include=["integer"]).columns - df[columns] = df[columns].astype(int, errors="ignore") - - columns = df.select_dtypes(include=["float"]).columns - df[columns] = df[columns].astype(float, errors="ignore") - - columns = df.select_dtypes(include=["string"]).columns - df[columns] = df[columns].astype(str) - - columns = df.select_dtypes(include=["boolean"]).columns - df[columns] = df[columns].astype(bool) - return df - def reset_index(self): """ Reset indices and remap bus-related indices to 0-based values. @@ -841,28 +864,22 @@ def to_dict(self): Returns: dict: Dictionary with attribute names as keys and their data as values. """ - # TODO: support mpc = cf.to_dict() with reserves data + # default version and baseMVA to None data = { - "version": getattr(self, "version", None), - "baseMVA": getattr(self, "baseMVA", None), + "version": None, + "baseMVA": None, } for attribute in self._attributes: - if attribute == "version" or attribute == "baseMVA": - data[attribute] = getattr(self, attribute) - elif attribute in ["bus_name", "branch_name", "gen_name"]: + value = getattr(self, attribute) + if attribute in ["bus_name", "branch_name", "gen_name"]: # NOTE: must be in 2D Cell or 2D np.array - data[attribute] = np.atleast_2d(getattr(self, attribute).values).T - elif attribute == "reserves": - reserves = {} - reserves["zones"] = getattr(self, attribute).zones.values.tolist() - reserves["req"] = getattr(self, attribute).req.values.tolist() - if hasattr(getattr(self, attribute), "cost"): - reserves["cost"] = getattr(self, attribute).cost.values.tolist() - if hasattr(getattr(self, attribute), "qty"): - reserves["qty"] = getattr(self, attribute).qty.values.tolist() - data[attribute] = reserves + data[attribute] = np.atleast_2d(value.values).T + elif isinstance(value, pd.DataFrame): + data[attribute] = value.values.tolist() + elif isinstance(value, DataFramesStruct): + data[attribute] = value.to_dict() else: - data[attribute] = getattr(self, attribute).values.tolist() + data[attribute] = value return data def to_mpc(self): @@ -875,7 +892,6 @@ def to_mpc(self): Returns: dict: MATPOWER-compatible dictionary with data. """ - # TODO: support mpc = cf.to_mpc() with reserves data return self.to_dict() def to_schema(self, path, prefix="", suffix=""): From 519876b77d0f58699b46d58587c78c628714bc4e Mon Sep 17 00:00:00 2001 From: Muhammad Yasirroni Date: Sat, 17 Jan 2026 07:08:51 +1100 Subject: [PATCH 06/17] add # TODO: support reserves for various other import --- matpowercaseframes/core.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/matpowercaseframes/core.py b/matpowercaseframes/core.py index e1425ff..71ae5c6 100644 --- a/matpowercaseframes/core.py +++ b/matpowercaseframes/core.py @@ -350,6 +350,7 @@ def _read_matpower(self, filepath, allow_any_keys=False): Args: filepath (str): Path to the MATPOWER file. """ + # TODO: support reserves with open(filepath) as f: string = f.read() @@ -409,6 +410,7 @@ def _read_numpy_struct(self, array, allow_any_keys=False): Args: array (np.ndarray): Structured NumPy array with named fields. """ + # TODO: support reserves self.name = "" for attribute in array.dtype.names: if attribute not in ATTRIBUTES and not allow_any_keys: @@ -434,6 +436,7 @@ def _read_excel(self, filepath, prefix="", suffix="", allow_any_keys=False): prefix (str): Sheet prefix for each attribute in the Excel file. suffix (str): Sheet suffix for each attribute in the Excel file. """ + # TODO: support reserves sheets = pd.read_excel(filepath, index_col=0, sheet_name=None) # info sheet to extract general metadata @@ -481,6 +484,7 @@ def _read_csv_dir(self, dirpath, prefix="", suffix="", allow_any_keys=False): suffix (str): File suffix for each attribute CSV file. allow_any_keys (bool): Whether to allow any keys beyond ATTRIBUTES. """ + # TODO: support reserves # create a dictionary mapping attribute names to file paths csv_data = {} for csv_file in os.listdir(dirpath): From c1df5416cded0095e5214149f7d7577d9d308e24 Mon Sep 17 00:00:00 2001 From: Muhammad Yasirroni Date: Sun, 18 Jan 2026 19:08:54 +1100 Subject: [PATCH 07/17] initial add xdgt --- matpowercaseframes/__init__.py | 17 +- matpowercaseframes/constants.py | 36 ++ matpowercaseframes/core.py | 112 +++++- notebooks/load_most_ex_case3a.ipynb | 555 ++++++++++++++-------------- tests/test_read_matpower_cases.py | 52 ++- 5 files changed, 484 insertions(+), 288 deletions(-) diff --git a/matpowercaseframes/__init__.py b/matpowercaseframes/__init__.py index 2564f78..0ea2eca 100644 --- a/matpowercaseframes/__init__.py +++ b/matpowercaseframes/__init__.py @@ -1,2 +1,15 @@ -from .core import CaseFrames, ReservesFrames # noqa: F401 -from .version import __version__ # noqa: F401 +from .core import ( + CaseFrames, + DataFramesStruct, + ReservesFrames, + xGenDataTableFrames, +) +from .version import __version__ + +__all__ = [ + "CaseFrames", + "DataFramesStruct", + "ReservesFrames", + "xGenDataTableFrames", + "__version__", +] diff --git a/matpowercaseframes/constants.py b/matpowercaseframes/constants.py index 9ebf361..7e1297e 100644 --- a/matpowercaseframes/constants.py +++ b/matpowercaseframes/constants.py @@ -16,6 +16,8 @@ "dclinecost", "case", "reserves", + "xgd_table", + "xgd", ) COLUMNS = { @@ -114,10 +116,44 @@ "MU_QMAXT", ], "reserves": { + "zones": [], "req": ["PREQ"], "cost": ["C1"], "qty": ["PQTY"], }, + "xgd_table": [ + "CommitKey", + "CommitSched", + "MinUp", + "MinDown", + "PositiveActiveReservePrice", + "PositiveActiveReserveQuantity", + "NegativeActiveReservePrice", + "NegativeActiveReserveQuantity", + "PositiveActiveDeltaPrice", + "NegativeActiveDeltaPrice", + "PositiveLoadFollowReservePrice", + "PositiveLoadFollowReserveQuantity", + "NegativeLoadFollowReservePrice", + "NegativeLoadFollowReserveQuantity", + ], + "xgd": { # xGenData + # TODO: xgd column names + "CommitKey": [], + "CommitSched": [], + "MinUp": [], + "MinDown": [], + "PositiveActiveReservePrice": [], + "PositiveActiveReserveQuantity": [], + "NegativeActiveReservePrice": [], + "NegativeActiveReserveQuantity": [], + "PositiveActiveDeltaPrice": [], + "NegativeActiveDeltaPrice": [], + "PositiveLoadFollowReservePrice": [], + "PositiveLoadFollowReserveQuantity": [], + "NegativeLoadFollowReservePrice": [], + "NegativeLoadFollowReserveQuantity": [], + }, "if": { # negative 'BRANCHIDX' defines opposite direction "map": ["IFNUM", "BRANCHIDX"], diff --git a/matpowercaseframes/core.py b/matpowercaseframes/core.py index 71ae5c6..6f20d08 100644 --- a/matpowercaseframes/core.py +++ b/matpowercaseframes/core.py @@ -119,7 +119,7 @@ def __repr__(self): class ReservesFrames(DataFramesStruct): """A struct-like container for reserves data, similar to CaseFrames.""" - def __init__(self, data=None): + def __init__(self, data=None, allow_any_keys=False): """ Initialize ReservesFrames with optional data. @@ -130,8 +130,13 @@ def __init__(self, data=None): super().__init__() if data is not None: if isinstance(data, dict): - for key, value in data.items(): - self.setattr(key, value) + if not allow_any_keys: + for key, value in data.items(): + if key in COLUMNS["reserves"]: + self.setattr(key, value) + else: + for key, value in data.items(): + self.setattr(key, value) else: raise TypeError(f"ReservesFrames data must be a dict, got {type(data)}") @@ -181,7 +186,6 @@ def __init__( FileNotFoundError: If the specified file cannot be found. """ # TODO: support Path object - super().__init__() if columns_templates is None: self.columns_templates = copy.deepcopy(COLUMNS) @@ -958,3 +962,103 @@ def reserves_data_to_dataframes(reserves): ) return dfs + + +class xGenDataTableFrames(DataFramesStruct): + """A struct-like container for reserves data, similar to CaseFrames.""" + + def __init__(self, data=None, colnames=None, index=None): + """ + Initialize xGenDataTableFrames with optional data. + + Args: + data (np.ndarray | list, optional): Data for the xGenDataTableFrames. + Defaults to None. + colnames (list, optional): Column names for the DataFrame. Defaults to None. + gen_index (pd.Index, optional): Index for the generators. Defaults to None. + """ + super().__init__() + if colnames is None: + n_col = np.atleast_2d(data).shape[1] + colnames = COLUMNS["xgd_table"][:n_col] + else: + colnames = colnames.flatten() + # if not allow_any_keys: + # # TODO: remove columns in data that are not in COLUMNS + # colnames = [ + # col + # for col in colnames + # if col in COLUMNS["xgd_table"] + # ] + + if data is not None: + if isinstance(data, np.ndarray) or isinstance(data, list): + # TODO: if index is None and cf is given, use cf.gen.index + self.table = pd.DataFrame(data, columns=colnames, index=index) + elif isinstance(data, str): + # TODO: support read from file + # xgdt = m.loadgenericdata( + # data, 'struct', {'colnames', 'data'}, 'xgd_table', cf.to_mpc() + # ) + raise NotImplementedError( + "Reading xGenDataTableFrames from file is not yet implemented." + ) + else: + raise TypeError( + f"xGenDataTableFrames data type is not supported, got {type(data)}." + ) + else: + self.table = pd.DataFrame(columns=colnames, index=index) + + self._attributes.extend(["table", "colnames", "data"]) + + @property + def colnames(self): + return np.atleast_2d(self.table.columns) + + @property + def data(self): + return self.table.to_numpy() + + def to_dict(self): + return { + "colnames": self.colnames, + "data": self.data, + } + + def to_mpc(self): + """ + Convert the xGenDataTableFrames data into a format compatible with MATPOWER. + + Returns: + np.ndarray: Data in a format compatible with MATPOWER. + """ + return self.to_dict() + + def _repr_html_(self): + """HTML representation for Jupyter notebooks.""" + return self.table._repr_html_() + + def __repr__(self): + """String representation.""" + return repr(self.table) + + def __str__(self): + """String representation for print().""" + return str(self.table) + + def __getitem__(self, key): + """Allow indexing like a DataFrame: obj['col'] or obj[0:5].""" + return self.table[key] + + def __setitem__(self, key, value): + """Allow setting values like a DataFrame: obj['col'] = values.""" + self.table[key] = value + + def __len__(self): + """Return number of rows.""" + return len(self.table) + + def __iter__(self): + """Iterate over column names like a DataFrame.""" + return iter(self.table) diff --git a/notebooks/load_most_ex_case3a.ipynb b/notebooks/load_most_ex_case3a.ipynb index 69d9539..19e12fc 100644 --- a/notebooks/load_most_ex_case3a.ipynb +++ b/notebooks/load_most_ex_case3a.ipynb @@ -1,5 +1,16 @@ { "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "aa3d7f0f", + "metadata": {}, + "outputs": [], + "source": [ + "%load_ext autoreload\n", + "%autoreload 2" + ] + }, { "cell_type": "code", "execution_count": 1, @@ -62,7 +73,7 @@ "\n", "from matpower import path_matpower, start_instance\n", "\n", - "from matpowercaseframes import CaseFrames" + "from matpowercaseframes import CaseFrames, xGenDataTableFrames" ] }, { @@ -343,273 +354,6 @@ { "cell_type": "code", "execution_count": 7, - "id": "e4acefc6", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "zones\n" - ] - }, - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
gen1234
zone
11.01.01.00.0
\n", - "
" - ], - "text/plain": [ - "gen 1 2 3 4\n", - "zone \n", - "1 1.0 1.0 1.0 0.0" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "req\n" - ] - }, - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
PREQ
zone
1150.0
\n", - "
" - ], - "text/plain": [ - " PREQ\n", - "zone \n", - "1 150.0" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "cost\n" - ] - }, - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
C1
gen
11.0
23.0
35.0
\n", - "
" - ], - "text/plain": [ - " C1\n", - "gen \n", - "1 1.0\n", - "2 3.0\n", - "3 5.0" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "qty\n" - ] - }, - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
PQTY
gen
1100.0
2100.0
3200.0
\n", - "
" - ], - "text/plain": [ - " PQTY\n", - "gen \n", - "1 100.0\n", - "2 100.0\n", - "3 200.0" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "mpc = cf.to_mpc()\n", - "cf2 = CaseFrames(mpc)\n", - "for attribute in cf2.reserves._attributes:\n", - " print(attribute)\n", - " display(getattr(cf2.reserves, attribute))" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "c96fa0ff", - "metadata": {}, - "outputs": [], - "source": [ - "# TODO: support mpc = cf.to_mpc() with reserves data\n", - "mpc = m.loadcase(os.path.join(path_most_ex_cases, \"ex_case3a.m\"))" - ] - }, - { - "cell_type": "code", - "execution_count": null, "id": "b3ef44f6", "metadata": {}, "outputs": [ @@ -754,7 +498,7 @@ " 'qty': [[100.0], [100.0], [200.0]]}}" ] }, - "execution_count": 8, + "execution_count": 7, "metadata": {}, "output_type": "execute_result" } @@ -774,9 +518,257 @@ "> functions. See: ." ] }, + { + "cell_type": "code", + "execution_count": 8, + "id": "57d214cd", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'colnames': Cell([['CommitKey', 'CommitSched', 'MinUp', 'MinDown',\n", + " 'PositiveActiveReservePrice', 'PositiveActiveReserveQuantity',\n", + " 'NegativeActiveReservePrice', 'NegativeActiveReserveQuantity',\n", + " 'PositiveActiveDeltaPrice', 'NegativeActiveDeltaPrice',\n", + " 'PositiveLoadFollowReservePrice',\n", + " 'PositiveLoadFollowReserveQuantity',\n", + " 'NegativeLoadFollowReservePrice',\n", + " 'NegativeLoadFollowReserveQuantity']]),\n", + " 'data': array([[1.0e+00, 1.0e+00, 1.0e+00, 1.0e+00, 5.0e+00, 2.5e+02, 1.0e+01,\n", + " 2.5e+02, 1.0e-09, 1.0e-09, 1.0e-06, 2.5e+02, 1.0e-06, 2.5e+02],\n", + " [1.0e+00, 1.0e+00, 3.0e+00, 1.0e+00, 1.0e-08, 2.5e+02, 2.0e-08,\n", + " 2.5e+02, 1.0e-09, 1.0e-09, 1.0e-06, 2.5e+02, 1.0e-06, 2.5e+02],\n", + " [1.0e+00, 1.0e+00, 1.0e+00, 1.0e+00, 1.5e+00, 6.0e+02, 3.0e+00,\n", + " 6.0e+02, 1.0e-09, 1.0e-09, 1.0e+01, 1.0e+02, 1.0e+01, 2.5e+02],\n", + " [2.0e+00, 1.0e+00, 1.0e+00, 1.0e+00, 1.0e-08, 8.0e+02, 2.0e-08,\n", + " 8.0e+02, 1.0e-09, 1.0e-09, 1.0e-06, 8.0e+02, 1.0e-06, 8.0e+02]])}" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "fields = {\"colnames\", \"data\"}\n", + "args = mpc\n", + "xgd_table = \"ex_xgd_uc.m\"\n", + "xgdt = m.loadgenericdata(xgd_table, \"struct\", fields, \"xgd_table\", args)\n", + "xgdt" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "d9b030d8", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "RangeIndex(start=1, stop=5, step=1, name='gen')" + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "cf.gen.index" + ] + }, { "cell_type": "code", "execution_count": null, + "id": "d2315b0c", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
CommitKeyCommitSchedMinUpMinDownPositiveActiveReservePricePositiveActiveReserveQuantityNegativeActiveReservePriceNegativeActiveReserveQuantityPositiveActiveDeltaPriceNegativeActiveDeltaPricePositiveLoadFollowReservePricePositiveLoadFollowReserveQuantityNegativeLoadFollowReservePriceNegativeLoadFollowReserveQuantity
gen
11.01.01.01.05.000000e+00250.01.000000e+01250.01.000000e-091.000000e-090.000001250.00.000001250.0
21.01.03.01.01.000000e-08250.02.000000e-08250.01.000000e-091.000000e-090.000001250.00.000001250.0
31.01.01.01.01.500000e+00600.03.000000e+00600.01.000000e-091.000000e-0910.000000100.010.000000250.0
42.01.01.01.01.000000e-08800.02.000000e-08800.01.000000e-091.000000e-090.000001800.00.000001800.0
\n", + "
" + ], + "text/plain": [ + " CommitKey CommitSched MinUp MinDown PositiveActiveReservePrice \\\n", + "gen \n", + "1 1.0 1.0 1.0 1.0 5.000000e+00 \n", + "2 1.0 1.0 3.0 1.0 1.000000e-08 \n", + "3 1.0 1.0 1.0 1.0 1.500000e+00 \n", + "4 2.0 1.0 1.0 1.0 1.000000e-08 \n", + "\n", + " PositiveActiveReserveQuantity NegativeActiveReservePrice \\\n", + "gen \n", + "1 250.0 1.000000e+01 \n", + "2 250.0 2.000000e-08 \n", + "3 600.0 3.000000e+00 \n", + "4 800.0 2.000000e-08 \n", + "\n", + " NegativeActiveReserveQuantity PositiveActiveDeltaPrice \\\n", + "gen \n", + "1 250.0 1.000000e-09 \n", + "2 250.0 1.000000e-09 \n", + "3 600.0 1.000000e-09 \n", + "4 800.0 1.000000e-09 \n", + "\n", + " NegativeActiveDeltaPrice PositiveLoadFollowReservePrice \\\n", + "gen \n", + "1 1.000000e-09 0.000001 \n", + "2 1.000000e-09 0.000001 \n", + "3 1.000000e-09 10.000000 \n", + "4 1.000000e-09 0.000001 \n", + "\n", + " PositiveLoadFollowReserveQuantity NegativeLoadFollowReservePrice \\\n", + "gen \n", + "1 250.0 0.000001 \n", + "2 250.0 0.000001 \n", + "3 100.0 10.000000 \n", + "4 800.0 0.000001 \n", + "\n", + " NegativeLoadFollowReserveQuantity \n", + "gen \n", + "1 250.0 \n", + "2 250.0 \n", + "3 250.0 \n", + "4 800.0 " + ] + }, + "execution_count": 15, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "xgdtf = xGenDataTableFrames(data=xgdt.data, colnames=xgdt.colnames, index=cf.gen.index)\n", + "xgdtf" + ] + }, + { + "cell_type": "code", + "execution_count": 8, "id": "65bf6c72", "metadata": {}, "outputs": [ @@ -853,19 +845,20 @@ " [1.]])}" ] }, - "execution_count": 9, + "execution_count": 8, "metadata": {}, "output_type": "execute_result" } ], "source": [ + "# NOTE: xgd should be loaded as struct, because the table format is for xgdt\n", "xgd = m.loadxgendata(\"ex_xgd_uc.m\", mpc)\n", "xgd" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 9, "id": "9688f94d", "metadata": {}, "outputs": [ @@ -1011,7 +1004,7 @@ " [1.]])}]" ] }, - "execution_count": 10, + "execution_count": 9, "metadata": {}, "output_type": "execute_result" } @@ -1023,7 +1016,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 10, "id": "676057c0", "metadata": {}, "outputs": [ @@ -1049,7 +1042,7 @@ " [0.95]])}" ] }, - "execution_count": 11, + "execution_count": 10, "metadata": {}, "output_type": "execute_result" } @@ -1061,7 +1054,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 11, "id": "2bcf5d64", "metadata": {}, "outputs": [ @@ -1077,7 +1070,7 @@ " values" ] }, - "execution_count": 12, + "execution_count": 11, "metadata": {}, "output_type": "execute_result" } @@ -1089,7 +1082,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 12, "id": "ee0cec81", "metadata": {}, "outputs": [], @@ -1099,7 +1092,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 13, "id": "c9726cc1", "metadata": {}, "outputs": [ @@ -1287,7 +1280,7 @@ " 'MaxStorageLevel': 200.0}]" ] }, - "execution_count": 14, + "execution_count": 13, "metadata": {}, "output_type": "execute_result" } @@ -1299,12 +1292,12 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 14, "id": "159b9137", "metadata": {}, "outputs": [], "source": [ - "m.exit()" + "# m.exit()" ] }, { diff --git a/tests/test_read_matpower_cases.py b/tests/test_read_matpower_cases.py index 50928fb..0c40238 100644 --- a/tests/test_read_matpower_cases.py +++ b/tests/test_read_matpower_cases.py @@ -1,9 +1,10 @@ import warnings +import numpy as np import pandas as pd from matpower import path_matpower, start_instance -from matpowercaseframes import CaseFrames, ReservesFrames +from matpowercaseframes import CaseFrames, ReservesFrames, xGenDataTableFrames from matpowercaseframes.testing import assert_frames_struct_equal """ @@ -164,3 +165,52 @@ def test_read_case_reserve(): assert cf.reserves.qty.index.equals(cf.reserves.cost.index) m.exit() + + +def test_read_xgd_table(): + """Test reading and using xGenDataTableFrames.""" + m = start_instance() + + # Load case and xGenDataTable from file + CASE_NAME = "data/ex_case3a.m" + cf = CaseFrames(CASE_NAME, load_case_engine=m) + + xgd_table_file = "ex_xgd_uc.m" + fields = {"colnames", "data"} + args = cf.to_mpc() + + # Load xGenDataTable using MATPOWER + # NOTE: loadgenericdata not yet support absolute path + xgdt = m.loadgenericdata(xgd_table_file, "struct", fields, "xgd_table", args) + + # Create xGenDataTableFrames + xgdtf = xGenDataTableFrames( + data=xgdt.data, colnames=xgdt.colnames, index=cf.gen.index + ) + + # Test basic attributes + assert hasattr(xgdtf, "table") + assert isinstance(xgdtf.table, pd.DataFrame) + assert xgdtf.table.index.equals(cf.gen.index) + + # Test properties + assert xgdtf.colnames.shape[0] == 1 # Should be 2D with one row + assert xgdtf.data.shape == xgdtf.table.shape + assert len(xgdtf) == len(cf.gen) + + # Test DataFrame-like behavior + first_col = xgdtf.table.columns[0] + assert xgdtf[first_col].equals(xgdtf.table[first_col]) + + # Test iteration + cols_from_iter = list(xgdtf) + assert cols_from_iter == list(xgdtf.table.columns) + + # Test to_dict and to_mpc + xgdtf_dict = xgdtf.to_dict() + assert "colnames" in xgdtf_dict + assert "data" in xgdtf_dict + np.testing.assert_array_equal(xgdtf_dict["colnames"], xgdtf.colnames) + np.testing.assert_array_equal(xgdtf_dict["data"], xgdtf.data) + + m.exit() From 09f7e5bf403511d80ca735fa551f93ca8fbdd447 Mon Sep 17 00:00:00 2001 From: Muhammad Yasirroni Date: Sun, 18 Jan 2026 20:11:46 +1100 Subject: [PATCH 08/17] support xdgt --- matpowercaseframes/constants.py | 11 +- matpowercaseframes/core.py | 57 +++- notebooks/load_most_ex_case3a.ipynb | 390 +++++++++++++++++----------- tests/test_read_matpower_cases.py | 38 +-- 4 files changed, 316 insertions(+), 180 deletions(-) diff --git a/matpowercaseframes/constants.py b/matpowercaseframes/constants.py index 7e1297e..6ef4531 100644 --- a/matpowercaseframes/constants.py +++ b/matpowercaseframes/constants.py @@ -138,11 +138,9 @@ "NegativeLoadFollowReserveQuantity", ], "xgd": { # xGenData - # TODO: xgd column names - "CommitKey": [], "CommitSched": [], - "MinUp": [], - "MinDown": [], + "InitialPg": [], + "RampWearCostCoeff": [], "PositiveActiveReservePrice": [], "PositiveActiveReserveQuantity": [], "NegativeActiveReservePrice": [], @@ -153,6 +151,11 @@ "PositiveLoadFollowReserveQuantity": [], "NegativeLoadFollowReservePrice": [], "NegativeLoadFollowReserveQuantity": [], + "TerminalPg": [], + "CommitKey": [], + "InitialState": [], + "MinUp": [], + "MinDow": [], }, "if": { # negative 'BRANCHIDX' defines opposite direction diff --git a/matpowercaseframes/core.py b/matpowercaseframes/core.py index 6f20d08..b558021 100644 --- a/matpowercaseframes/core.py +++ b/matpowercaseframes/core.py @@ -975,7 +975,7 @@ def __init__(self, data=None, colnames=None, index=None): data (np.ndarray | list, optional): Data for the xGenDataTableFrames. Defaults to None. colnames (list, optional): Column names for the DataFrame. Defaults to None. - gen_index (pd.Index, optional): Index for the generators. Defaults to None. + index (pd.Index, optional): Index for the generators. Defaults to None. """ super().__init__() if colnames is None: @@ -992,11 +992,16 @@ def __init__(self, data=None, colnames=None, index=None): # ] if data is not None: - if isinstance(data, np.ndarray) or isinstance(data, list): - # TODO: if index is None and cf is given, use cf.gen.index + if isinstance(data, dict): + self.table = pd.DataFrame( + {k: np.asarray(v).flatten() for k, v in data.items()}, index=index + ) + elif isinstance(data, np.ndarray) or isinstance(data, list): self.table = pd.DataFrame(data, columns=colnames, index=index) elif isinstance(data, str): - # TODO: support read from file + # TODO: + # 1. Support read from file. + # 2. Differentiate between xdg_table and xdg # xgdt = m.loadgenericdata( # data, 'struct', {'colnames', 'data'}, 'xgd_table', cf.to_mpc() # ) @@ -1007,6 +1012,12 @@ def __init__(self, data=None, colnames=None, index=None): raise TypeError( f"xGenDataTableFrames data type is not supported, got {type(data)}." ) + + if index is None: + # TODO: if index is None and cf is given, use cf.gen.index + self.table.index = pd.RangeIndex( + start=1, stop=self.table.shape[0] + 1, name="gen" + ) else: self.table = pd.DataFrame(columns=colnames, index=index) @@ -1020,20 +1031,50 @@ def colnames(self): def data(self): return self.table.to_numpy() + @property + def df(self): + return self.table + + def to_df(self): + return self.table + def to_dict(self): + """ + Convert to combined dict with both xgd and xgd_table formats. + + Returns: + dict: Combined dictionary with: + - Column names as keys with 2D array values (xgd format) + - 'colnames' and 'data' keys (xgd_table format) + """ + xgd_dict = self.to_xgd() + xgdt_dict = self.to_xdgt() + return {**xgd_dict, **xgdt_dict} + + def to_xgdt(self): + """ + Convert to xgd_table format (for loadgenericdata). + + Returns: + dict: Dictionary with 'colnames' (2D array) and 'data' (2D array) + """ return { "colnames": self.colnames, "data": self.data, } - def to_mpc(self): + def to_xgd(self): """ - Convert the xGenDataTableFrames data into a format compatible with MATPOWER. + Convert to xgd struct format (for loadxgendata/MOST functions). Returns: - np.ndarray: Data in a format compatible with MATPOWER. + dict: Dictionary where keys are column names and values are 2D arrays (n, 1) """ - return self.to_dict() + return { + col: self.table[col].values.reshape(-1, 1) for col in self.table.columns + } + + # TODO: support other DataFrame methods def _repr_html_(self): """HTML representation for Jupyter notebooks.""" diff --git a/notebooks/load_most_ex_case3a.ipynb b/notebooks/load_most_ex_case3a.ipynb index 19e12fc..c3f35be 100644 --- a/notebooks/load_most_ex_case3a.ipynb +++ b/notebooks/load_most_ex_case3a.ipynb @@ -2,7 +2,7 @@ "cells": [ { "cell_type": "code", - "execution_count": null, + "execution_count": 1, "id": "aa3d7f0f", "metadata": {}, "outputs": [], @@ -13,7 +13,7 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": 2, "id": "eae79aa2", "metadata": {}, "outputs": [], @@ -41,7 +41,7 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 3, "id": "5bc86ee4", "metadata": {}, "outputs": [ @@ -64,7 +64,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 45, "id": "3116b86b", "metadata": {}, "outputs": [], @@ -78,7 +78,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 5, "id": "10fd2d91", "metadata": {}, "outputs": [], @@ -88,7 +88,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 6, "id": "7e230eb0", "metadata": {}, "outputs": [], @@ -98,7 +98,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 7, "id": "87bc9631", "metadata": {}, "outputs": [ @@ -353,7 +353,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 8, "id": "b3ef44f6", "metadata": {}, "outputs": [ @@ -498,7 +498,7 @@ " 'qty': [[100.0], [100.0], [200.0]]}}" ] }, - "execution_count": 7, + "execution_count": 8, "metadata": {}, "output_type": "execute_result" } @@ -520,69 +520,8 @@ }, { "cell_type": "code", - "execution_count": 8, - "id": "57d214cd", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'colnames': Cell([['CommitKey', 'CommitSched', 'MinUp', 'MinDown',\n", - " 'PositiveActiveReservePrice', 'PositiveActiveReserveQuantity',\n", - " 'NegativeActiveReservePrice', 'NegativeActiveReserveQuantity',\n", - " 'PositiveActiveDeltaPrice', 'NegativeActiveDeltaPrice',\n", - " 'PositiveLoadFollowReservePrice',\n", - " 'PositiveLoadFollowReserveQuantity',\n", - " 'NegativeLoadFollowReservePrice',\n", - " 'NegativeLoadFollowReserveQuantity']]),\n", - " 'data': array([[1.0e+00, 1.0e+00, 1.0e+00, 1.0e+00, 5.0e+00, 2.5e+02, 1.0e+01,\n", - " 2.5e+02, 1.0e-09, 1.0e-09, 1.0e-06, 2.5e+02, 1.0e-06, 2.5e+02],\n", - " [1.0e+00, 1.0e+00, 3.0e+00, 1.0e+00, 1.0e-08, 2.5e+02, 2.0e-08,\n", - " 2.5e+02, 1.0e-09, 1.0e-09, 1.0e-06, 2.5e+02, 1.0e-06, 2.5e+02],\n", - " [1.0e+00, 1.0e+00, 1.0e+00, 1.0e+00, 1.5e+00, 6.0e+02, 3.0e+00,\n", - " 6.0e+02, 1.0e-09, 1.0e-09, 1.0e+01, 1.0e+02, 1.0e+01, 2.5e+02],\n", - " [2.0e+00, 1.0e+00, 1.0e+00, 1.0e+00, 1.0e-08, 8.0e+02, 2.0e-08,\n", - " 8.0e+02, 1.0e-09, 1.0e-09, 1.0e-06, 8.0e+02, 1.0e-06, 8.0e+02]])}" - ] - }, - "execution_count": 8, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "fields = {\"colnames\", \"data\"}\n", - "args = mpc\n", - "xgd_table = \"ex_xgd_uc.m\"\n", - "xgdt = m.loadgenericdata(xgd_table, \"struct\", fields, \"xgd_table\", args)\n", - "xgdt" - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "id": "d9b030d8", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "RangeIndex(start=1, stop=5, step=1, name='gen')" - ] - }, - "execution_count": 12, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "cf.gen.index" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "d2315b0c", + "execution_count": 52, + "id": "575be760", "metadata": {}, "outputs": [ { @@ -756,109 +695,260 @@ "4 800.0 " ] }, - "execution_count": 15, + "execution_count": 52, "metadata": {}, "output_type": "execute_result" } ], "source": [ + "# NOTE:\n", + "# This code below and `m.loadxgendata(xgdtf.to_dict(), mpc)` can be skipped using,\n", + "# `xgd = m.loadxgendata(\"ex_xgd_uc.m\", mpc)`\n", + "xgdt = m.loadgenericdata(\n", + " \"ex_xgd_uc.m\", \"struct\", {\"colnames\", \"data\"}, \"xgd_table\", cf.to_mpc()\n", + ")\n", "xgdtf = xGenDataTableFrames(data=xgdt.data, colnames=xgdt.colnames, index=cf.gen.index)\n", "xgdtf" ] }, { "cell_type": "code", - "execution_count": 8, - "id": "65bf6c72", + "execution_count": null, + "id": "63a198e4", "metadata": {}, "outputs": [ { "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
CommitSchedInitialPgRampWearCostCoeffPositiveActiveReservePricePositiveActiveReserveQuantityNegativeActiveReservePriceNegativeActiveReserveQuantityPositiveActiveDeltaPriceNegativeActiveDeltaPricePositiveLoadFollowReservePricePositiveLoadFollowReserveQuantityNegativeLoadFollowReservePriceNegativeLoadFollowReserveQuantityCommitKeyInitialStateMinUpMinDown
gen
11.0125.00.05.000000e+00250.01.000000e+01250.01.000000e-091.000000e-090.000001250.00.000001250.01.0inf1.01.0
21.0125.00.01.000000e-08250.02.000000e-08250.01.000000e-091.000000e-090.000001250.00.000001250.01.0inf3.01.0
31.0200.00.01.500000e+00600.03.000000e+00600.01.000000e-091.000000e-0910.000000100.010.000000250.01.0inf1.01.0
41.0-450.00.01.000000e-08800.02.000000e-08800.01.000000e-091.000000e-090.000001800.00.000001800.02.0inf1.01.0
\n", + "
" + ], "text/plain": [ - "{'CommitSched': array([[1.],\n", - " [1.],\n", - " [1.],\n", - " [1.]]),\n", - " 'InitialPg': array([[ 125.],\n", - " [ 125.],\n", - " [ 200.],\n", - " [-450.]]),\n", - " 'RampWearCostCoeff': array([[0.],\n", - " [0.],\n", - " [0.],\n", - " [0.]]),\n", - " 'PositiveActiveReservePrice': array([[5.0e+00],\n", - " [1.0e-08],\n", - " [1.5e+00],\n", - " [1.0e-08]]),\n", - " 'PositiveActiveReserveQuantity': array([[250.],\n", - " [250.],\n", - " [600.],\n", - " [800.]]),\n", - " 'NegativeActiveReservePrice': array([[1.e+01],\n", - " [2.e-08],\n", - " [3.e+00],\n", - " [2.e-08]]),\n", - " 'NegativeActiveReserveQuantity': array([[250.],\n", - " [250.],\n", - " [600.],\n", - " [800.]]),\n", - " 'PositiveActiveDeltaPrice': array([[1.e-09],\n", - " [1.e-09],\n", - " [1.e-09],\n", - " [1.e-09]]),\n", - " 'NegativeActiveDeltaPrice': array([[1.e-09],\n", - " [1.e-09],\n", - " [1.e-09],\n", - " [1.e-09]]),\n", - " 'PositiveLoadFollowReservePrice': array([[1.e-06],\n", - " [1.e-06],\n", - " [1.e+01],\n", - " [1.e-06]]),\n", - " 'PositiveLoadFollowReserveQuantity': array([[250.],\n", - " [250.],\n", - " [100.],\n", - " [800.]]),\n", - " 'NegativeLoadFollowReservePrice': array([[1.e-06],\n", - " [1.e-06],\n", - " [1.e+01],\n", - " [1.e-06]]),\n", - " 'NegativeLoadFollowReserveQuantity': array([[250.],\n", - " [250.],\n", - " [250.],\n", - " [800.]]),\n", - " 'CommitKey': array([[1.],\n", - " [1.],\n", - " [1.],\n", - " [2.]]),\n", - " 'InitialState': array([[inf],\n", - " [inf],\n", - " [inf],\n", - " [inf]]),\n", - " 'MinUp': array([[1.],\n", - " [3.],\n", - " [1.],\n", - " [1.]]),\n", - " 'MinDown': array([[1.],\n", - " [1.],\n", - " [1.],\n", - " [1.]])}" + " CommitSched InitialPg RampWearCostCoeff PositiveActiveReservePrice \\\n", + "gen \n", + "1 1.0 125.0 0.0 5.000000e+00 \n", + "2 1.0 125.0 0.0 1.000000e-08 \n", + "3 1.0 200.0 0.0 1.500000e+00 \n", + "4 1.0 -450.0 0.0 1.000000e-08 \n", + "\n", + " PositiveActiveReserveQuantity NegativeActiveReservePrice \\\n", + "gen \n", + "1 250.0 1.000000e+01 \n", + "2 250.0 2.000000e-08 \n", + "3 600.0 3.000000e+00 \n", + "4 800.0 2.000000e-08 \n", + "\n", + " NegativeActiveReserveQuantity PositiveActiveDeltaPrice \\\n", + "gen \n", + "1 250.0 1.000000e-09 \n", + "2 250.0 1.000000e-09 \n", + "3 600.0 1.000000e-09 \n", + "4 800.0 1.000000e-09 \n", + "\n", + " NegativeActiveDeltaPrice PositiveLoadFollowReservePrice \\\n", + "gen \n", + "1 1.000000e-09 0.000001 \n", + "2 1.000000e-09 0.000001 \n", + "3 1.000000e-09 10.000000 \n", + "4 1.000000e-09 0.000001 \n", + "\n", + " PositiveLoadFollowReserveQuantity NegativeLoadFollowReservePrice \\\n", + "gen \n", + "1 250.0 0.000001 \n", + "2 250.0 0.000001 \n", + "3 100.0 10.000000 \n", + "4 800.0 0.000001 \n", + "\n", + " NegativeLoadFollowReserveQuantity CommitKey InitialState MinUp \\\n", + "gen \n", + "1 250.0 1.0 inf 1.0 \n", + "2 250.0 1.0 inf 3.0 \n", + "3 250.0 1.0 inf 1.0 \n", + "4 800.0 2.0 inf 1.0 \n", + "\n", + " MinDown \n", + "gen \n", + "1 1.0 \n", + "2 1.0 \n", + "3 1.0 \n", + "4 1.0 " ] }, - "execution_count": 8, + "execution_count": 53, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "# NOTE: xgd should be loaded as struct, because the table format is for xgdt\n", - "xgd = m.loadxgendata(\"ex_xgd_uc.m\", mpc)\n", - "xgd" + "xgd = m.loadxgendata(xgdtf.to_xgdt(), cf.to_mpc())\n", + "xgdf = xGenDataTableFrames(data=xgd)\n", + "xgdf" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "447c676e", + "metadata": {}, + "outputs": [ + { + "ename": "AttributeError", + "evalue": "'xGenDataTableFrames' object has no attribute 'CommitSched'", + "output_type": "error", + "traceback": [ + "\u001b[31m---------------------------------------------------------------------------\u001b[39m", + "\u001b[31mAttributeError\u001b[39m Traceback (most recent call last)", + "\u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[56]\u001b[39m\u001b[32m, line 1\u001b[39m\n\u001b[32m----> \u001b[39m\u001b[32m1\u001b[39m xgdf[\u001b[33m'\u001b[39m\u001b[33mCommitSched\u001b[39m\u001b[33m'\u001b[39m], \u001b[43mxgdf\u001b[49m\u001b[43m.\u001b[49m\u001b[43mCommitSched\u001b[49m\n", + "\u001b[31mAttributeError\u001b[39m: 'xGenDataTableFrames' object has no attribute 'CommitSched'" + ] + } + ], + "source": [ + "xgdf[\"CommitSched\"], xgdf.CommitSched" ] }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 54, "id": "9688f94d", "metadata": {}, "outputs": [ @@ -1004,13 +1094,13 @@ " [1.]])}]" ] }, - "execution_count": 9, + "execution_count": 54, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "[iwind, mpc, xgd] = m.addwind(\"ex_wind_uc\", mpc, xgd, nout=3)\n", + "[iwind, mpc, xgd] = m.addwind(\"ex_wind_uc\", cf.to_mpc(), xgdf.to_xgd(), nout=3)\n", "[iwind, mpc, xgd]" ] }, diff --git a/tests/test_read_matpower_cases.py b/tests/test_read_matpower_cases.py index 0c40238..037db46 100644 --- a/tests/test_read_matpower_cases.py +++ b/tests/test_read_matpower_cases.py @@ -171,46 +171,48 @@ def test_read_xgd_table(): """Test reading and using xGenDataTableFrames.""" m = start_instance() - # Load case and xGenDataTable from file CASE_NAME = "data/ex_case3a.m" cf = CaseFrames(CASE_NAME, load_case_engine=m) - xgd_table_file = "ex_xgd_uc.m" - fields = {"colnames", "data"} - args = cf.to_mpc() - - # Load xGenDataTable using MATPOWER # NOTE: loadgenericdata not yet support absolute path - xgdt = m.loadgenericdata(xgd_table_file, "struct", fields, "xgd_table", args) + xgd_table_file = "ex_xgd_uc.m" + xgdt = m.loadgenericdata( + xgd_table_file, "struct", {"colnames", "data"}, "xgd_table", cf.to_mpc() + ) - # Create xGenDataTableFrames xgdtf = xGenDataTableFrames( data=xgdt.data, colnames=xgdt.colnames, index=cf.gen.index ) - # Test basic attributes assert hasattr(xgdtf, "table") assert isinstance(xgdtf.table, pd.DataFrame) assert xgdtf.table.index.equals(cf.gen.index) - # Test properties assert xgdtf.colnames.shape[0] == 1 # Should be 2D with one row assert xgdtf.data.shape == xgdtf.table.shape assert len(xgdtf) == len(cf.gen) - # Test DataFrame-like behavior first_col = xgdtf.table.columns[0] assert xgdtf[first_col].equals(xgdtf.table[first_col]) - # Test iteration cols_from_iter = list(xgdtf) assert cols_from_iter == list(xgdtf.table.columns) - # Test to_dict and to_mpc - xgdtf_dict = xgdtf.to_dict() - assert "colnames" in xgdtf_dict - assert "data" in xgdtf_dict - np.testing.assert_array_equal(xgdtf_dict["colnames"], xgdtf.colnames) - np.testing.assert_array_equal(xgdtf_dict["data"], xgdtf.data) + xdgt = xgdtf.to_xgdt() + assert "colnames" in xdgt + assert "data" in xdgt + np.testing.assert_array_equal(xdgt["colnames"], xgdtf.colnames) + np.testing.assert_array_equal(xdgt["data"], xgdtf.data) + + xgd = m.loadxgendata(xgdtf.to_xgdt(), cf.to_mpc()) + xgdf = xGenDataTableFrames(data=xgd) + xgdf_df = xgdf.to_df() + assert isinstance(xgdf_df, pd.DataFrame) + + xdg = xgdf.to_xgd() + for k, v in xdg.items(): + assert k in xgdf.colnames.flatten().tolist() + assert k in xgdf_df.columns + np.testing.assert_array_equal(v, xgdf[k].to_numpy().reshape(-1, 1)) m.exit() From d876203e2a2b9c814b3022644887b5486b25c9f8 Mon Sep 17 00:00:00 2001 From: Muhammad Yasirroni Date: Sun, 18 Jan 2026 22:21:08 +1100 Subject: [PATCH 09/17] support DataFrame methods --- matpowercaseframes/core.py | 63 ++++++++++++++++++----------- notebooks/load_most_ex_case3a.ipynb | 28 ++----------- 2 files changed, 43 insertions(+), 48 deletions(-) diff --git a/matpowercaseframes/core.py b/matpowercaseframes/core.py index b558021..b6e8d3c 100644 --- a/matpowercaseframes/core.py +++ b/matpowercaseframes/core.py @@ -965,24 +965,32 @@ def reserves_data_to_dataframes(reserves): class xGenDataTableFrames(DataFramesStruct): - """A struct-like container for reserves data, similar to CaseFrames.""" + """ + A struct-like and DataFrame-like container for xGenData with MATPOWER + compatibility. Support standard DataFrame operations. + """ def __init__(self, data=None, colnames=None, index=None): """ Initialize xGenDataTableFrames with optional data. Args: - data (np.ndarray | list, optional): Data for the xGenDataTableFrames. - Defaults to None. - colnames (list, optional): Column names for the DataFrame. Defaults to None. - index (pd.Index, optional): Index for the generators. Defaults to None. + data (np.ndarray | list | dict, optional): Data for xGenDataTableFrames. + colnames (list, optional): Column names. Defaults to None. + index (pd.Index, optional): Row index. Defaults to None. """ super().__init__() - if colnames is None: - n_col = np.atleast_2d(data).shape[1] - colnames = COLUMNS["xgd_table"][:n_col] + + if data is not None and colnames is None: + if isinstance(data, dict): + colnames = list(data.keys()) + else: + n_col = np.atleast_2d(data).shape[1] + colnames = COLUMNS["xgd_table"][:n_col] + elif colnames is not None: + colnames = np.asarray(colnames).flatten() else: - colnames = colnames.flatten() + colnames = [] # if not allow_any_keys: # # TODO: remove columns in data that are not in COLUMNS # colnames = [ @@ -993,11 +1001,15 @@ def __init__(self, data=None, colnames=None, index=None): if data is not None: if isinstance(data, dict): - self.table = pd.DataFrame( - {k: np.asarray(v).flatten() for k, v in data.items()}, index=index + self.setattr( + "table", + pd.DataFrame( + {k: np.asarray(v).flatten() for k, v in data.items()}, + index=index, + ), ) - elif isinstance(data, np.ndarray) or isinstance(data, list): - self.table = pd.DataFrame(data, columns=colnames, index=index) + elif isinstance(data, (np.ndarray, list)): + self.setattr("table", pd.DataFrame(data, columns=colnames, index=index)) elif isinstance(data, str): # TODO: # 1. Support read from file. @@ -1006,29 +1018,26 @@ def __init__(self, data=None, colnames=None, index=None): # data, 'struct', {'colnames', 'data'}, 'xgd_table', cf.to_mpc() # ) raise NotImplementedError( - "Reading xGenDataTableFrames from file is not yet implemented." + "Reading xGenDataTableFrames from file not yet implemented." ) else: - raise TypeError( - f"xGenDataTableFrames data type is not supported, got {type(data)}." - ) + raise TypeError(f"Unsupported data type: {type(data)}") if index is None: - # TODO: if index is None and cf is given, use cf.gen.index self.table.index = pd.RangeIndex( - start=1, stop=self.table.shape[0] + 1, name="gen" + start=1, stop=len(self.table) + 1, name="gen" ) else: - self.table = pd.DataFrame(columns=colnames, index=index) - - self._attributes.extend(["table", "colnames", "data"]) + self.setattr("table", pd.DataFrame(columns=colnames, index=index)) @property def colnames(self): + """Get column names as 2D array (MATPOWER xgdt format).""" return np.atleast_2d(self.table.columns) @property def data(self): + """Get data as NumPy array.""" return self.table.to_numpy() @property @@ -1074,7 +1083,15 @@ def to_xgd(self): col: self.table[col].values.reshape(-1, 1) for col in self.table.columns } - # TODO: support other DataFrame methods + def __getattr__(self, name): + """Delegate attribute access to the underlying DataFrame.""" + # if attribute not found in self, + if name in self.table.columns: + # try to get it from self.table[name] + return np.atleast_2d(self.table[name].values) + else: + # try to get it from self.table + return getattr(self.table, name) def _repr_html_(self): """HTML representation for Jupyter notebooks.""" diff --git a/notebooks/load_most_ex_case3a.ipynb b/notebooks/load_most_ex_case3a.ipynb index c3f35be..4b9d8d3 100644 --- a/notebooks/load_most_ex_case3a.ipynb +++ b/notebooks/load_most_ex_case3a.ipynb @@ -64,7 +64,7 @@ }, { "cell_type": "code", - "execution_count": 45, + "execution_count": 57, "id": "3116b86b", "metadata": {}, "outputs": [], @@ -913,7 +913,7 @@ "4 1.0 " ] }, - "execution_count": 53, + "execution_count": 58, "metadata": {}, "output_type": "execute_result" } @@ -921,29 +921,7 @@ "source": [ "xgd = m.loadxgendata(xgdtf.to_xgdt(), cf.to_mpc())\n", "xgdf = xGenDataTableFrames(data=xgd)\n", - "xgdf" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "447c676e", - "metadata": {}, - "outputs": [ - { - "ename": "AttributeError", - "evalue": "'xGenDataTableFrames' object has no attribute 'CommitSched'", - "output_type": "error", - "traceback": [ - "\u001b[31m---------------------------------------------------------------------------\u001b[39m", - "\u001b[31mAttributeError\u001b[39m Traceback (most recent call last)", - "\u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[56]\u001b[39m\u001b[32m, line 1\u001b[39m\n\u001b[32m----> \u001b[39m\u001b[32m1\u001b[39m xgdf[\u001b[33m'\u001b[39m\u001b[33mCommitSched\u001b[39m\u001b[33m'\u001b[39m], \u001b[43mxgdf\u001b[49m\u001b[43m.\u001b[49m\u001b[43mCommitSched\u001b[49m\n", - "\u001b[31mAttributeError\u001b[39m: 'xGenDataTableFrames' object has no attribute 'CommitSched'" - ] - } - ], - "source": [ - "xgdf[\"CommitSched\"], xgdf.CommitSched" + "xgdf # support both `xgdf[\"CommitSched\"]` and `xgdf.CommitSched`" ] }, { From fea3907bcc3278c076a5bb851830c8dae86e61e2 Mon Sep 17 00:00:00 2001 From: Muhammad Yasirroni Date: Mon, 19 Jan 2026 06:32:47 +1100 Subject: [PATCH 10/17] add dcline index --- matpowercaseframes/idx/dcline.py | 67 ++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 matpowercaseframes/idx/dcline.py diff --git a/matpowercaseframes/idx/dcline.py b/matpowercaseframes/idx/dcline.py new file mode 100644 index 0000000..20d0df9 --- /dev/null +++ b/matpowercaseframes/idx/dcline.py @@ -0,0 +1,67 @@ +# The index, name and meaning of each column of the dcline matrix is given +# below: +# +# columns 0-16 must be included in input matrix (in case file) +# 0 F_BUS f, "from" bus number +# 1 T_BUS t, "to" bus number +# 2 BR_STATUS initial dcline status, 1 - in service, 0 - out of service +# 3 PF MW flow at "from" bus ("from" -> "to") +# 4 PT MW flow at "to" bus ("from" -> "to") +# 5 QF MVAr injection at "from" bus ("from" -> "to") +# 6 QT MVAr injection at "to" bus ("from" -> "to") +# 7 VF voltage setpoint at "from" bus (p.u.) +# 8 VT voltage setpoint at "to" bus (p.u.) +# 9 PMIN lower limit on PF (MW flow at "from" end) +# 10 PMAX upper limit on PF (MW flow at "from" end) +# 11 QMINF lower limit on MVAr injection at "from" bus +# 12 QMAXF upper limit on MVAr injection at "from" bus +# 13 QMINT lower limit on MVAr injection at "to" bus +# 14 QMAXT upper limit on MVAr injection at "to" bus +# 15 LOSS0 constant term of linear loss function (MW) +# 16 LOSS1 linear term of linear loss function (MW/MW) +# (loss = LOSS0 + LOSS1 * PF) +# +# columns 17-22 are added to matrix after OPF solution +# they are typically not present in the input matrix +# (assume OPF objective function has units, u) +# 17 MU_PMIN Kuhn-Tucker multiplier on lower flow lim at "from" bus (u/MW) +# 18 MU_PMAX Kuhn-Tucker multiplier on upper flow lim at "from" bus (u/MW) +# 19 MU_QMINF Kuhn-Tucker multiplier on lower VAr lim at "from" bus (u/MVAr) +# 20 MU_QMAXF Kuhn-Tucker multiplier on upper VAr lim at "from" bus (u/MVAr) +# 21 MU_QMINT Kuhn-Tucker multiplier on lower VAr lim at "to" bus (u/MVAr) +# 22 MU_QMAXT Kuhn-Tucker multiplier on upper VAr lim at "to" bus (u/MVAr) +# +# See also toggle_dcline. + +# MATPOWER +# Copyright (c) 2011-2024, Power Systems Engineering Research Center (PSERC) +# by Ray Zimmerman, PSERC Cornell +# +# This file is part of MATPOWER. +# Covered by the 3-clause BSD License (see LICENSE file for details). +# See https://matpower.org for more info. + +# define the indices +F_BUS = 0 # f, "from" bus number +T_BUS = 1 # t, "to" bus number +BR_STATUS = 2 # initial dcline status, 1 - in service, 0 - out of service +PF = 3 # MW flow at "from" bus ("from" -> "to") +PT = 4 # MW flow at "to" bus ("from" -> "to") +QF = 5 # MVAr injection at "from" bus ("from" -> "to") +QT = 6 # MVAr injection at "to" bus ("from" -> "to") +VF = 7 # voltage setpoint at "from" bus (p.u.) +VT = 8 # voltage setpoint at "to" bus (p.u.) +PMIN = 9 # lower limit on PF (MW flow at "from" end) +PMAX = 10 # upper limit on PF (MW flow at "from" end) +QMINF = 11 # lower limit on MVAr injection at "from" bus +QMAXF = 12 # upper limit on MVAr injection at "from" bus +QMINT = 13 # lower limit on MVAr injection at "to" bus +QMAXT = 14 # upper limit on MVAr injection at "to" bus +LOSS0 = 15 # constant term of linear loss function (MW) +LOSS1 = 16 # linear term of linear loss function (MW) +MU_PMIN = 17 # Kuhn-Tucker multiplier on lower flow lim at "from" bus (u/MW) +MU_PMAX = 18 # Kuhn-Tucker multiplier on upper flow lim at "from" bus (u/MW) +MU_QMINF = 19 # Kuhn-Tucker multiplier on lower VAr lim at "from" bus (u/MVAr) +MU_QMAXF = 20 # Kuhn-Tucker multiplier on upper VAr lim at "from" bus (u/MVAr) +MU_QMINT = 21 # Kuhn-Tucker multiplier on lower VAr lim at "to" bus (u/MVAr) +MU_QMAXT = 22 # Kuhn-Tucker multiplier on upper VAr lim at "to" bus (u/MVAr) From 8de4b2615d0e02b589327df295c9d263be65a44a Mon Sep 17 00:00:00 2001 From: Muhammad Yasirroni Date: Mon, 19 Jan 2026 06:33:19 +1100 Subject: [PATCH 11/17] add ct and profile index --- matpowercaseframes/idx/ct.py | 111 ++++++++++++++++++ matpowercaseframes/idx/profile.py | 187 ++++++++++++++++++++++++++++++ 2 files changed, 298 insertions(+) create mode 100644 matpowercaseframes/idx/ct.py create mode 100644 matpowercaseframes/idx/profile.py diff --git a/matpowercaseframes/idx/ct.py b/matpowercaseframes/idx/ct.py new file mode 100644 index 0000000..5df65a1 --- /dev/null +++ b/matpowercaseframes/idx/ct.py @@ -0,0 +1,111 @@ +# idx_ct - Defines constants for named column indices to changes table +# :: +# +# [CT_LABEL, CT_PROB, CT_TABLE, CT_TBUS, CT_TGEN, CT_TBRCH, CT_TAREABUS, ... +# CT_TAREAGEN, CT_TAREABRCH, CT_ROW, CT_COL, CT_CHGTYPE, CT_REP, ... +# CT_REL, CT_ADD, CT_NEWVAL, CT_TLOAD, CT_TAREALOAD, CT_LOAD_ALL_PQ, ... +# CT_LOAD_FIX_PQ, CT_LOAD_DIS_PQ, CT_LOAD_ALL_P, CT_LOAD_FIX_P, ... +# CT_LOAD_DIS_P, CT_TGENCOST, CT_TAREAGENCOST, CT_MODCOST_F, ... +# CT_MODCOST_X] = idx_ct; +# +# CT_LABEL: column of changes table where the change set label is stored +# +# CT_PROB: column of changes table where the probability of the +# change set is stored +# +# CT_TABLE: column of the changes table where the type of system data +# table to be modified is stored; +# type CT_TBUS indicates bus table +# type CT_TGEN indicates gen table +# type CT_TBRCH indicates branch table +# type CT_TLOAD indicates a load modification (bus and/or gen tables) +# type CT_TAREABUS indicates area-wide change in bus table +# type CT_TAREAGEN indicates area-wide change in generator table +# type CT_TAREABRCH indicates area-wide change in branch table +# type CT_TAREALOAD indicates area-wide change in load +# (bus and/or gen tables) +# +# CT_ROW: column of changes table where the row number in the data +# table to be modified is stored. A value of "0" in this column +# has the special meaning "apply to all rows". For an area-wide +# type of change, the area number is stored here instead. +# +# CT_COL: column of changes table where the number of the column in +# the data table to be modified is stored +# For CT_TLOAD and CT_TAREALOAD, the value entered in this column +# is one of the following codes (or its negative), rather than +# a column index: +# type CT_LOAD_ALL_PQ modify all loads, real & reactive +# type CT_LOAD_FIX_PQ modify only fixed loads, real & reactive +# type CT_LOAD_DIS_PQ modify only dispatchable loads, real & reactive +# type CT_LOAD_ALL_P modify all loads, real only +# type CT_LOAD_FIX_P modify only fixed loads, real only +# type CT_LOAD_DIS_P modify only dispatchable loads, real only +# If the negative of one of these codes is used, then any affected +# dispatchable loads will have their costs scaled as well. +# For CT_TGENCOST and CT_TAREAGENCOST, in addition to an actual +# column index, this value can also take one of the following +# codes to indicate a scaling (CT_REL change type) or shifting +# (CT_ADD change type) of the specified cost functions: +# type CT_MODCOST_F scales or shifts the cost function vertically +# type CT_MODCOST_X scales or shifts the cost function horizontally +# See MODCOST. +# +# CT_CHGTYPE: column of changes table where the type of change to +# be made is stored: +# type CT_REP replaces old value by value in CT_NEWVAL column +# type CT_REL multiplies old value by factor in CT_NEWVAL column +# type CT_ADD adds value in CT_NEWVAL column to old value +# +# See also apply_changes, modcost. + +# MATPOWER +# Copyright (c) 2000-2024, Power Systems Engineering Research Center (PSERC) +# by Carlos E. Murillo-Sanchez, PSERC Cornell & Universidad Nacional de Colombia +# and Ray Zimmerman, PSERC Cornell +# +# This file is part of MATPOWER. +# Covered by the 3-clause BSD License (see LICENSE file for details). +# See https://matpower.org for more info. + +# column labels for changes table +CT_LABEL = 0 # change set label +CT_PROB = 1 # change set probability +CT_TABLE = 2 # type of table to be modified (see possible values below) +CT_ROW = 3 # number of the row to be modified (0 means all rows) +CT_COL = 4 # number of the column to be modified +# (for some values in CT_TABLE column, this can be a +# special code instead of an actual column index) +CT_CHGTYPE = 5 # type of parameter modification to be made +# (see possible values below) +CT_NEWVAL = 6 # quantity to use for replacement value, scale factor +# or shift amount + +# named values for CT_TABLE entry +CT_TBUS = 1 # bus table +CT_TGEN = 2 # gen table +CT_TBRCH = 3 # branch table +CT_TAREABUS = 4 # area-wide change in bus table +CT_TAREAGEN = 5 # area-wide change in gen table +CT_TAREABRCH = 6 # area-wide change in branch table +CT_TLOAD = 7 # single bus load change +CT_TAREALOAD = 8 # area-wide bus load change +CT_TGENCOST = 9 # gencost table +CT_TAREAGENCOST = 10 # area-wide change in gencost table + +# named values for CT_CHGTYPE entry +CT_REP = 1 # replace old value with new one in column CT_NEWVAL +CT_REL = 2 # multiply old value by factor in column CT_NEWVAL +CT_ADD = 3 # add value in column CT_NEWVAL to old value + +# codes for CT_COL entry when CT_TABLE entry is CT_TLOAD or CT_TAREALOAD +CT_LOAD_ALL_PQ = 1 # all loads, real and reactive +CT_LOAD_FIX_PQ = 2 # only fixed loads, real and reactive +CT_LOAD_DIS_PQ = 3 # only dispatchable loads, real and reactive +CT_LOAD_ALL_P = 4 # all loads, real only +CT_LOAD_FIX_P = 5 # only fixed loads, real only +CT_LOAD_DIS_P = 6 # only dispatchable loads, real only + +# codes for CT_COL entry when CT_TABLE entry is CT_TGENCOST or CT_TAREAGENCOST +CT_MODCOST_F = -1 # scale or shift cost function vertically +CT_MODCOST_X = -2 # scale or shift cost function horizontally diff --git a/matpowercaseframes/idx/profile.py b/matpowercaseframes/idx/profile.py new file mode 100644 index 0000000..6cbe197 --- /dev/null +++ b/matpowercaseframes/idx/profile.py @@ -0,0 +1,187 @@ +# idx_profile - Defines constants used by Profiles. +# :: +# +# [PR_REP, PR_REL, PR_ADD, PR_TCONT, PR_TYPES, PR_TMPCD,... +# PR_TXGD, PR_TCTD, PR_TSTGD, PR_CHGTYPES] = idx_profile; +# +# Indicates and defines numeric and string id's for the types of profiles +# that can be created to modify input data for MOST across time and +# scenarios. Some types may require that a table is further specified, +# i.e., a specific table of data whose content is to be modified by the +# profile. Rows (rows) and columns (col) fields of each profile further +# specify which entries of the data in question is to be modified by the +# profile. The change type field (chgtype) indicates how the change is +# applied, and finally, the values field (values) contains the new values +# to be used. +# +# type: string field of each profile struct where the id of the type of +# the profile is stored. Possible values are: +# string 'mpcData' indicates changes on fields of mpc +# (e.g. bus or gen tables) +# string 'xGenData' indicates changes on xgd fields +# (e.g. offers or commitment vars) +# string 'ContingencyData' indicates changes on the set of +# contingencies to be applied +# string 'StorageData' indicates changes on the storage struct +# +# table: scalar or string field of each profile struct where the id/name +# of the table/field that needs to be modified is stored. +# Possible values are: +# - if type is 'mpcData' you may modify the following tables +# (specified with a scalar): +# scalar CT_TBUS change in bus table +# scalar CT_TGEN change in gen table +# scalar CT_TBRCH change in branch table +# scalar CT_TAREABUS area-wide change in bus table +# scalar CT_TAREAGEN area-wide change in gen table +# scalar CT_TAREABRCH area-wide change in branch table +# scalar CT_TLOAD load modification (bus and/or gen tables) +# scalar CT_TAREALOAD area-wide change in load (bus/gen tables) +# scalar CT_TGENCOST change in gencost table +# scalar CT_TAREAGENCOST area-wide change in gencost table +# - if type is 'xGenData' you may modify the following fields: +# string 'CommitSched' +# string 'InitialPg' +# string 'RampWearCostCoeff' +# string 'PositiveActiveReservePrice' +# string 'PositiveActiveReserveQuantity' +# string 'NegativeActiveReservePrice' +# string 'NegativeActiveReserveQuantity' +# string 'PositiveActiveDeltaPrice' +# string 'NegativeActiveDeltaPrice' +# string 'PositiveLoadFollowReservePrice' +# string 'PositiveLoadFollowReserveQuantity' +# string 'NegativeLoadFollowReservePrice' +# string 'NegativeLoadFollowReserveQuantity' +# - if type is 'ContingencyData' you may modify the following +# tables: +# scalar PR_TCONT indicates a modification of the subset of +# contingencies (of the master table of contingencies) to be +# applied at each time period and/or scenario. +# - if type is 'StorageData' you may modify the following fields: +# string 'MinStorageLevel' +# string 'MaxStorageLevel' +# string 'OutEff' +# string 'InEff' +# string 'LossFactor' +# string 'rho' +# +# rows: numeric vector field of each profile struct where the row +# numbers (1st-dim) of the array to be modified are stored. Row +# numbers usually represent specifically which gens, branches, +# buses, contingencies (by the labels), or area will be affected +# by the modification across time or across scenarios. A value of +# "0" in this field has the special meaning of "apply to all +# rows". For an area-wide type of change, the area number is +# stored here instead. +# +# col: scalar field of each profile struct where the id of the +# parameter to be modified is stored. This id may indicate a +# column number or some other parameter depending +# on the type and the table/field. Possible values are: +# - if type is 'mpcData' the parameters you may modify depend on +# the table chosen, see IDX_CT on CT_COL for details. +# - if type is 'xGenData', 'ContingencyData', or 'StorageData', +# then col is completely ignored. +# +# chgtype: scalar field of each profile struct where the id of the type of +# change to be made is stored. Possible values are: +# scalar PR_REP replaces old values by new 'values' +# scalar PR_REL multiplies old value by factors in 'vales' +# scalar PR_ADD adds entries in 'values' field to old value +# +# values: numeric array field of each profile struct where the new +# values/factors, i.e., the profile itself, is stored. This array +# must have 3 dimensions in a pre-defined order: [nt nj_max n] +# (i) dimension corresponding to time periods +# (ii) dimension corresponding to scenarios +# (iii) dimension corresponding to elements indicated by 'rows' +# A singleton dimension in 'values' not matching with nt==1, +# nj_max==1 or length(profile.rows)==1 is interpreted as "apply +# to all" whenever the parameter being modified allows such an +# expansion. These "expansions" occur within loadmd(). + +# MOST +# Copyright (c) 2013-2024, Power Systems Engineering Research Center (PSERC) +# by Daniel Munoz-Alvarez and Ray Zimmerman, PSERC Cornell +# +# This file is part of MOST. +# Covered by the 3-clause BSD License (see LICENSE file for details). +# See https://github.com/MATPOWER/most for more info. + +from .ct import ( + CT_TAREABRCH, + CT_TAREABUS, + CT_TAREAGEN, + CT_TAREAGENCOST, + CT_TAREALOAD, + CT_TBRCH, + CT_TBUS, + CT_TGEN, + CT_TGENCOST, + CT_TLOAD, +) + +# types of profiles +PR_TYPES = [ + "mpcData", # changes to mpc + "xGenData", # changes to xgd + "ContingencyData", # changes to ct_subset + "StorageData", # changes to Storage +] + +# labels for modifiable tables +# - tables for type 'mpcData' +PR_TMPCD = [ + CT_TBUS, + CT_TGEN, + CT_TBRCH, + CT_TAREABUS, + CT_TAREAGEN, + CT_TAREABRCH, + CT_TLOAD, + CT_TAREALOAD, + CT_TGENCOST, + CT_TAREAGENCOST, +] + +# - tables for type xGenData +PR_TXGD = [ + "CommitSched", + "InitialPg", + "RampWearCostCoeff", + "PositiveActiveReservePrice", + "PositiveActiveReserveQuantity", + "NegativeActiveReservePrice", + "NegativeActiveReserveQuantity", + "PositiveActiveDeltaPrice", + "NegativeActiveDeltaPrice", + "PositiveLoadFollowReservePrice", + "PositiveLoadFollowReserveQuantity", + "NegativeLoadFollowReservePrice", + "NegativeLoadFollowReserveQuantity", + "CommitKey", + "InitialState", + "MinUp", + "MinDown", +] + +# - tables for type 'ContingencyData' +PR_TCONT = 1 # ct_subset of contingencies +PR_TCTD = [PR_TCONT] + +# - tables for type storage +PR_TSTGD = [ + "MinStorageLevel", + "MaxStorageLevel", + "OutEff", + "InEff", + "LossFactor", + "rho", +] + +# named values for chgtype entry +PR_REP = 1 # replace old values with new ones in field 'values' +PR_REL = 2 # multiply old values by factors in field 'values' +PR_ADD = 3 # add value in field 'values' to old values +PR_CHGTYPES = [PR_REP, PR_REL, PR_ADD] From a23adb3f5003b5c87f6d969890af5fe26ed445ee Mon Sep 17 00:00:00 2001 From: Muhammad Yasirroni Date: Mon, 19 Jan 2026 09:44:40 +1100 Subject: [PATCH 12/17] exploratory ess --- notebooks/load_most_ex_case3a.ipynb | 1536 +++++++++++++++++---------- 1 file changed, 976 insertions(+), 560 deletions(-) diff --git a/notebooks/load_most_ex_case3a.ipynb b/notebooks/load_most_ex_case3a.ipynb index 4b9d8d3..a669765 100644 --- a/notebooks/load_most_ex_case3a.ipynb +++ b/notebooks/load_most_ex_case3a.ipynb @@ -98,7 +98,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 79, "id": "87bc9631", "metadata": {}, "outputs": [ @@ -351,163 +351,6 @@ " display(getattr(cf.reserves, attribute))" ] }, - { - "cell_type": "code", - "execution_count": 8, - "id": "b3ef44f6", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'version': '2',\n", - " 'baseMVA': 100.0,\n", - " 'bus': [[1.0, 3.0, 0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 0.0, 135.0, 1.0, 1.05, 0.95],\n", - " [2.0, 2.0, 0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 0.0, 135.0, 1.0, 1.05, 0.95],\n", - " [3.0, 2.0, 0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 0.0, 135.0, 1.0, 1.05, 0.95]],\n", - " 'gen': [[1.0,\n", - " 125.0,\n", - " 0.0,\n", - " 25.0,\n", - " -25.0,\n", - " 1.0,\n", - " 100.0,\n", - " 1.0,\n", - " 200.0,\n", - " 0.0,\n", - " 0.0,\n", - " 0.0,\n", - " 0.0,\n", - " 0.0,\n", - " 0.0,\n", - " 0.0,\n", - " 0.0,\n", - " 250.0,\n", - " 250.0,\n", - " 0.0,\n", - " 0.0],\n", - " [1.0,\n", - " 125.0,\n", - " 0.0,\n", - " 25.0,\n", - " -25.0,\n", - " 1.0,\n", - " 100.0,\n", - " 1.0,\n", - " 200.0,\n", - " 0.0,\n", - " 0.0,\n", - " 0.0,\n", - " 0.0,\n", - " 0.0,\n", - " 0.0,\n", - " 0.0,\n", - " 0.0,\n", - " 250.0,\n", - " 250.0,\n", - " 0.0,\n", - " 0.0],\n", - " [2.0,\n", - " 200.0,\n", - " 0.0,\n", - " 50.0,\n", - " -50.0,\n", - " 1.0,\n", - " 100.0,\n", - " 1.0,\n", - " 500.0,\n", - " 0.0,\n", - " 0.0,\n", - " 0.0,\n", - " 0.0,\n", - " 0.0,\n", - " 0.0,\n", - " 0.0,\n", - " 0.0,\n", - " 600.0,\n", - " 600.0,\n", - " 0.0,\n", - " 0.0],\n", - " [3.0,\n", - " -450.0,\n", - " 0.0,\n", - " 0.0,\n", - " 0.0,\n", - " 1.0,\n", - " 100.0,\n", - " 1.0,\n", - " 0.0,\n", - " -450.0,\n", - " 0.0,\n", - " 0.0,\n", - " 0.0,\n", - " 0.0,\n", - " 0.0,\n", - " 0.0,\n", - " 0.0,\n", - " 500.0,\n", - " 500.0,\n", - " 0.0,\n", - " 0.0]],\n", - " 'branch': [[1.0,\n", - " 2.0,\n", - " 0.005,\n", - " 0.01,\n", - " 0.0,\n", - " 300.0,\n", - " 300.0,\n", - " 300.0,\n", - " 0.0,\n", - " 0.0,\n", - " 1.0,\n", - " -360.0,\n", - " 360.0],\n", - " [1.0,\n", - " 3.0,\n", - " 0.005,\n", - " 0.01,\n", - " 0.0,\n", - " 240.0,\n", - " 240.0,\n", - " 240.0,\n", - " 0.0,\n", - " 0.0,\n", - " 1.0,\n", - " -360.0,\n", - " 360.0],\n", - " [2.0,\n", - " 3.0,\n", - " 0.005,\n", - " 0.01,\n", - " 0.0,\n", - " 300.0,\n", - " 300.0,\n", - " 300.0,\n", - " 0.0,\n", - " 0.0,\n", - " 1.0,\n", - " -360.0,\n", - " 360.0]],\n", - " 'gencost': [[2.0, 0.0, 0.0, 3.0, 0.1, 0.0, 0.0],\n", - " [2.0, 0.0, 0.0, 3.0, 0.1, 0.0, 0.0],\n", - " [2.0, 0.0, 0.0, 3.0, 0.1, 0.0, 0.0],\n", - " [2.0, 0.0, 0.0, 3.0, 0.0, 1000.0, 0.0]],\n", - " 'reserves': {'zones': [[1.0, 1.0, 1.0, 0.0]],\n", - " 'req': [[150.0]],\n", - " 'cost': [[1.0], [3.0], [5.0]],\n", - " 'qty': [[100.0], [100.0], [200.0]]}}" - ] - }, - "execution_count": 8, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "mpc = cf.to_mpc()\n", - "mpc" - ] - }, { "cell_type": "markdown", "id": "8789a89c", @@ -520,7 +363,7 @@ }, { "cell_type": "code", - "execution_count": 52, + "execution_count": 80, "id": "575be760", "metadata": {}, "outputs": [ @@ -695,7 +538,7 @@ "4 800.0 " ] }, - "execution_count": 52, + "execution_count": 80, "metadata": {}, "output_type": "execute_result" } @@ -713,7 +556,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 81, "id": "63a198e4", "metadata": {}, "outputs": [ @@ -913,7 +756,7 @@ "4 1.0 " ] }, - "execution_count": 58, + "execution_count": 81, "metadata": {}, "output_type": "execute_result" } @@ -926,436 +769,1009 @@ }, { "cell_type": "code", - "execution_count": 54, - "id": "9688f94d", + "execution_count": null, + "id": "13caf9af", "metadata": {}, "outputs": [ { "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
CommitSchedInitialPgRampWearCostCoeffPositiveActiveReservePricePositiveActiveReserveQuantityNegativeActiveReservePriceNegativeActiveReserveQuantityPositiveActiveDeltaPriceNegativeActiveDeltaPricePositiveLoadFollowReservePricePositiveLoadFollowReserveQuantityNegativeLoadFollowReservePriceNegativeLoadFollowReserveQuantityCommitKeyInitialStateMinUpMinDown
gen
11.0125.00.05.000000e+00250.01.000000e+01250.01.000000e-091.000000e-090.000001250.00.000001250.01.0inf1.01.0
21.0125.00.01.000000e-08250.02.000000e-08250.01.000000e-091.000000e-090.000001250.00.000001250.01.0inf3.01.0
31.0200.00.01.500000e+00600.03.000000e+00600.01.000000e-091.000000e-0910.000000100.010.000000250.01.0inf1.01.0
41.0-450.00.01.000000e-08800.02.000000e-08800.01.000000e-091.000000e-090.000001800.00.000001800.02.0inf1.01.0
51.00.00.01.000000e-08200.02.000000e-08200.01.000000e-091.000000e-090.000001200.00.000001200.02.0inf1.01.0
61.00.00.01.000000e-08200.02.000000e-08200.01.000000e-091.000000e-090.000001200.00.000001200.02.0inf1.01.0
\n", + "
" + ], "text/plain": [ - "[5.0,\n", - " {'version': '2',\n", - " 'baseMVA': 100.0,\n", - " 'bus': array([[ 1. , 3. , 0. , 0. , 0. , 0. , 1. , 1. ,\n", - " 0. , 135. , 1. , 1.05, 0.95],\n", - " [ 2. , 2. , 0. , 0. , 0. , 0. , 1. , 1. ,\n", - " 0. , 135. , 1. , 1.05, 0.95],\n", - " [ 3. , 2. , 0. , 0. , 0. , 0. , 1. , 1. ,\n", - " 0. , 135. , 1. , 1.05, 0.95]]),\n", - " 'gen': array([[ 1., 125., 0., 25., -25., 1., 100., 1., 200.,\n", - " 0., 0., 0., 0., 0., 0., 0., 0., 250.,\n", - " 250., 0., 0.],\n", - " [ 1., 125., 0., 25., -25., 1., 100., 1., 200.,\n", - " 0., 0., 0., 0., 0., 0., 0., 0., 250.,\n", - " 250., 0., 0.],\n", - " [ 2., 200., 0., 50., -50., 1., 100., 1., 500.,\n", - " 0., 0., 0., 0., 0., 0., 0., 0., 600.,\n", - " 600., 0., 0.],\n", - " [ 3., -450., 0., 0., 0., 1., 100., 1., 0.,\n", - " -450., 0., 0., 0., 0., 0., 0., 0., 500.,\n", - " 500., 0., 0.],\n", - " [ 2., 0., 0., 50., -50., 1., 100., 1., 100.,\n", - " 0., 0., 0., 0., 0., 0., 0., 0., 200.,\n", - " 200., 0., 0.]]),\n", - " 'branch': array([[ 1.0e+00, 2.0e+00, 5.0e-03, 1.0e-02, 0.0e+00, 3.0e+02,\n", - " 3.0e+02, 3.0e+02, 0.0e+00, 0.0e+00, 1.0e+00, -3.6e+02,\n", - " 3.6e+02],\n", - " [ 1.0e+00, 3.0e+00, 5.0e-03, 1.0e-02, 0.0e+00, 2.4e+02,\n", - " 2.4e+02, 2.4e+02, 0.0e+00, 0.0e+00, 1.0e+00, -3.6e+02,\n", - " 3.6e+02],\n", - " [ 2.0e+00, 3.0e+00, 5.0e-03, 1.0e-02, 0.0e+00, 3.0e+02,\n", - " 3.0e+02, 3.0e+02, 0.0e+00, 0.0e+00, 1.0e+00, -3.6e+02,\n", - " 3.6e+02]]),\n", - " 'gencost': array([[2.e+00, 0.e+00, 0.e+00, 3.e+00, 1.e-01, 0.e+00, 0.e+00],\n", - " [2.e+00, 0.e+00, 0.e+00, 3.e+00, 1.e-01, 0.e+00, 0.e+00],\n", - " [2.e+00, 0.e+00, 0.e+00, 3.e+00, 1.e-01, 0.e+00, 0.e+00],\n", - " [2.e+00, 0.e+00, 0.e+00, 3.e+00, 0.e+00, 1.e+03, 0.e+00],\n", - " [2.e+00, 0.e+00, 0.e+00, 2.e+00, 0.e+00, 0.e+00, 0.e+00]]),\n", - " 'reserves': {'zones': array([[1., 1., 1., 0., 0.]]),\n", - " 'req': 150.0,\n", - " 'cost': array([[1.],\n", - " [3.],\n", - " [5.]]),\n", - " 'qty': array([[100.],\n", - " [100.],\n", - " [200.]])},\n", - " 'genfuel': Cell([['unknown'],\n", - " ['unknown'],\n", - " ['unknown'],\n", - " ['unknown'],\n", - " ['wind']]),\n", - " 'iwind': 5.0},\n", - " {'CommitSched': array([[1.],\n", - " [1.],\n", - " [1.],\n", - " [1.],\n", - " [1.]]),\n", - " 'InitialPg': array([[ 125.],\n", - " [ 125.],\n", - " [ 200.],\n", - " [-450.],\n", - " [ 0.]]),\n", - " 'RampWearCostCoeff': array([[0.],\n", - " [0.],\n", - " [0.],\n", - " [0.],\n", - " [0.]]),\n", - " 'PositiveActiveReservePrice': array([[5.0e+00],\n", - " [1.0e-08],\n", - " [1.5e+00],\n", - " [1.0e-08],\n", - " [1.0e-08]]),\n", - " 'PositiveActiveReserveQuantity': array([[250.],\n", - " [250.],\n", - " [600.],\n", - " [800.],\n", - " [200.]]),\n", - " 'NegativeActiveReservePrice': array([[1.e+01],\n", - " [2.e-08],\n", - " [3.e+00],\n", - " [2.e-08],\n", - " [2.e-08]]),\n", - " 'NegativeActiveReserveQuantity': array([[250.],\n", - " [250.],\n", - " [600.],\n", - " [800.],\n", - " [200.]]),\n", - " 'PositiveActiveDeltaPrice': array([[1.e-09],\n", - " [1.e-09],\n", - " [1.e-09],\n", - " [1.e-09],\n", - " [1.e-09]]),\n", - " 'NegativeActiveDeltaPrice': array([[1.e-09],\n", - " [1.e-09],\n", - " [1.e-09],\n", - " [1.e-09],\n", - " [1.e-09]]),\n", - " 'PositiveLoadFollowReservePrice': array([[1.e-06],\n", - " [1.e-06],\n", - " [1.e+01],\n", - " [1.e-06],\n", - " [1.e-06]]),\n", - " 'PositiveLoadFollowReserveQuantity': array([[250.],\n", - " [250.],\n", - " [100.],\n", - " [800.],\n", - " [200.]]),\n", - " 'NegativeLoadFollowReservePrice': array([[1.e-06],\n", - " [1.e-06],\n", - " [1.e+01],\n", - " [1.e-06],\n", - " [1.e-06]]),\n", - " 'NegativeLoadFollowReserveQuantity': array([[250.],\n", - " [250.],\n", - " [250.],\n", - " [800.],\n", - " [200.]]),\n", - " 'CommitKey': array([[1.],\n", - " [1.],\n", - " [1.],\n", - " [2.],\n", - " [2.]]),\n", - " 'InitialState': array([[inf],\n", - " [inf],\n", - " [inf],\n", - " [inf],\n", - " [inf]]),\n", - " 'MinUp': array([[1.],\n", - " [3.],\n", - " [1.],\n", - " [1.],\n", - " [1.]]),\n", - " 'MinDown': array([[1.],\n", - " [1.],\n", - " [1.],\n", - " [1.],\n", - " [1.]])}]" + " CommitSched InitialPg RampWearCostCoeff PositiveActiveReservePrice \\\n", + "gen \n", + "1 1.0 125.0 0.0 5.000000e+00 \n", + "2 1.0 125.0 0.0 1.000000e-08 \n", + "3 1.0 200.0 0.0 1.500000e+00 \n", + "4 1.0 -450.0 0.0 1.000000e-08 \n", + "5 1.0 0.0 0.0 1.000000e-08 \n", + "6 1.0 0.0 0.0 1.000000e-08 \n", + "\n", + " PositiveActiveReserveQuantity NegativeActiveReservePrice \\\n", + "gen \n", + "1 250.0 1.000000e+01 \n", + "2 250.0 2.000000e-08 \n", + "3 600.0 3.000000e+00 \n", + "4 800.0 2.000000e-08 \n", + "5 200.0 2.000000e-08 \n", + "6 200.0 2.000000e-08 \n", + "\n", + " NegativeActiveReserveQuantity PositiveActiveDeltaPrice \\\n", + "gen \n", + "1 250.0 1.000000e-09 \n", + "2 250.0 1.000000e-09 \n", + "3 600.0 1.000000e-09 \n", + "4 800.0 1.000000e-09 \n", + "5 200.0 1.000000e-09 \n", + "6 200.0 1.000000e-09 \n", + "\n", + " NegativeActiveDeltaPrice PositiveLoadFollowReservePrice \\\n", + "gen \n", + "1 1.000000e-09 0.000001 \n", + "2 1.000000e-09 0.000001 \n", + "3 1.000000e-09 10.000000 \n", + "4 1.000000e-09 0.000001 \n", + "5 1.000000e-09 0.000001 \n", + "6 1.000000e-09 0.000001 \n", + "\n", + " PositiveLoadFollowReserveQuantity NegativeLoadFollowReservePrice \\\n", + "gen \n", + "1 250.0 0.000001 \n", + "2 250.0 0.000001 \n", + "3 100.0 10.000000 \n", + "4 800.0 0.000001 \n", + "5 200.0 0.000001 \n", + "6 200.0 0.000001 \n", + "\n", + " NegativeLoadFollowReserveQuantity CommitKey InitialState MinUp \\\n", + "gen \n", + "1 250.0 1.0 inf 1.0 \n", + "2 250.0 1.0 inf 3.0 \n", + "3 250.0 1.0 inf 1.0 \n", + "4 800.0 2.0 inf 1.0 \n", + "5 200.0 2.0 inf 1.0 \n", + "6 200.0 2.0 inf 1.0 \n", + "\n", + " MinDown \n", + "gen \n", + "1 1.0 \n", + "2 1.0 \n", + "3 1.0 \n", + "4 1.0 \n", + "5 1.0 \n", + "6 1.0 " ] }, - "execution_count": 54, "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "[iwind, mpc, xgd] = m.addwind(\"ex_wind_uc\", cf.to_mpc(), xgdf.to_xgd(), nout=3)\n", - "[iwind, mpc, xgd]" - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "id": "676057c0", + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
GEN_BUSPGQGQMAXQMINVGMBASEGEN_STATUSPMAXPMIN...PC2QC1MINQC1MAXQC2MINQC2MAXRAMP_AGCRAMP_10RAMP_30RAMP_QAPF
gen
11.0125.00.025.0-25.01.0100.01.0200.00.0...0.00.00.00.00.00.0250.0250.00.00.0
21.0125.00.025.0-25.01.0100.01.0200.00.0...0.00.00.00.00.00.0250.0250.00.00.0
32.0200.00.050.0-50.01.0100.01.0500.00.0...0.00.00.00.00.00.0600.0600.00.00.0
43.0-450.00.00.00.01.0100.01.00.0-450.0...0.00.00.00.00.00.0500.0500.00.00.0
52.00.00.050.0-50.01.0100.01.0100.00.0...0.00.00.00.00.00.0200.0200.00.00.0
62.00.00.050.0-50.01.0100.01.0100.00.0...0.00.00.00.00.00.0200.0200.00.00.0
\n", + "

6 rows × 21 columns

\n", + "
" + ], + "text/plain": [ + " GEN_BUS PG QG QMAX QMIN VG MBASE GEN_STATUS PMAX PMIN \\\n", + "gen \n", + "1 1.0 125.0 0.0 25.0 -25.0 1.0 100.0 1.0 200.0 0.0 \n", + "2 1.0 125.0 0.0 25.0 -25.0 1.0 100.0 1.0 200.0 0.0 \n", + "3 2.0 200.0 0.0 50.0 -50.0 1.0 100.0 1.0 500.0 0.0 \n", + "4 3.0 -450.0 0.0 0.0 0.0 1.0 100.0 1.0 0.0 -450.0 \n", + "5 2.0 0.0 0.0 50.0 -50.0 1.0 100.0 1.0 100.0 0.0 \n", + "6 2.0 0.0 0.0 50.0 -50.0 1.0 100.0 1.0 100.0 0.0 \n", + "\n", + " ... PC2 QC1MIN QC1MAX QC2MIN QC2MAX RAMP_AGC RAMP_10 RAMP_30 \\\n", + "gen ... \n", + "1 ... 0.0 0.0 0.0 0.0 0.0 0.0 250.0 250.0 \n", + "2 ... 0.0 0.0 0.0 0.0 0.0 0.0 250.0 250.0 \n", + "3 ... 0.0 0.0 0.0 0.0 0.0 0.0 600.0 600.0 \n", + "4 ... 0.0 0.0 0.0 0.0 0.0 0.0 500.0 500.0 \n", + "5 ... 0.0 0.0 0.0 0.0 0.0 0.0 200.0 200.0 \n", + "6 ... 0.0 0.0 0.0 0.0 0.0 0.0 200.0 200.0 \n", + "\n", + " RAMP_Q APF \n", + "gen \n", + "1 0.0 0.0 \n", + "2 0.0 0.0 \n", + "3 0.0 0.0 \n", + "4 0.0 0.0 \n", + "5 0.0 0.0 \n", + "6 0.0 0.0 \n", + "\n", + "[6 rows x 21 columns]" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# NOTE:\n", + "# 1. iwind, the wind index in cf.gen, can be singleton or 2D array of (n_wind, 1)\n", + "# 2. mpc and xgd are updated with wind generators added\n", + "[iwind, mpc, xgd] = m.addwind(\"ex_wind_uc\", cf.to_mpc(), xgdf.to_xgd(), nout=3)\n", + "xgdf = xGenDataTableFrames(data=xgd)\n", + "cf = CaseFrames(mpc)\n", + "\n", + "display(xgdf)\n", + "display(cf.gen)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "126c9a34", "metadata": {}, "outputs": [ { "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
CommitSchedInitialPgRampWearCostCoeffPositiveActiveReservePricePositiveActiveReserveQuantityNegativeActiveReservePriceNegativeActiveReserveQuantityPositiveActiveDeltaPriceNegativeActiveDeltaPricePositiveLoadFollowReservePricePositiveLoadFollowReserveQuantityNegativeLoadFollowReservePriceNegativeLoadFollowReserveQuantityCommitKeyInitialStateMinUpMinDown
gen
11.0125.00.05.000000e+00250.01.000000e+01250.01.000000e-091.000000e-090.000001250.00.000001250.01.0inf1.01.0
21.0125.00.01.000000e-08250.02.000000e-08250.01.000000e-091.000000e-090.000001250.00.000001250.01.0inf3.01.0
31.0200.00.01.500000e+00600.03.000000e+00600.01.000000e-091.000000e-0910.000000100.010.000000250.01.0inf1.01.0
41.0-450.00.01.000000e-08800.02.000000e-08800.01.000000e-091.000000e-090.000001800.00.000001800.02.0inf1.01.0
51.00.00.01.000000e-08200.02.000000e-08200.01.000000e-091.000000e-090.000001200.00.000001200.02.0inf1.01.0
\n", + "
" + ], "text/plain": [ - "{'type': 'mpcData',\n", - " 'table': 2.0,\n", - " 'rows': 5.0,\n", - " 'col': 9.0,\n", - " 'chgtype': 2.0,\n", - " 'values': array([[0.8 ],\n", - " [0.65],\n", - " [0.6 ],\n", - " [0.82],\n", - " [1. ],\n", - " [0.7 ],\n", - " [0.5 ],\n", - " [0.85],\n", - " [1. ],\n", - " [1.1 ],\n", - " [1.06],\n", - " [0.95]])}" + " CommitSched InitialPg RampWearCostCoeff PositiveActiveReservePrice \\\n", + "gen \n", + "1 1.0 125.0 0.0 5.000000e+00 \n", + "2 1.0 125.0 0.0 1.000000e-08 \n", + "3 1.0 200.0 0.0 1.500000e+00 \n", + "4 1.0 -450.0 0.0 1.000000e-08 \n", + "5 1.0 0.0 0.0 1.000000e-08 \n", + "\n", + " PositiveActiveReserveQuantity NegativeActiveReservePrice \\\n", + "gen \n", + "1 250.0 1.000000e+01 \n", + "2 250.0 2.000000e-08 \n", + "3 600.0 3.000000e+00 \n", + "4 800.0 2.000000e-08 \n", + "5 200.0 2.000000e-08 \n", + "\n", + " NegativeActiveReserveQuantity PositiveActiveDeltaPrice \\\n", + "gen \n", + "1 250.0 1.000000e-09 \n", + "2 250.0 1.000000e-09 \n", + "3 600.0 1.000000e-09 \n", + "4 800.0 1.000000e-09 \n", + "5 200.0 1.000000e-09 \n", + "\n", + " NegativeActiveDeltaPrice PositiveLoadFollowReservePrice \\\n", + "gen \n", + "1 1.000000e-09 0.000001 \n", + "2 1.000000e-09 0.000001 \n", + "3 1.000000e-09 10.000000 \n", + "4 1.000000e-09 0.000001 \n", + "5 1.000000e-09 0.000001 \n", + "\n", + " PositiveLoadFollowReserveQuantity NegativeLoadFollowReservePrice \\\n", + "gen \n", + "1 250.0 0.000001 \n", + "2 250.0 0.000001 \n", + "3 100.0 10.000000 \n", + "4 800.0 0.000001 \n", + "5 200.0 0.000001 \n", + "\n", + " NegativeLoadFollowReserveQuantity CommitKey InitialState MinUp \\\n", + "gen \n", + "1 250.0 1.0 inf 1.0 \n", + "2 250.0 1.0 inf 3.0 \n", + "3 250.0 1.0 inf 1.0 \n", + "4 800.0 2.0 inf 1.0 \n", + "5 200.0 2.0 inf 1.0 \n", + "\n", + " MinDown \n", + "gen \n", + "1 1.0 \n", + "2 1.0 \n", + "3 1.0 \n", + "4 1.0 \n", + "5 1.0 " ] }, - "execution_count": 10, "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "profiles = m.getprofiles(\"ex_wind_profile_d\", iwind)\n", - "profiles" - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "id": "2bcf5d64", - "metadata": {}, - "outputs": [ + "output_type": "display_data" + }, { "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
GEN_BUSPGQGQMAXQMINVGMBASEGEN_STATUSPMAXPMIN...PC2QC1MINQC1MAXQC2MINQC2MAXRAMP_AGCRAMP_10RAMP_30RAMP_QAPF
gen
11.0125.00.025.0-25.01.0100.01.0200.00.0...0.00.00.00.00.00.0250.0250.00.00.0
21.0125.00.025.0-25.01.0100.01.0200.00.0...0.00.00.00.00.00.0250.0250.00.00.0
32.0200.00.050.0-50.01.0100.01.0500.00.0...0.00.00.00.00.00.0600.0600.00.00.0
43.0-450.00.00.00.01.0100.01.00.0-450.0...0.00.00.00.00.00.0500.0500.00.00.0
52.00.00.050.0-50.01.0100.01.0100.00.0...0.00.00.00.00.00.0200.0200.00.00.0
\n", + "

5 rows × 21 columns

\n", + "
" + ], "text/plain": [ - "2x1 StructArray containing the fields:\n", - " type\n", - " table\n", - " rows\n", - " col\n", - " chgtype\n", - " values" + " GEN_BUS PG QG QMAX QMIN VG MBASE GEN_STATUS PMAX PMIN \\\n", + "gen \n", + "1 1.0 125.0 0.0 25.0 -25.0 1.0 100.0 1.0 200.0 0.0 \n", + "2 1.0 125.0 0.0 25.0 -25.0 1.0 100.0 1.0 200.0 0.0 \n", + "3 2.0 200.0 0.0 50.0 -50.0 1.0 100.0 1.0 500.0 0.0 \n", + "4 3.0 -450.0 0.0 0.0 0.0 1.0 100.0 1.0 0.0 -450.0 \n", + "5 2.0 0.0 0.0 50.0 -50.0 1.0 100.0 1.0 100.0 0.0 \n", + "\n", + " ... PC2 QC1MIN QC1MAX QC2MIN QC2MAX RAMP_AGC RAMP_10 RAMP_30 \\\n", + "gen ... \n", + "1 ... 0.0 0.0 0.0 0.0 0.0 0.0 250.0 250.0 \n", + "2 ... 0.0 0.0 0.0 0.0 0.0 0.0 250.0 250.0 \n", + "3 ... 0.0 0.0 0.0 0.0 0.0 0.0 600.0 600.0 \n", + "4 ... 0.0 0.0 0.0 0.0 0.0 0.0 500.0 500.0 \n", + "5 ... 0.0 0.0 0.0 0.0 0.0 0.0 200.0 200.0 \n", + "\n", + " RAMP_Q APF \n", + "gen \n", + "1 0.0 0.0 \n", + "2 0.0 0.0 \n", + "3 0.0 0.0 \n", + "4 0.0 0.0 \n", + "5 0.0 0.0 \n", + "\n", + "[5 rows x 21 columns]" ] }, - "execution_count": 11, "metadata": {}, - "output_type": "execute_result" + "output_type": "display_data" } ], "source": [ - "profiles = m.getprofiles(\"ex_load_profile\", profiles)\n", - "profiles" + "# NOTE:\n", + "# 1. iess, the ess index in cf.gen, can be singleton or 2D array of (n_ess, 1)\n", + "# 2. mpc and xgd are updated with ess added\n", + "[iess, mpc, xgd, sd] = m.addstorage(\"ex_storage\", mpc, xgd, nout=4)\n", + "xgdf = xGenDataTableFrames(data=xgd)\n", + "cf = CaseFrames(mpc)\n", + "# TODO: convert sd into DataFrame\n", + "\n", + "display(xgdf)\n", + "display(cf.gen)" ] }, { "cell_type": "code", - "execution_count": 12, - "id": "ee0cec81", + "execution_count": 89, + "id": "676057c0", "metadata": {}, "outputs": [], "source": [ - "# TODO: example on `mdi = loadmd(mpc, transmat, xgd, [], 'ex_contab', profiles);`" - ] - }, - { - "cell_type": "code", - "execution_count": 13, - "id": "c9726cc1", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "[6.0,\n", - " {'version': '2',\n", - " 'baseMVA': 100.0,\n", - " 'bus': array([[ 1. , 3. , 0. , 0. , 0. , 0. , 1. , 1. ,\n", - " 0. , 135. , 1. , 1.05, 0.95],\n", - " [ 2. , 2. , 0. , 0. , 0. , 0. , 1. , 1. ,\n", - " 0. , 135. , 1. , 1.05, 0.95],\n", - " [ 3. , 2. , 0. , 0. , 0. , 0. , 1. , 1. ,\n", - " 0. , 135. , 1. , 1.05, 0.95]]),\n", - " 'gen': array([[ 1., 125., 0., 25., -25., 1., 100., 1., 200.,\n", - " 0., 0., 0., 0., 0., 0., 0., 0., 250.,\n", - " 250., 0., 0.],\n", - " [ 1., 125., 0., 25., -25., 1., 100., 1., 200.,\n", - " 0., 0., 0., 0., 0., 0., 0., 0., 250.,\n", - " 250., 0., 0.],\n", - " [ 2., 200., 0., 50., -50., 1., 100., 1., 500.,\n", - " 0., 0., 0., 0., 0., 0., 0., 0., 600.,\n", - " 600., 0., 0.],\n", - " [ 3., -450., 0., 0., 0., 1., 100., 1., 0.,\n", - " -450., 0., 0., 0., 0., 0., 0., 0., 500.,\n", - " 500., 0., 0.],\n", - " [ 2., 0., 0., 50., -50., 1., 100., 1., 100.,\n", - " 0., 0., 0., 0., 0., 0., 0., 0., 200.,\n", - " 200., 0., 0.],\n", - " [ 3., 0., 0., 0., 0., 1., 100., 1., 80.,\n", - " -80., 0., 0., 0., 0., 0., 0., 0., 20.,\n", - " 20., 0., 0.]]),\n", - " 'branch': array([[ 1.0e+00, 2.0e+00, 5.0e-03, 1.0e-02, 0.0e+00, 3.0e+02,\n", - " 3.0e+02, 3.0e+02, 0.0e+00, 0.0e+00, 1.0e+00, -3.6e+02,\n", - " 3.6e+02],\n", - " [ 1.0e+00, 3.0e+00, 5.0e-03, 1.0e-02, 0.0e+00, 2.4e+02,\n", - " 2.4e+02, 2.4e+02, 0.0e+00, 0.0e+00, 1.0e+00, -3.6e+02,\n", - " 3.6e+02],\n", - " [ 2.0e+00, 3.0e+00, 5.0e-03, 1.0e-02, 0.0e+00, 3.0e+02,\n", - " 3.0e+02, 3.0e+02, 0.0e+00, 0.0e+00, 1.0e+00, -3.6e+02,\n", - " 3.6e+02]]),\n", - " 'gencost': array([[2.e+00, 0.e+00, 0.e+00, 3.e+00, 1.e-01, 0.e+00, 0.e+00],\n", - " [2.e+00, 0.e+00, 0.e+00, 3.e+00, 1.e-01, 0.e+00, 0.e+00],\n", - " [2.e+00, 0.e+00, 0.e+00, 3.e+00, 1.e-01, 0.e+00, 0.e+00],\n", - " [2.e+00, 0.e+00, 0.e+00, 3.e+00, 0.e+00, 1.e+03, 0.e+00],\n", - " [2.e+00, 0.e+00, 0.e+00, 2.e+00, 0.e+00, 0.e+00, 0.e+00],\n", - " [2.e+00, 0.e+00, 0.e+00, 2.e+00, 0.e+00, 0.e+00, 0.e+00]]),\n", - " 'reserves': {'zones': array([[1., 1., 1., 0., 0., 0.]]),\n", - " 'req': 150.0,\n", - " 'cost': array([[1.],\n", - " [3.],\n", - " [5.]]),\n", - " 'qty': array([[100.],\n", - " [100.],\n", - " [200.]])},\n", - " 'genfuel': Cell([['unknown'],\n", - " ['unknown'],\n", - " ['unknown'],\n", - " ['unknown'],\n", - " ['wind'],\n", - " ['ess']]),\n", - " 'iwind': 5.0,\n", - " 'iess': 6.0},\n", - " {'CommitSched': array([[1.],\n", - " [1.],\n", - " [1.],\n", - " [1.],\n", - " [1.],\n", - " [1.]]),\n", - " 'InitialPg': array([[ 125.],\n", - " [ 125.],\n", - " [ 200.],\n", - " [-450.],\n", - " [ 0.],\n", - " [ 0.]]),\n", - " 'RampWearCostCoeff': array([[0.],\n", - " [0.],\n", - " [0.],\n", - " [0.],\n", - " [0.],\n", - " [0.]]),\n", - " 'PositiveActiveReservePrice': array([[5.0e+00],\n", - " [1.0e-08],\n", - " [1.5e+00],\n", - " [1.0e-08],\n", - " [1.0e-08],\n", - " [1.0e-08]]),\n", - " 'PositiveActiveReserveQuantity': array([[250.],\n", - " [250.],\n", - " [600.],\n", - " [800.],\n", - " [200.],\n", - " [160.]]),\n", - " 'NegativeActiveReservePrice': array([[1.e+01],\n", - " [2.e-08],\n", - " [3.e+00],\n", - " [2.e-08],\n", - " [2.e-08],\n", - " [2.e-08]]),\n", - " 'NegativeActiveReserveQuantity': array([[250.],\n", - " [250.],\n", - " [600.],\n", - " [800.],\n", - " [200.],\n", - " [160.]]),\n", - " 'PositiveActiveDeltaPrice': array([[1.e-09],\n", - " [1.e-09],\n", - " [1.e-09],\n", - " [1.e-09],\n", - " [1.e-09],\n", - " [1.e-09]]),\n", - " 'NegativeActiveDeltaPrice': array([[1.e-09],\n", - " [1.e-09],\n", - " [1.e-09],\n", - " [1.e-09],\n", - " [1.e-09],\n", - " [1.e-09]]),\n", - " 'PositiveLoadFollowReservePrice': array([[1.e-06],\n", - " [1.e-06],\n", - " [1.e+01],\n", - " [1.e-06],\n", - " [1.e-06],\n", - " [1.e-06]]),\n", - " 'PositiveLoadFollowReserveQuantity': array([[250.],\n", - " [250.],\n", - " [100.],\n", - " [800.],\n", - " [200.],\n", - " [160.]]),\n", - " 'NegativeLoadFollowReservePrice': array([[1.e-06],\n", - " [1.e-06],\n", - " [1.e+01],\n", - " [1.e-06],\n", - " [1.e-06],\n", - " [1.e-06]]),\n", - " 'NegativeLoadFollowReserveQuantity': array([[250.],\n", - " [250.],\n", - " [250.],\n", - " [800.],\n", - " [200.],\n", - " [160.]]),\n", - " 'CommitKey': array([[1.],\n", - " [1.],\n", - " [1.],\n", - " [2.],\n", - " [2.],\n", - " [2.]]),\n", - " 'InitialState': array([[inf],\n", - " [inf],\n", - " [inf],\n", - " [inf],\n", - " [inf],\n", - " [inf]]),\n", - " 'MinUp': array([[1.],\n", - " [3.],\n", - " [1.],\n", - " [1.],\n", - " [1.],\n", - " [1.]]),\n", - " 'MinDown': array([[1.],\n", - " [1.],\n", - " [1.],\n", - " [1.],\n", - " [1.],\n", - " [1.]])},\n", - " {'UnitIdx': 6.0,\n", - " 'ExpectedTerminalStorageAim': [],\n", - " 'ExpectedTerminalStorageMin': [],\n", - " 'ExpectedTerminalStorageMax': [],\n", - " 'rho': 0.0,\n", - " 'TerminalChargingPrice0': [],\n", - " 'TerminalDischargingPrice0': [],\n", - " 'TerminalChargingPriceK': [],\n", - " 'TerminalDischargingPriceK': [],\n", - " 'OutEff': 1.0,\n", - " 'InEff': 1.0,\n", - " 'LossFactor': 1e-05,\n", - " 'InitialStorage': 0.0,\n", - " 'InitialStorageLowerBound': 0.0,\n", - " 'InitialStorageUpperBound': 200.0,\n", - " 'InitialStorageCost': 45.166667,\n", - " 'TerminalStoragePrice': 45.166667,\n", - " 'MinStorageLevel': 0.0,\n", - " 'MaxStorageLevel': 200.0}]" - ] - }, - "execution_count": 13, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "[iess, mpc, xgd, sd] = m.addstorage(\"ex_storage\", mpc, xgd, nout=4)\n", - "[iess, mpc, xgd, sd]" + "profiles = m.getprofiles(\"ex_wind_profile_d\", iwind) # add wind profiles\n", + "profiles = m.getprofiles(\"ex_load_profile\", profiles) # update profiles with load\n", + "wind_profile, load_profile = profiles[0], profiles[1]" ] }, { From a5f7a2a94551ed899f27834d04bcdf4fe016f013 Mon Sep 17 00:00:00 2001 From: Muhammad Yasirroni Date: Mon, 19 Jan 2026 10:50:09 +1100 Subject: [PATCH 13/17] initial DataFramesStruct and DataFrameStruct --- matpowercaseframes/core.py | 198 ++++++++++++++++++++----------------- 1 file changed, 105 insertions(+), 93 deletions(-) diff --git a/matpowercaseframes/core.py b/matpowercaseframes/core.py index b6e8d3c..833d4ec 100644 --- a/matpowercaseframes/core.py +++ b/matpowercaseframes/core.py @@ -116,6 +116,106 @@ def __repr__(self): return f"{self.__class__.__name__}(attributes=[{attrs}])" +class DataFrameStruct(DataFramesStruct): + def __init__(self, data, index, colnames): + super().__init__() + + if data is not None: + if isinstance(data, dict): + self.setattr( + "table", + pd.DataFrame( + {k: np.asarray(v).flatten() for k, v in data.items()}, + index=index, + ), + ) + elif isinstance(data, (np.ndarray, list)): + self.setattr("table", pd.DataFrame(data, columns=colnames, index=index)) + elif isinstance(data, str): + # TODO: + # 1. Support read from file. + # 2. Differentiate between xdg_table and xdg + # xgdt = m.loadgenericdata( + # data, 'struct', {'colnames', 'data'}, 'xgd_table', cf.to_mpc() + # ) + raise NotImplementedError( + "Reading DataTableFrames from file not yet implemented." + ) + else: + raise TypeError(f"Unsupported data type: {type(data)}") + + if index is None: + self.table.index = pd.RangeIndex( + start=1, stop=len(self.table) + 1, name="gen" + ) + else: + self.setattr("table", pd.DataFrame(columns=colnames, index=index)) + + @property + def colnames(self): + """Get column names as 2D array (MATPOWER xgdt format).""" + return np.atleast_2d(self.table.columns) + + @property + def data(self): + """Get data as NumPy array.""" + return self.table.to_numpy() + + @property + def df(self): + return self.table + + def to_df(self): + return self.table + + def to_dict(self): + return { + col: self.table[col].values.reshape(-1, 1) for col in self.table.columns + } + + def infer_numpy(self): + df = self._infer_numpy(self.table) + self.table = df + + def __getattr__(self, name): + """Delegate attribute access to the underlying DataFrame.""" + # if attribute not found in self, + if name in self.table.columns: + # try to get it from self.table[name] + return np.atleast_2d(self.table[name].values) + else: + # try to get it from self.table + return getattr(self.table, name) + + def _repr_html_(self): + """HTML representation for Jupyter notebooks.""" + return self.table._repr_html_() + + def __repr__(self): + """String representation.""" + return repr(self.table) + + def __str__(self): + """String representation for print().""" + return str(self.table) + + def __getitem__(self, key): + """Allow indexing like a DataFrame: obj['col'] or obj[0:5].""" + return self.table[key] + + def __setitem__(self, key, value): + """Allow setting values like a DataFrame: obj['col'] = values.""" + self.table[key] = value + + def __len__(self): + """Return number of rows.""" + return len(self.table) + + def __iter__(self): + """Iterate over column names like a DataFrame.""" + return iter(self.table) + + class ReservesFrames(DataFramesStruct): """A struct-like container for reserves data, similar to CaseFrames.""" @@ -964,7 +1064,7 @@ def reserves_data_to_dataframes(reserves): return dfs -class xGenDataTableFrames(DataFramesStruct): +class xGenDataTableFrames(DataFrameStruct): """ A struct-like and DataFrame-like container for xGenData with MATPOWER compatibility. Support standard DataFrame operations. @@ -972,15 +1072,13 @@ class xGenDataTableFrames(DataFramesStruct): def __init__(self, data=None, colnames=None, index=None): """ - Initialize xGenDataTableFrames with optional data. + Initialize DataTableFrames with optional data. Args: - data (np.ndarray | list | dict, optional): Data for xGenDataTableFrames. + data (np.ndarray | list | dict, optional): Data for DataTableFrames. colnames (list, optional): Column names. Defaults to None. index (pd.Index, optional): Row index. Defaults to None. """ - super().__init__() - if data is not None and colnames is None: if isinstance(data, dict): colnames = list(data.keys()) @@ -999,53 +1097,7 @@ def __init__(self, data=None, colnames=None, index=None): # if col in COLUMNS["xgd_table"] # ] - if data is not None: - if isinstance(data, dict): - self.setattr( - "table", - pd.DataFrame( - {k: np.asarray(v).flatten() for k, v in data.items()}, - index=index, - ), - ) - elif isinstance(data, (np.ndarray, list)): - self.setattr("table", pd.DataFrame(data, columns=colnames, index=index)) - elif isinstance(data, str): - # TODO: - # 1. Support read from file. - # 2. Differentiate between xdg_table and xdg - # xgdt = m.loadgenericdata( - # data, 'struct', {'colnames', 'data'}, 'xgd_table', cf.to_mpc() - # ) - raise NotImplementedError( - "Reading xGenDataTableFrames from file not yet implemented." - ) - else: - raise TypeError(f"Unsupported data type: {type(data)}") - - if index is None: - self.table.index = pd.RangeIndex( - start=1, stop=len(self.table) + 1, name="gen" - ) - else: - self.setattr("table", pd.DataFrame(columns=colnames, index=index)) - - @property - def colnames(self): - """Get column names as 2D array (MATPOWER xgdt format).""" - return np.atleast_2d(self.table.columns) - - @property - def data(self): - """Get data as NumPy array.""" - return self.table.to_numpy() - - @property - def df(self): - return self.table - - def to_df(self): - return self.table + super().__init__(data=data, index=index, colnames=colnames) def to_dict(self): """ @@ -1079,44 +1131,4 @@ def to_xgd(self): Returns: dict: Dictionary where keys are column names and values are 2D arrays (n, 1) """ - return { - col: self.table[col].values.reshape(-1, 1) for col in self.table.columns - } - - def __getattr__(self, name): - """Delegate attribute access to the underlying DataFrame.""" - # if attribute not found in self, - if name in self.table.columns: - # try to get it from self.table[name] - return np.atleast_2d(self.table[name].values) - else: - # try to get it from self.table - return getattr(self.table, name) - - def _repr_html_(self): - """HTML representation for Jupyter notebooks.""" - return self.table._repr_html_() - - def __repr__(self): - """String representation.""" - return repr(self.table) - - def __str__(self): - """String representation for print().""" - return str(self.table) - - def __getitem__(self, key): - """Allow indexing like a DataFrame: obj['col'] or obj[0:5].""" - return self.table[key] - - def __setitem__(self, key, value): - """Allow setting values like a DataFrame: obj['col'] = values.""" - self.table[key] = value - - def __len__(self): - """Return number of rows.""" - return len(self.table) - - def __iter__(self): - """Iterate over column names like a DataFrame.""" - return iter(self.table) + return super().to_dict() From 1acfee143dc0a85394dda5ea40ab59280f05c745 Mon Sep 17 00:00:00 2001 From: Muhammad Yasirroni Date: Mon, 19 Jan 2026 11:05:25 +1100 Subject: [PATCH 14/17] reafactor docstring --- matpowercaseframes/core.py | 421 ++++++++++++++++++++++++++++++------- 1 file changed, 348 insertions(+), 73 deletions(-) diff --git a/matpowercaseframes/core.py b/matpowercaseframes/core.py index 833d4ec..e22592f 100644 --- a/matpowercaseframes/core.py +++ b/matpowercaseframes/core.py @@ -22,16 +22,21 @@ class DataFramesStruct: - """Base class for struct-like containers with DataFrames.""" + """ + Base class for struct-like containers with DataFrames. + """ def __init__(self): - """Initialize the base struct with an empty attributes list.""" + """ + Initialize the base struct with an empty attributes list. + """ self._attributes = [] def setattr(self, name, value): """ Set attribute and track it in _attributes list. + Args: name (str): Attribute name. value: Attribute value. @@ -46,7 +51,8 @@ def to_dict(self): Returns: - dict: Dictionary with attribute names as keys and their data as values. + dict: + Dictionary with attribute names as keys and their data as values. """ # TODO: support mpc = cf.to_dict() with reserves data data = {} @@ -79,11 +85,15 @@ def _infer_numpy(df): """ Infer and convert the data types of a DataFrame to NumPy-compatible types. + Args: - df (pd.DataFrame): DataFrame to be processed. + df (pd.DataFrame): + DataFrame to be processed. + Returns: - pd.DataFrame: DataFrame with updated data types. + pd.DataFrame: + DataFrame with updated data types. """ df = df.convert_dtypes() @@ -105,19 +115,49 @@ def attributes(self): """ List of attributes in this struct object. + Returns: list: List of attribute names. """ return self._attributes def __repr__(self): - """String representation of the struct.""" + """ + String representation of the struct. + + + Returns: + str: String representation showing class name and attributes. + """ attrs = ", ".join(self._attributes) return f"{self.__class__.__name__}(attributes=[{attrs}])" class DataFrameStruct(DataFramesStruct): + """ + A struct-like and DataFrame-like container with MATPOWER compatibility. + """ + def __init__(self, data, index, colnames): + """ + Initialize DataFrameStruct with data, index, and column names. + + + Args: + data (dict | np.ndarray | list | str | None): + Data for the table. + index (pd.Index | None): + Row index for the DataFrame. + colnames (list | None): + Column names for the DataFrame. + + + Raises: + NotImplementedError: + If data is a string (file reading not yet supported). + TypeError: + If data type is not supported. + """ super().__init__() if data is not None: @@ -153,32 +193,85 @@ def __init__(self, data, index, colnames): @property def colnames(self): - """Get column names as 2D array (MATPOWER xgdt format).""" + """ + Get column names as 2D array (MATPOWER xgdt format). + + + Returns: + np.ndarray: + 2D array of column names. + """ return np.atleast_2d(self.table.columns) @property def data(self): - """Get data as NumPy array.""" + """ + Get data as NumPy array. + + + Returns: + np.ndarray: + Data array. + """ return self.table.to_numpy() @property def df(self): + """ + Get the underlying DataFrame. + + + Returns: + pd.DataFrame: + The underlying DataFrame. + """ return self.table def to_df(self): + """ + Convert to DataFrame. + + + Returns: + pd.DataFrame: + The underlying DataFrame. + """ return self.table def to_dict(self): + """ + Convert to dictionary with column names as keys and 2D arrays as values. + + + Returns: + dict: + Dictionary with column data as 2D arrays. + """ return { col: self.table[col].values.reshape(-1, 1) for col in self.table.columns } def infer_numpy(self): + """ + Infer and convert data types to NumPy-compatible types. + """ df = self._infer_numpy(self.table) self.table = df def __getattr__(self, name): - """Delegate attribute access to the underlying DataFrame.""" + """ + Delegate attribute access to the underlying DataFrame. + + + Args: + name (str): + Attribute name to access. + + + Returns: + np.ndarray | Any: + Column data as 2D array or DataFrame attribute. + """ # if attribute not found in self, if name in self.table.columns: # try to get it from self.table[name] @@ -188,44 +281,111 @@ def __getattr__(self, name): return getattr(self.table, name) def _repr_html_(self): - """HTML representation for Jupyter notebooks.""" + """ + HTML representation for Jupyter notebooks. + + + Returns: + str: + HTML representation of the DataFrame. + """ return self.table._repr_html_() def __repr__(self): - """String representation.""" + """ + String representation. + + + Returns: + str: + String representation of the DataFrame. + """ return repr(self.table) def __str__(self): - """String representation for print().""" + """ + String representation for print(). + + + Returns: + str: + String representation of the DataFrame. + """ return str(self.table) def __getitem__(self, key): - """Allow indexing like a DataFrame: obj['col'] or obj[0:5].""" + """ + Allow indexing like a DataFrame. + + + Args: + key: + Index or column key. + + + Returns: + pd.Series | pd.DataFrame: + Indexed data. + """ return self.table[key] def __setitem__(self, key, value): - """Allow setting values like a DataFrame: obj['col'] = values.""" + """ + Allow setting values like a DataFrame. + + + Args: + key: + Column key. + value: + Values to set. + """ self.table[key] = value def __len__(self): - """Return number of rows.""" + """ + Return number of rows. + + + Returns: + int: + Number of rows in the DataFrame. + """ return len(self.table) def __iter__(self): - """Iterate over column names like a DataFrame.""" + """ + Iterate over column names like a DataFrame. + + + Returns: + Iterator: + Iterator over column names. + """ return iter(self.table) class ReservesFrames(DataFramesStruct): - """A struct-like container for reserves data, similar to CaseFrames.""" + """ + A struct-like container for reserves data, similar to CaseFrames. + """ def __init__(self, data=None, allow_any_keys=False): """ Initialize ReservesFrames with optional data. + Args: - data (dict, optional): Dictionary containing reserves DataFrames. - Expected keys: 'zones', 'req', 'cost', 'qty' + data (dict | None): + Dictionary containing reserves DataFrames. + Expected keys: 'zones', 'req', 'cost', 'qty'. + allow_any_keys (bool): + Whether to allow any keys beyond expected keys. + + + Raises: + TypeError: + If data is not a dictionary. """ super().__init__() if data is not None: @@ -242,6 +402,10 @@ def __init__(self, data=None, allow_any_keys=False): class CaseFrames(DataFramesStruct): + """ + Main class for MATPOWER case data representation using DataFrames. + """ + def __init__( self, data=None, @@ -256,14 +420,13 @@ def __init__( """ Load data and initialize the CaseFrames class. + Args: - data (str | dict | oct2py.io.Struct | np.ndarray): + data (str | dict | oct2py.io.Struct | np.ndarray | None, optional): - str: File path to MATPOWER case name, .m file, or .xlsx file. - dict: Data from a structured dictionary. - oct2py.io.Struct: Octave's oct2py struct. - np.ndarray: Structured NumPy array with named fields. - update_index (bool, optional): - Whether to update the index numbering. Defaults to True. load_case_engine (object, optional): External engine used to call MATPOWER `loadcase` (e.g. Octave). Defaults to None. If None, parse data using matpowercaseframes.reader.parse_file. @@ -276,14 +439,18 @@ def __init__( allow_any_keys (bool, optional): Whether to allow any keys beyond the predefined ATTRIBUTES. Defaults to False. + update_index (bool, optional): + Whether to update the index numbering. Defaults to True. columns_templates (dict, optional): Custom column templates for DataFrames. Defaults to None. reset_index (bool, optional): Whether to reset indices to 0-based numbering. Defaults to False. Raises: - TypeError: If the input data format is unsupported. - FileNotFoundError: If the specified file cannot be found. + TypeError: + If the input data format is unsupported. + FileNotFoundError: + If the specified file cannot be found. """ # TODO: support Path object super().__init__() @@ -312,6 +479,29 @@ def _read_data( suffix="", allow_any_keys=False, ): + """ + Read data from various sources and populate the CaseFrames object. + + + Args: + data (str | dict | np.ndarray | None, optional): + Data source. + load_case_engine (object | None, optional): + External engine for loading MATPOWER cases. + prefix (str, optional): + Prefix for attribute names. + suffix (str, optional): + Suffix for attribute names. + allow_any_keys (bool, optional): + Whether to allow any keys beyond ATTRIBUTES. + + + Raises: + FileNotFoundError: + If file path is invalid. + TypeError: + If data type is not supported. + """ if isinstance(data, str): # TODO: support Path # TYPE: str of path @@ -384,17 +574,28 @@ def _read_data( def setattr_as_df(self, name, value, columns_template=None): """ - Convert value to df and assign to attributes. + Convert value to DataFrame and assign to attributes. + Args: - name (str): Attribute name. + name (str): + Attribute name. value: Data that can be converted into DataFrame. - columns_template: List of column names used for DataFrame column header. + columns_template (list | None): + List of column names used for DataFrame column header. """ df = self._get_dataframe(name, value, columns_template=columns_template) self.setattr(name, df) def update_columns_templates(self, columns_templates): + """ + Update the column templates dictionary. + + + Args: + columns_templates (dict): + Dictionary of column templates to update. + """ self.columns_templates.update(columns_templates) @staticmethod @@ -402,12 +603,15 @@ def _get_path(path): """ Determine the correct file path for the given input. + Args: path (str): File path, directory path, or MATPOWER case name. + Returns: str: Resolved file path or directory path. + Raises: FileNotFoundError: If the file or MATPOWER case cannot be found. """ @@ -448,11 +652,14 @@ def _read_matpower(self, filepath, allow_any_keys=False): """ Read and parse a MATPOWER file. - Old attribute is not guaranted to be replaced in re-read. This method is + Old attribute is not guaranteed to be replaced in re-read. This method is intended to be used only during initialization. Args: - filepath (str): Path to the MATPOWER file. + filepath (str): + Path to the MATPOWER file. + allow_any_keys (bool): + Whether to allow any keys beyond ATTRIBUTES. """ # TODO: support reserves with open(filepath) as f: @@ -481,9 +688,12 @@ def _read_oct2py_struct(self, struct, allow_any_keys=False): """ Read data from an Octave struct or dictionary. + Args: struct (dict): Data in structured dictionary or Octave's oct2py struct format. + allow_any_keys (bool): + Whether to allow any keys beyond ATTRIBUTES. """ self.name = "" @@ -511,8 +721,12 @@ def _read_numpy_struct(self, array, allow_any_keys=False): """ Read data from a structured NumPy array. + Args: - array (np.ndarray): Structured NumPy array with named fields. + array (np.ndarray): + Structured NumPy array with named fields. + allow_any_keys (bool): + Whether to allow any keys beyond ATTRIBUTES. """ # TODO: support reserves self.name = "" @@ -535,10 +749,16 @@ def _read_excel(self, filepath, prefix="", suffix="", allow_any_keys=False): """ Read data from an Excel file. + Args: - filepath (str): File path for the Excel file. - prefix (str): Sheet prefix for each attribute in the Excel file. - suffix (str): Sheet suffix for each attribute in the Excel file. + filepath (str): + File path for the Excel file. + prefix (str): + Sheet prefix for each attribute in the Excel file. + suffix (str): + Sheet suffix for each attribute in the Excel file. + allow_any_keys (bool): + Whether to allow any keys beyond ATTRIBUTES. """ # TODO: support reserves sheets = pd.read_excel(filepath, index_col=0, sheet_name=None) @@ -582,11 +802,16 @@ def _read_csv_dir(self, dirpath, prefix="", suffix="", allow_any_keys=False): """ Read data from a directory of CSV files. + Args: - dirpath (str): Directory path containing the CSV files. - prefix (str): File prefix for each attribute CSV file. - suffix (str): File suffix for each attribute CSV file. - allow_any_keys (bool): Whether to allow any keys beyond ATTRIBUTES. + dirpath (str): + Directory path containing the CSV files. + prefix (str): + File prefix for each attribute CSV file. + suffix (str): + File suffix for each attribute CSV file. + allow_any_keys (bool): + Whether to allow any keys beyond ATTRIBUTES. """ # TODO: support reserves # create a dictionary mapping attribute names to file paths @@ -639,14 +864,22 @@ def _get_dataframe(self, attribute, data, n_cols=None, columns_template=None): """ Create a DataFrame with proper columns from raw data. + Args: - attribute (str): Name of the attribute. - data (list | np.ndarray): Data for the attribute. - n_cols (int): Number of columns in the data. + attribute (str): + Name of the attribute. + data (list | np.ndarray): + Data for the attribute. + n_cols (int | None): + Number of columns in the data. + columns_template (list | None): + Custom column template. + Returns: pd.DataFrame: DataFrame with appropriate columns. + Raises: IndexError: If the number of columns in the data exceeds the expected number. @@ -703,8 +936,12 @@ def _get_dataframe(self, attribute, data, n_cols=None, columns_template=None): def _update_index(self, allow_any_keys=False): """ - Update the index of the bus, branch, and generator tables based on naming. If - naming is not available, index start from 1 to N. + Update the index of the bus, branch, and generator tables based on naming. + + + Args: + allow_any_keys (bool): + Whether to update index for any keys beyond standard attributes. """ for attribute, attribute_name in zip( ["bus", "branch", "gen"], ["bus_name", "branch_name", "gen_name"] @@ -746,6 +983,9 @@ def _update_index(self, allow_any_keys=False): self._update_index_any() def _update_index_any(self): + """ + Update index for any additional attributes that are DataFrames or Series. + """ for attribute in self._attributes: if attribute in ["bus", "branch", "gen", "gencost", "reserves"]: continue @@ -766,19 +1006,11 @@ def reset_index(self): """ Reset indices and remap bus-related indices to 0-based values. - This method ensures that: - 1. All DataFrames in the case have their row indices reset. - 2. Bus numbers (BUS_I) are renumbered from 0 to n-1. - 3. References to buses in branch (F_BUS, T_BUS) and gen (GEN_BUS) tables - are updated consistently with the new numbering. Notes: - - This method requires `infer_numpy` to be called beforehand, - as mapping does not support float-backed integers. + - This method requires `infer_numpy` to be called beforehand, as mapping + does not support float-backed integers. - Support for additional tables (e.g., dcline) is not yet implemented. - - Returns: - None: The CaseFrames object is modified in place. """ # store original gen index mapping before resetting, used in reserves gen_map = {v: k for k, v in enumerate(self.gen.index)} @@ -815,6 +1047,18 @@ def reset_index(self): self.reserves.qty.index.name = "gen" def add_schema_case(self, F=None): + """ + Add case attribute to follow caseformat/schema. + + + Args: + F (float | None): + Optional frequency value. + + + Warnings: + This might be deprecated in the future if MATPOWER defines this later. + """ # add case to follow casefromat/schema # !WARNING: # this might be deprecated in the future if matpower define this later @@ -830,6 +1074,7 @@ def to_pu(self): """ Create a new CaseFrame object with data in p.u. and rad. + Returns: CaseFrames: CaseFrames object with data in p.u. and rad. """ @@ -894,6 +1139,7 @@ def to_excel(self, path, prefix="", suffix=""): """ Save the CaseFrames data into a single Excel file. + Args: path (str): File path for the Excel file. prefix (str): Sheet prefix for each attribute for the Excel file. @@ -934,10 +1180,16 @@ def to_csv(self, path, prefix="", suffix="", attributes=None): """ Save the CaseFrames data into multiple CSV files. + Args: - path (str): Directory path where the CSV files will be saved. - prefix (str): Sheet prefix for each attribute for the CSV files. - suffix (str): Sheet suffix for each attribute for the CSV files. + path (str): + Directory path where the CSV files will be saved. + prefix (str): + File prefix for each attribute CSV file. + suffix (str): + File suffix for each attribute CSV file. + attributes (list | None): + Specific attributes to save (unused parameter). """ # make dir os.makedirs(path, exist_ok=True) @@ -967,7 +1219,6 @@ def to_dict(self): """ Convert the CaseFrames data into a dictionary. - The value of the data will be in str, numeric, and list. Returns: dict: Dictionary with attribute names as keys and their data as values. @@ -992,10 +1243,8 @@ def to_dict(self): def to_mpc(self): """ - Convert the CaseFrames data into a format compatible with MATPOWER (as a - dictionary). + Convert the CaseFrames data into a format compatible with MATPOWER. - The value of the data will be in str, numeric, and list. Returns: dict: MATPOWER-compatible dictionary with data. @@ -1006,10 +1255,22 @@ def to_schema(self, path, prefix="", suffix=""): """ Convert to format compatible with caseformat/schema. - This method also mutate the CaseFormat by adding "case" as a new - attribute if not exists. - See more: + Args: + path (str): + Directory path where the schema files will be saved. + prefix (str): + File prefix for each attribute. + suffix (str): + File suffix for each attribute. + + + Notes: + This method also mutates the CaseFrames by adding "case" as a new attribute + if not exists. + + + See Also: https://github.com/caseformat/schema """ @@ -1022,11 +1283,14 @@ def reserves_data_to_dataframes(reserves): """ Convert all mpc.reserves struct data to DataFrames. + Args: - reserves: dict or oct2py.io.Struct of mpc.reserves object from MATPOWER + reserves (dict | oct2py.io.Struct): + mpc.reserves object from MATPOWER. + Returns: - dict or oct2py.io.Struct containing: + dict: Dictionary containing: - 'zones': Reserve zones DataFrame - 'req': Reserve requirements DataFrame - 'cost': Reserve costs DataFrame (if exists) @@ -1066,18 +1330,24 @@ def reserves_data_to_dataframes(reserves): class xGenDataTableFrames(DataFrameStruct): """ - A struct-like and DataFrame-like container for xGenData with MATPOWER - compatibility. Support standard DataFrame operations. + A struct-like and DataFrame-like container for xGenData with MATPOWER compatibility. + + + Supports standard DataFrame operations. """ def __init__(self, data=None, colnames=None, index=None): """ - Initialize DataTableFrames with optional data. + Initialize xGenDataTableFrames with optional data. + Args: - data (np.ndarray | list | dict, optional): Data for DataTableFrames. - colnames (list, optional): Column names. Defaults to None. - index (pd.Index, optional): Row index. Defaults to None. + data (np.ndarray | list | dict | None): + Data for xGenDataTableFrames. + colnames (list | None): + Column names. + index (pd.Index | None): + Row index. """ if data is not None and colnames is None: if isinstance(data, dict): @@ -1103,6 +1373,7 @@ def to_dict(self): """ Convert to combined dict with both xgd and xgd_table formats. + Returns: dict: Combined dictionary with: - Column names as keys with 2D array values (xgd format) @@ -1112,12 +1383,14 @@ def to_dict(self): xgdt_dict = self.to_xdgt() return {**xgd_dict, **xgdt_dict} - def to_xgdt(self): + def to_xdgt(self): """ Convert to xgd_table format (for loadgenericdata). + Returns: - dict: Dictionary with 'colnames' (2D array) and 'data' (2D array) + dict: + Dictionary with 'colnames' (2D array) and 'data' (2D array). """ return { "colnames": self.colnames, @@ -1128,7 +1401,9 @@ def to_xgd(self): """ Convert to xgd struct format (for loadxgendata/MOST functions). + Returns: - dict: Dictionary where keys are column names and values are 2D arrays (n, 1) + dict: + Dictionary where keys are column names and values are 2D arrays (n, 1). """ return super().to_dict() From 6365b9346a516334b75d56db8152a9b112409d48 Mon Sep 17 00:00:00 2001 From: Muhammad Yasirroni Date: Mon, 19 Jan 2026 19:17:50 +1100 Subject: [PATCH 15/17] add BaseStruct --- matpowercaseframes/core.py | 94 ++++++++++++++++++++------------------ 1 file changed, 50 insertions(+), 44 deletions(-) diff --git a/matpowercaseframes/core.py b/matpowercaseframes/core.py index e22592f..37115ac 100644 --- a/matpowercaseframes/core.py +++ b/matpowercaseframes/core.py @@ -21,9 +21,9 @@ MATPOWER_EXIST = False -class DataFramesStruct: +class BaseStruct: """ - Base class for struct-like containers with DataFrames. + Base class for struct-like containers. """ def __init__(self): @@ -45,40 +45,16 @@ def setattr(self, name, value): self._attributes.append(name) super().__setattr__(name, value) - def to_dict(self): + @property + def attributes(self): """ - Convert the DataFramesStruct data into a dictionary. + List of attributes in this struct object. Returns: - dict: - Dictionary with attribute names as keys and their data as values. - """ - # TODO: support mpc = cf.to_dict() with reserves data - data = {} - for attribute in self._attributes: - value = getattr(self, attribute) - if isinstance(value, pd.DataFrame): - data[attribute] = value.values.tolist() - elif isinstance(value, DataFramesStruct): - data[attribute] = value.to_dict() - else: - data[attribute] = value - - return data - - def infer_numpy(self): - """ - Infer and convert data types in all DataFrames to appropriate NumPy-compatible - types. + list: List of attribute names. """ - for attribute in self._attributes: - df = getattr(self, attribute) - if isinstance(df, pd.DataFrame): - df = self._infer_numpy(df) - setattr(self, attribute, df) - elif isinstance(df, DataFramesStruct): - df.infer_numpy() + return self._attributes @staticmethod def _infer_numpy(df): @@ -110,30 +86,60 @@ def _infer_numpy(df): df[columns] = df[columns].astype(bool) return df - @property - def attributes(self): + def __repr__(self): """ - List of attributes in this struct object. + String representation of the struct. Returns: - list: List of attribute names. + str: String representation showing class name and attributes. """ - return self._attributes + attrs = ", ".join(self._attributes) + return f"{self.__class__.__name__}(attributes=[{attrs}])" - def __repr__(self): + +class DataFramesStruct(BaseStruct): + """ + Base class for struct-like containers with DataFrames. + """ + + def to_dict(self): """ - String representation of the struct. + Convert the DataFramesStruct data into a dictionary. Returns: - str: String representation showing class name and attributes. + dict: + Dictionary with attribute names as keys and their data as values. """ - attrs = ", ".join(self._attributes) - return f"{self.__class__.__name__}(attributes=[{attrs}])" + # TODO: support mpc = cf.to_dict() with reserves data + data = {} + for attribute in self._attributes: + value = getattr(self, attribute) + if isinstance(value, pd.DataFrame): + data[attribute] = value.values.tolist() + elif isinstance(value, DataFramesStruct): + data[attribute] = value.to_dict() + else: + data[attribute] = value + + return data + + def infer_numpy(self): + """ + Infer and convert data types in all DataFrames to appropriate NumPy-compatible + types. + """ + for attribute in self._attributes: + df = getattr(self, attribute) + if isinstance(df, pd.DataFrame): + df = self._infer_numpy(df) + setattr(self, attribute, df) + elif isinstance(df, DataFramesStruct): + df.infer_numpy() -class DataFrameStruct(DataFramesStruct): +class DataFrameStruct(BaseStruct): """ A struct-like and DataFrame-like container with MATPOWER compatibility. """ @@ -1380,10 +1386,10 @@ def to_dict(self): - 'colnames' and 'data' keys (xgd_table format) """ xgd_dict = self.to_xgd() - xgdt_dict = self.to_xdgt() + xgdt_dict = self.to_xgdt() return {**xgd_dict, **xgdt_dict} - def to_xdgt(self): + def to_xgdt(self): """ Convert to xgd_table format (for loadgenericdata). From 06eb8b9fa9e47c4d0c66a0173b3b395573cdbcd1 Mon Sep 17 00:00:00 2001 From: Muhammad Yasirroni Date: Mon, 19 Jan 2026 23:06:15 +1100 Subject: [PATCH 16/17] add display --- README.md | 4 +- matpowercaseframes/constants.py | 6 + matpowercaseframes/core.py | 130 +- notebooks/load_most_ex_case3a.ipynb | 2536 ++++++++++++++++++++++++++- 4 files changed, 2611 insertions(+), 65 deletions(-) diff --git a/README.md b/README.md index d40b4d7..37710f3 100644 --- a/README.md +++ b/README.md @@ -109,7 +109,7 @@ LOAD_COL = ["LD_ID", "LD_BUS", "LD_STATUS", "LD_PD", "LD_QD"] mpc = m.loadcase('case9', verbose=False) cf = CaseFrames(mpc) -cf.setattr_as_df('load', mpc.load, columns_template=LOAD_COL) +cf.set_attribute_as_df('load', mpc.load, columns_template=LOAD_COL) ``` If data already in `DataFrame`, we can use `setattr` directly as follows, @@ -122,7 +122,7 @@ m = start_instance() mpc = m.loadcase('case9', verbose=False) cf = CaseFrames(mpc) -cf.setattr('load', df_load) +cf.set_attribute('load', df_load) ``` ### Export as `xlsx` diff --git a/matpowercaseframes/constants.py b/matpowercaseframes/constants.py index 6ef4531..026b433 100644 --- a/matpowercaseframes/constants.py +++ b/matpowercaseframes/constants.py @@ -18,8 +18,14 @@ "reserves", "xgd_table", "xgd", + "f", + "et", + "success", ) +ATTRIBUTES_NAME = ("bus_name", "branch_name", "gen_name") +ATTRIBUTES_INFO = ("version", "baseMVA", "f", "et", "success") + COLUMNS = { "bus": [ "BUS_I", diff --git a/matpowercaseframes/core.py b/matpowercaseframes/core.py index 37115ac..6be9588 100644 --- a/matpowercaseframes/core.py +++ b/matpowercaseframes/core.py @@ -9,7 +9,7 @@ import numpy as np import pandas as pd -from .constants import ATTRIBUTES, COLUMNS +from .constants import ATTRIBUTES, ATTRIBUTES_INFO, ATTRIBUTES_NAME, COLUMNS from .reader import find_attributes, find_name, parse_file from .utils import get_attr, has_attr @@ -21,6 +21,15 @@ MATPOWER_EXIST = False +def display(*args, **kwargs): + try: + from IPython.display import display as ipython_display + + return ipython_display(*args, **kwargs) + except ImportError: + print(*args, **kwargs) + + class BaseStruct: """ Base class for struct-like containers. @@ -30,9 +39,9 @@ def __init__(self): """ Initialize the base struct with an empty attributes list. """ - self._attributes = [] + object.__setattr__(self, "_attributes", []) - def setattr(self, name, value): + def set_attribute(self, name, value): """ Set attribute and track it in _attributes list. @@ -134,10 +143,27 @@ def infer_numpy(self): df = getattr(self, attribute) if isinstance(df, pd.DataFrame): df = self._infer_numpy(df) - setattr(self, attribute, df) + self.set_attribute(attribute, df) elif isinstance(df, DataFramesStruct): df.infer_numpy() + def display(self): + data = {"INFO": {}} + for attribute in ATTRIBUTES_INFO: + if attribute in self._attributes: + data["INFO"][attribute] = getattr(self, attribute, None) + display(pd.DataFrame(data=data)) + + for attribute in self._attributes: + df = getattr(self, attribute) + if isinstance(df, pd.DataFrame): + print(attribute) + display(df) + + if isinstance(getattr(self, attribute), DataFramesStruct): + print(attribute) + df.display() + class DataFrameStruct(BaseStruct): """ @@ -168,7 +194,7 @@ def __init__(self, data, index, colnames): if data is not None: if isinstance(data, dict): - self.setattr( + self.set_attribute( "table", pd.DataFrame( {k: np.asarray(v).flatten() for k, v in data.items()}, @@ -176,7 +202,9 @@ def __init__(self, data, index, colnames): ), ) elif isinstance(data, (np.ndarray, list)): - self.setattr("table", pd.DataFrame(data, columns=colnames, index=index)) + self.set_attribute( + "table", pd.DataFrame(data, columns=colnames, index=index) + ) elif isinstance(data, str): # TODO: # 1. Support read from file. @@ -195,7 +223,7 @@ def __init__(self, data, index, colnames): start=1, stop=len(self.table) + 1, name="gen" ) else: - self.setattr("table", pd.DataFrame(columns=colnames, index=index)) + self.set_attribute("table", pd.DataFrame(columns=colnames, index=index)) @property def colnames(self): @@ -399,10 +427,10 @@ def __init__(self, data=None, allow_any_keys=False): if not allow_any_keys: for key, value in data.items(): if key in COLUMNS["reserves"]: - self.setattr(key, value) + self.set_attribute(key, value) else: for key, value in data.items(): - self.setattr(key, value) + self.set_attribute(key, value) else: raise TypeError(f"ReservesFrames data must be a dict, got {type(data)}") @@ -578,7 +606,7 @@ def _read_data( ) raise TypeError(message) - def setattr_as_df(self, name, value, columns_template=None): + def set_attribute_as_df(self, name, value, columns_template=None): """ Convert value to DataFrame and assign to attributes. @@ -591,7 +619,7 @@ def setattr_as_df(self, name, value, columns_template=None): List of column names used for DataFrame column header. """ df = self._get_dataframe(name, value, columns_template=columns_template) - self.setattr(name, df) + self.set_attribute(name, df) def update_columns_templates(self, columns_templates): """ @@ -680,15 +708,15 @@ def _read_matpower(self, filepath, allow_any_keys=False): # TODO: compare with GridCal approach list_ = parse_file(attribute, string) # list_ in nested list array if list_ is not None: - if attribute == "version" or attribute == "baseMVA": + if attribute in ATTRIBUTES_INFO: value = list_[0][0] - elif attribute in ["bus_name", "branch_name", "gen_name"]: + elif attribute in ATTRIBUTES_NAME: value = pd.Index([name[0] for name in list_], name=attribute) else: # bus, branch, gen, gencost, dcline, dclinecost n_cols = max([len(l) for l in list_]) value = self._get_dataframe(attribute, list_, n_cols) - self.setattr(attribute, value) + self.set_attribute(attribute, value) def _read_oct2py_struct(self, struct, allow_any_keys=False): """ @@ -707,9 +735,9 @@ def _read_oct2py_struct(self, struct, allow_any_keys=False): if attribute not in ATTRIBUTES and not allow_any_keys: continue - if attribute == "version" or attribute == "baseMVA": + if attribute in ATTRIBUTES_INFO: value = list_ - elif attribute in ["bus_name", "branch_name", "gen_name"]: + elif attribute in ATTRIBUTES_NAME: value = pd.Index([name[0] for name in list_], name=attribute) elif attribute in ["reserves"]: dfs = reserves_data_to_dataframes(list_) @@ -719,7 +747,7 @@ def _read_oct2py_struct(self, struct, allow_any_keys=False): n_cols = list_.shape[1] value = self._get_dataframe(attribute, list_, n_cols) - self.setattr(attribute, value) + self.set_attribute(attribute, value) return None @@ -740,16 +768,16 @@ def _read_numpy_struct(self, array, allow_any_keys=False): if attribute not in ATTRIBUTES and not allow_any_keys: continue - if attribute == "version" or attribute == "baseMVA": + if attribute in ATTRIBUTES_INFO: value = array[attribute].item().item() - elif attribute in ["bus_name", "branch_name", "gen_name"]: + elif attribute in ATTRIBUTES_NAME: value = pd.Index(array[attribute].item(), name=attribute) else: # bus, branch, gen, gencost, dcline, dclinecost data = array[attribute].item() n_cols = data.shape[1] value = self._get_dataframe(attribute, data, n_cols) - self.setattr(attribute, value) + self.set_attribute(attribute, value) def _read_excel(self, filepath, prefix="", suffix="", allow_any_keys=False): """ @@ -773,12 +801,12 @@ def _read_excel(self, filepath, prefix="", suffix="", allow_any_keys=False): info_sheet_name = f"{prefix}info{suffix}" if info_sheet_name in sheets: info_data = sheets[info_sheet_name] - + # TODO: support other info fields, skip if not exist value = info_data.loc["version", "INFO"].item() - self.setattr("version", str(value)) + self.set_attribute("version", str(value)) value = info_data.loc["baseMVA", "INFO"].item() - self.setattr("baseMVA", value) + self.set_attribute("baseMVA", value) # iterate through the remaining sheets for attribute, sheet_data in sheets.items(): @@ -796,13 +824,13 @@ def _read_excel(self, filepath, prefix="", suffix="", allow_any_keys=False): if attribute not in ATTRIBUTES and not allow_any_keys: continue - if attribute in ["bus_name", "branch_name", "gen_name"]: + if attribute in ATTRIBUTES_NAME: # convert back to an index value = pd.Index(sheet_data[attribute].values.tolist(), name=attribute) else: value = sheet_data - self.setattr(attribute, value) + self.set_attribute(attribute, value) def _read_csv_dir(self, dirpath, prefix="", suffix="", allow_any_keys=False): """ @@ -840,10 +868,10 @@ def _read_csv_dir(self, dirpath, prefix="", suffix="", allow_any_keys=False): info_data = pd.read_csv(csv_data[info_name], index_col=0) value = info_data.loc["version", "INFO"].item() - self.setattr("version", str(value)) + self.set_attribute("version", str(value)) value = info_data.loc["baseMVA", "INFO"].item() - self.setattr("baseMVA", value) + self.set_attribute("baseMVA", value) # iterate through the remaining CSV files for attribute, filepath in csv_data.items(): @@ -858,13 +886,13 @@ def _read_csv_dir(self, dirpath, prefix="", suffix="", allow_any_keys=False): # read CSV file sheet_data = pd.read_csv(filepath, index_col=0) - if attribute in ["bus_name", "branch_name", "gen_name"]: + if attribute in ATTRIBUTES_NAME: # convert back to an index value = pd.Index(sheet_data[attribute].values.tolist(), name=attribute) else: value = sheet_data - self.setattr(attribute, value) + self.set_attribute(attribute, value) def _get_dataframe(self, attribute, data, n_cols=None, columns_template=None): """ @@ -949,9 +977,7 @@ def _update_index(self, allow_any_keys=False): allow_any_keys (bool): Whether to update index for any keys beyond standard attributes. """ - for attribute, attribute_name in zip( - ["bus", "branch", "gen"], ["bus_name", "branch_name", "gen_name"] - ): + for attribute, attribute_name in zip(["bus", "branch", "gen"], ATTRIBUTES_NAME): attribute_data = getattr(self, attribute) try: attribute_name_data = getattr(self, attribute_name) @@ -1072,9 +1098,9 @@ def add_schema_case(self, F=None): version = getattr(self, "version", None) baseMVA = getattr(self, "baseMVA", None) if F: - self.setattr_as_df("case", [[case_name, version, baseMVA, F]]) + self.set_attribute_as_df("case", [[case_name, version, baseMVA, F]]) else: - self.setattr_as_df("case", [[case_name, version, baseMVA]]) + self.set_attribute_as_df("case", [[case_name, version, baseMVA]]) def to_pu(self): """ @@ -1162,18 +1188,15 @@ def to_excel(self, path, prefix="", suffix=""): # convert to xlsx with pd.ExcelWriter(path) as writer: - pd.DataFrame( - data={ - "INFO": { - "version": getattr(self, "version", None), - "baseMVA": getattr(self, "baseMVA", None), - } - } - ).to_excel(writer, sheet_name=f"{prefix}info{suffix}") + data = {"INFO": {}} + for attribute in ATTRIBUTES_INFO: + if attribute in self._attributes: + data["INFO"][attribute] = getattr(self, attribute, None) + pd.DataFrame(data=data).to_excel(writer, sheet_name=f"{prefix}info{suffix}") for attribute in self._attributes: - if attribute == "version" or attribute == "baseMVA": + if attribute in ATTRIBUTES_INFO: continue - elif attribute in ["bus_name", "branch_name", "gen_name"]: + elif attribute in ATTRIBUTES_NAME: pd.DataFrame(data={attribute: getattr(self, attribute)}).to_excel( writer, sheet_name=f"{prefix}{attribute}{suffix}" ) @@ -1200,19 +1223,16 @@ def to_csv(self, path, prefix="", suffix="", attributes=None): # make dir os.makedirs(path, exist_ok=True) - pd.DataFrame( - data={ - "INFO": { - "version": getattr(self, "version", None), - "baseMVA": getattr(self, "baseMVA", None), - } - } - ).to_csv(os.path.join(path, f"{prefix}info{suffix}.csv")) + data = {"INFO": {}} + for attribute in ATTRIBUTES_INFO: + if attribute in self._attributes: + data["INFO"][attribute] = getattr(self, attribute, None) + pd.DataFrame(data=data).to_csv(os.path.join(path, f"{prefix}info{suffix}.csv")) for attribute in self._attributes: - if attribute == "version" or attribute == "baseMVA": + if attribute in ATTRIBUTES_INFO: continue - elif attribute in ["bus_name", "branch_name", "gen_name"]: + elif attribute in ATTRIBUTES_NAME: pd.DataFrame(data={attribute: getattr(self, attribute)}).to_csv( os.path.join(path, f"{prefix}{attribute}{suffix}.csv") ) @@ -1236,7 +1256,7 @@ def to_dict(self): } for attribute in self._attributes: value = getattr(self, attribute) - if attribute in ["bus_name", "branch_name", "gen_name"]: + if attribute in ATTRIBUTES_NAME: # NOTE: must be in 2D Cell or 2D np.array data[attribute] = np.atleast_2d(value.values).T elif isinstance(value, pd.DataFrame): diff --git a/notebooks/load_most_ex_case3a.ipynb b/notebooks/load_most_ex_case3a.ipynb index a669765..74edbf7 100644 --- a/notebooks/load_most_ex_case3a.ipynb +++ b/notebooks/load_most_ex_case3a.ipynb @@ -64,16 +64,18 @@ }, { "cell_type": "code", - "execution_count": 57, + "execution_count": 22, "id": "3116b86b", "metadata": {}, "outputs": [], "source": [ "import os\n", "\n", + "import pandas as pd\n", "from matpower import path_matpower, start_instance\n", "\n", - "from matpowercaseframes import CaseFrames, xGenDataTableFrames" + "from matpowercaseframes import CaseFrames, xGenDataTableFrames\n", + "from matpowercaseframes.constants import ATTRIBUTES_INFO" ] }, { @@ -98,10 +100,2496 @@ }, { "cell_type": "code", - "execution_count": 79, + "execution_count": 7, + "id": "33e60f85", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'v': 25.0,\n", + " 'model': 'DC',\n", + " 'pf': {'alg': 'NR',\n", + " 'current_balance': 0.0,\n", + " 'v_cartesian': 0.0,\n", + " 'tol': 1e-08,\n", + " 'nr': {'max_it': 10.0, 'lin_solver': ''},\n", + " 'fd': {'max_it': 30.0},\n", + " 'gs': {'max_it': 1000.0},\n", + " 'zg': {'max_it': 1000.0},\n", + " 'radial': {'max_it': 20.0, 'vcorr': 0.0},\n", + " 'enforce_q_lims': 0.0},\n", + " 'cpf': {'parameterization': 3.0,\n", + " 'stop_at': 'NOSE',\n", + " 'enforce_p_lims': 0.0,\n", + " 'enforce_q_lims': 0.0,\n", + " 'enforce_v_lims': 0.0,\n", + " 'enforce_flow_lims': 0.0,\n", + " 'step': 0.05,\n", + " 'step_min': 0.0001,\n", + " 'step_max': 0.2,\n", + " 'adapt_step': 0.0,\n", + " 'adapt_step_damping': 0.7,\n", + " 'adapt_step_tol': 0.001,\n", + " 'target_lam_tol': 1e-05,\n", + " 'nose_tol': 1e-05,\n", + " 'p_lims_tol': 0.01,\n", + " 'q_lims_tol': 0.01,\n", + " 'v_lims_tol': 0.0001,\n", + " 'flow_lims_tol': 0.01,\n", + " 'plot': {'level': 0.0, 'bus': []},\n", + " 'user_callback': ''},\n", + " 'opf': {'ac': {'solver': 'DEFAULT'},\n", + " 'dc': {'solver': 'MIPS'},\n", + " 'current_balance': 0.0,\n", + " 'v_cartesian': 0.0,\n", + " 'violation': 5e-06,\n", + " 'use_vg': 0.0,\n", + " 'flow_lim': 'S',\n", + " 'ignore_angle_lim': 0.0,\n", + " 'softlims': {'default': 1.0},\n", + " 'start': 0.0,\n", + " 'return_raw_der': 0.0},\n", + " 'verbose': 1.0,\n", + " 'out': {'all': -1.0,\n", + " 'sys_sum': 1.0,\n", + " 'area_sum': 0.0,\n", + " 'bus': 1.0,\n", + " 'branch': 1.0,\n", + " 'gen': 1.0,\n", + " 'lim': {'all': -1.0, 'v': 1.0, 'line': 1.0, 'pg': 1.0, 'qg': 1.0},\n", + " 'force': 0.0,\n", + " 'suppress_detail': -1.0},\n", + " 'mips': {'step_control': 0.0,\n", + " 'linsolver': '',\n", + " 'feastol': 0.0,\n", + " 'gradtol': 1e-06,\n", + " 'comptol': 1e-06,\n", + " 'costtol': 1e-06,\n", + " 'max_it': 150.0,\n", + " 'sc': {'red_it': 20.0}},\n", + " 'exp': {'use_legacy_core': 0.0, 'sys_wide_zip_loads': {'pw': [], 'qw': []}},\n", + " 'glpk': {'opts': [], 'opt_fname': ''},\n", + " 'most': {'build_model': 1.0,\n", + " 'solve_model': 1.0,\n", + " 'resolve_new_cost': 0.0,\n", + " 'dc_model': 0.0,\n", + " 'fixed_res': -1.0,\n", + " 'q_coordination': 0.0,\n", + " 'security_constraints': -1.0,\n", + " 'storage': {'terminal_target': -1.0, 'cyclic': 0.0},\n", + " 'uc': {'run': -1.0, 'cyclic': 0.0},\n", + " 'alpha': 0.0,\n", + " 'solver': 'MIPS',\n", + " 'skip_prices': 0.0,\n", + " 'price_stage_warn_tol': 1e-07}}" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "verbose = 1\n", + "mpopt = m.mpoption(\"verbose\", verbose)\n", + "mpopt = m.mpoption(mpopt, \"out.gen\", 1)\n", + "mpopt = m.mpoption(mpopt, \"model\", \"DC\")\n", + "mpopt = m.mpoption(mpopt, \"opf.dc.solver\", \"MIPS\")\n", + "mpopt = m.mpoption(mpopt, \"most.solver\", mpopt.opf.dc.solver)\n", + "mpopt = m.mpoption(mpopt, \"most.dc_model\", 0) # use model with no network\n", + "mpopt" + ] + }, + { + "cell_type": "markdown", + "id": "e25c8a6f", + "metadata": {}, + "source": [ + "## Example 1 - Economic Dispatch" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "id": "9cbd4a01", + "metadata": {}, + "outputs": [], + "source": [ + "def display_cf(cf):\n", + " data = {\"INFO\": {}}\n", + " for attribute in ATTRIBUTES_INFO:\n", + " if attribute in cf._attributes:\n", + " data[\"INFO\"][attribute] = getattr(cf, attribute, None)\n", + " display(pd.DataFrame(data=data))\n", + "\n", + " for attribute in cf._attributes:\n", + " if attribute in ATTRIBUTES_INFO or attribute == \"reserves\":\n", + " continue\n", + " print(attribute)\n", + " display(getattr(cf, attribute))\n", + "\n", + " for attribute in cf.reserves._attributes:\n", + " print(attribute)\n", + " display(getattr(cf.reserves, attribute))" + ] + }, + { + "cell_type": "code", + "execution_count": 24, "id": "87bc9631", "metadata": {}, "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
INFO
version2
baseMVA100.0
\n", + "
" + ], + "text/plain": [ + " INFO\n", + "version 2\n", + "baseMVA 100.0" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "bus\n" + ] + }, + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
BUS_IBUS_TYPEPDQDGSBSBUS_AREAVMVABASE_KVZONEVMAXVMIN
bus
11.03.00.00.00.00.01.01.00.0135.01.01.050.95
22.02.00.00.00.00.01.01.00.0135.01.01.050.95
33.02.00.00.00.00.01.01.00.0135.01.01.050.95
\n", + "
" + ], + "text/plain": [ + " BUS_I BUS_TYPE PD QD GS BS BUS_AREA VM VA BASE_KV ZONE \\\n", + "bus \n", + "1 1.0 3.0 0.0 0.0 0.0 0.0 1.0 1.0 0.0 135.0 1.0 \n", + "2 2.0 2.0 0.0 0.0 0.0 0.0 1.0 1.0 0.0 135.0 1.0 \n", + "3 3.0 2.0 0.0 0.0 0.0 0.0 1.0 1.0 0.0 135.0 1.0 \n", + "\n", + " VMAX VMIN \n", + "bus \n", + "1 1.05 0.95 \n", + "2 1.05 0.95 \n", + "3 1.05 0.95 " + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "gen\n" + ] + }, + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
GEN_BUSPGQGQMAXQMINVGMBASEGEN_STATUSPMAXPMIN...PC2QC1MINQC1MAXQC2MINQC2MAXRAMP_AGCRAMP_10RAMP_30RAMP_QAPF
gen
11.0125.00.025.0-25.01.0100.01.0200.00.0...0.00.00.00.00.00.0250.0250.00.00.0
21.0125.00.025.0-25.01.0100.01.0200.00.0...0.00.00.00.00.00.0250.0250.00.00.0
32.0200.00.050.0-50.01.0100.01.0500.00.0...0.00.00.00.00.00.0600.0600.00.00.0
43.0-450.00.00.00.01.0100.01.00.0-450.0...0.00.00.00.00.00.0500.0500.00.00.0
\n", + "

4 rows × 21 columns

\n", + "
" + ], + "text/plain": [ + " GEN_BUS PG QG QMAX QMIN VG MBASE GEN_STATUS PMAX PMIN \\\n", + "gen \n", + "1 1.0 125.0 0.0 25.0 -25.0 1.0 100.0 1.0 200.0 0.0 \n", + "2 1.0 125.0 0.0 25.0 -25.0 1.0 100.0 1.0 200.0 0.0 \n", + "3 2.0 200.0 0.0 50.0 -50.0 1.0 100.0 1.0 500.0 0.0 \n", + "4 3.0 -450.0 0.0 0.0 0.0 1.0 100.0 1.0 0.0 -450.0 \n", + "\n", + " ... PC2 QC1MIN QC1MAX QC2MIN QC2MAX RAMP_AGC RAMP_10 RAMP_30 \\\n", + "gen ... \n", + "1 ... 0.0 0.0 0.0 0.0 0.0 0.0 250.0 250.0 \n", + "2 ... 0.0 0.0 0.0 0.0 0.0 0.0 250.0 250.0 \n", + "3 ... 0.0 0.0 0.0 0.0 0.0 0.0 600.0 600.0 \n", + "4 ... 0.0 0.0 0.0 0.0 0.0 0.0 500.0 500.0 \n", + "\n", + " RAMP_Q APF \n", + "gen \n", + "1 0.0 0.0 \n", + "2 0.0 0.0 \n", + "3 0.0 0.0 \n", + "4 0.0 0.0 \n", + "\n", + "[4 rows x 21 columns]" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "branch\n" + ] + }, + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
F_BUST_BUSBR_RBR_XBR_BRATE_ARATE_BRATE_CTAPSHIFTBR_STATUSANGMINANGMAX
branch
11.02.00.0050.010.0300.0300.0300.00.00.01.0-360.0360.0
21.03.00.0050.010.0240.0240.0240.00.00.01.0-360.0360.0
32.03.00.0050.010.0300.0300.0300.00.00.01.0-360.0360.0
\n", + "
" + ], + "text/plain": [ + " F_BUS T_BUS BR_R BR_X BR_B RATE_A RATE_B RATE_C TAP SHIFT \\\n", + "branch \n", + "1 1.0 2.0 0.005 0.01 0.0 300.0 300.0 300.0 0.0 0.0 \n", + "2 1.0 3.0 0.005 0.01 0.0 240.0 240.0 240.0 0.0 0.0 \n", + "3 2.0 3.0 0.005 0.01 0.0 300.0 300.0 300.0 0.0 0.0 \n", + "\n", + " BR_STATUS ANGMIN ANGMAX \n", + "branch \n", + "1 1.0 -360.0 360.0 \n", + "2 1.0 -360.0 360.0 \n", + "3 1.0 -360.0 360.0 " + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "gencost\n" + ] + }, + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
MODELSTARTUPSHUTDOWNNCOSTC2C1C0
gen
12.00.00.03.00.10.00.0
22.00.00.03.00.10.00.0
32.00.00.03.00.10.00.0
42.00.00.03.00.01000.00.0
\n", + "
" + ], + "text/plain": [ + " MODEL STARTUP SHUTDOWN NCOST C2 C1 C0\n", + "gen \n", + "1 2.0 0.0 0.0 3.0 0.1 0.0 0.0\n", + "2 2.0 0.0 0.0 3.0 0.1 0.0 0.0\n", + "3 2.0 0.0 0.0 3.0 0.1 0.0 0.0\n", + "4 2.0 0.0 0.0 3.0 0.0 1000.0 0.0" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "zones\n" + ] + }, + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
gen1234
zone
11.01.01.00.0
\n", + "
" + ], + "text/plain": [ + "gen 1 2 3 4\n", + "zone \n", + "1 1.0 1.0 1.0 0.0" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "req\n" + ] + }, + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
PREQ
zone
1150.0
\n", + "
" + ], + "text/plain": [ + " PREQ\n", + "zone \n", + "1 150.0" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "cost\n" + ] + }, + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
C1
gen
11.0
23.0
35.0
\n", + "
" + ], + "text/plain": [ + " C1\n", + "gen \n", + "1 1.0\n", + "2 3.0\n", + "3 5.0" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "qty\n" + ] + }, + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
PQTY
gen
1100.0
2100.0
3200.0
\n", + "
" + ], + "text/plain": [ + " PQTY\n", + "gen \n", + "1 100.0\n", + "2 100.0\n", + "3 200.0" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "cf = CaseFrames(os.path.join(path_most_ex_cases, \"ex_case3a.m\"), load_case_engine=m)\n", + "display_cf(cf)" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "d2baa348", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'Delta_T': 1.0,\n", + " 'Storage': {'UnitIdx': [],\n", + " 'ExpectedTerminalStorageAim': [],\n", + " 'ExpectedTerminalStorageMin': [],\n", + " 'ExpectedTerminalStorageMax': [],\n", + " 'rho': [],\n", + " 'TerminalChargingPrice0': [],\n", + " 'TerminalDischargingPrice0': [],\n", + " 'TerminalChargingPriceK': [],\n", + " 'TerminalDischargingPriceK': []},\n", + " 'UC': {'CommitKey': [],\n", + " 'CommitSched': array([[1.],\n", + " [1.],\n", + " [1.],\n", + " [1.]])},\n", + " 'idx': {'nt': 1.0},\n", + " 'tstep': {'TransMat': 1.0, 'OpCondSched': {'tab': []}},\n", + " 'mpc': {'version': '2',\n", + " 'baseMVA': 100.0,\n", + " 'bus': array([[ 1. , 3. , 0. , 0. , 0. , 0. , 1. , 1. ,\n", + " 0. , 135. , 1. , 1.05, 0.95],\n", + " [ 2. , 2. , 0. , 0. , 0. , 0. , 1. , 1. ,\n", + " 0. , 135. , 1. , 1.05, 0.95],\n", + " [ 3. , 2. , 0. , 0. , 0. , 0. , 1. , 1. ,\n", + " 0. , 135. , 1. , 1.05, 0.95]]),\n", + " 'gen': array([[ 1., 125., 0., 25., -25., 1., 100., 1., 200.,\n", + " 0., 0., 0., 0., 0., 0., 0., 0., 250.,\n", + " 250., 0., 0.],\n", + " [ 1., 125., 0., 25., -25., 1., 100., 1., 200.,\n", + " 0., 0., 0., 0., 0., 0., 0., 0., 250.,\n", + " 250., 0., 0.],\n", + " [ 2., 200., 0., 50., -50., 1., 100., 1., 500.,\n", + " 0., 0., 0., 0., 0., 0., 0., 0., 600.,\n", + " 600., 0., 0.],\n", + " [ 3., -450., 0., 0., 0., 1., 100., 1., 0.,\n", + " -450., 0., 0., 0., 0., 0., 0., 0., 500.,\n", + " 500., 0., 0.]]),\n", + " 'branch': array([[ 1.0e+00, 2.0e+00, 5.0e-03, 1.0e-02, 0.0e+00, 3.0e+02,\n", + " 3.0e+02, 3.0e+02, 0.0e+00, 0.0e+00, 1.0e+00, -3.6e+02,\n", + " 3.6e+02],\n", + " [ 1.0e+00, 3.0e+00, 5.0e-03, 1.0e-02, 0.0e+00, 2.4e+02,\n", + " 2.4e+02, 2.4e+02, 0.0e+00, 0.0e+00, 1.0e+00, -3.6e+02,\n", + " 3.6e+02],\n", + " [ 2.0e+00, 3.0e+00, 5.0e-03, 1.0e-02, 0.0e+00, 3.0e+02,\n", + " 3.0e+02, 3.0e+02, 0.0e+00, 0.0e+00, 1.0e+00, -3.6e+02,\n", + " 3.6e+02]]),\n", + " 'gencost': array([[2.e+00, 0.e+00, 0.e+00, 3.e+00, 1.e-01, 0.e+00, 0.e+00],\n", + " [2.e+00, 0.e+00, 0.e+00, 3.e+00, 1.e-01, 0.e+00, 0.e+00],\n", + " [2.e+00, 0.e+00, 0.e+00, 3.e+00, 1.e-01, 0.e+00, 0.e+00],\n", + " [2.e+00, 0.e+00, 0.e+00, 3.e+00, 0.e+00, 1.e+03, 0.e+00]]),\n", + " 'reserves': {'zones': array([[1., 1., 1., 0.]]),\n", + " 'req': 150.0,\n", + " 'cost': array([[1.],\n", + " [3.],\n", + " [5.]]),\n", + " 'qty': array([[100.],\n", + " [100.],\n", + " [200.]])}},\n", + " 'InitialPg': array([[ 125.],\n", + " [ 125.],\n", + " [ 200.],\n", + " [-450.]]),\n", + " 'RampWearCostCoeff': array([[0.],\n", + " [0.],\n", + " [0.],\n", + " [0.]]),\n", + " 'offer': {'PositiveActiveReservePrice': array([[0.],\n", + " [0.],\n", + " [0.],\n", + " [0.]]),\n", + " 'PositiveActiveReserveQuantity': array([[ 400.],\n", + " [ 400.],\n", + " [1000.],\n", + " [ 900.]]),\n", + " 'NegativeActiveReservePrice': array([[0.],\n", + " [0.],\n", + " [0.],\n", + " [0.]]),\n", + " 'NegativeActiveReserveQuantity': array([[ 400.],\n", + " [ 400.],\n", + " [1000.],\n", + " [ 900.]]),\n", + " 'PositiveActiveDeltaPrice': array([[0.],\n", + " [0.],\n", + " [0.],\n", + " [0.]]),\n", + " 'NegativeActiveDeltaPrice': array([[0.],\n", + " [0.],\n", + " [0.],\n", + " [0.]]),\n", + " 'PositiveLoadFollowReservePrice': array([[0.],\n", + " [0.],\n", + " [0.],\n", + " [0.]]),\n", + " 'PositiveLoadFollowReserveQuantity': array([[ 400.],\n", + " [ 400.],\n", + " [1000.],\n", + " [ 900.]]),\n", + " 'NegativeLoadFollowReservePrice': array([[0.],\n", + " [0.],\n", + " [0.],\n", + " [0.]]),\n", + " 'NegativeLoadFollowReserveQuantity': array([[ 400.],\n", + " [ 400.],\n", + " [1000.],\n", + " [ 900.]])},\n", + " 'cont': {'contab': []}}" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# TODO: convert mdi into DataFrame\n", + "# mdi: MOST data structure input\n", + "mdi = m.loadmd(cf.to_mpc())\n", + "mdi" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "f10d8d18", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "=============================================================================\n", + " MATPOWER Optimal Scheduling Tool -- MOST Version 1.3.1\n", + " A multiperiod stochastic secure OPF with unit commitment\n", + " ----- Built on MATPOWER -----\n", + " by Carlos E. Murillo-Sanchez, Universidad Nacional de Colombia--Manizales\n", + " and Ray D. Zimmerman, Cornell University\n", + " (c) 2010-2025 Power Systems Engineering Research Center (PSERC)\n", + "=============================================================================\n", + "- Building indexing structures.\n", + "- Building expected storage-tracking mechanism.\n", + "- Building constraint submatrices.\n", + " - Building load balance constraints.\n", + " - Building CCV constraints for piecewise-linear costs.\n", + " - Building contingency reserve constraints.\n", + " - Building ramping transitions and reserve constraints.\n", + "- Building cost structures.\n", + "- Assembling full set of constraints.\n", + "- Assembling full set of variable bounds.\n", + "- Assembling full set of costs.\n", + "- Calling QP solver.\n", + "\n", + "============================================================================\n", + "\n", + "MATPOWER Interior Point Solver -- MIPS, Version 1.5.2, 12-Jul-2025\n", + " (using built-in linear solver)\n", + "Converged!\n", + "\n", + "============================================================================\n", + "- MOST: QP solved successfully.\n", + "- Post-processing results.\n", + "- MOST: Done.\n", + "\n", + "warning: load: classdef element has been converted to a struct\n", + "warning: called from\n", + " _pyeval at line 28 column 9\n", + "\n", + "warning: load: classdef element has been converted to a struct\n", + "warning: called from\n", + " _pyeval at line 28 column 9\n", + "\n", + "warning: load: classdef element has been converted to a struct\n", + "warning: called from\n", + " _pyeval at line 28 column 9\n", + "\n", + "warning: load: classdef element has been converted to a struct\n", + "warning: called from\n", + " _pyeval at line 28 column 9\n", + "\n", + "warning: load: classdef element has been converted to a struct\n", + "warning: called from\n", + " _pyeval at line 28 column 9\n", + "\n", + "warning: load: classdef element has been converted to a struct\n", + "warning: called from\n", + " _pyeval at line 28 column 9\n", + "\n", + "warning: load: classdef element has been converted to a struct\n", + "warning: called from\n", + " _pyeval at line 28 column 9\n", + "\n", + "warning: load: classdef element has been converted to a struct\n", + "warning: called from\n", + " _pyeval at line 28 column 9\n", + "\n", + "warning: load: classdef element has been converted to a struct\n", + "warning: called from\n", + " _pyeval at line 28 column 9\n", + "\n", + "warning: load: classdef element has been converted to a struct\n", + "warning: called from\n", + " _pyeval at line 28 column 9\n", + "\n", + "warning: load: classdef element has been converted to a struct\n", + "warning: called from\n", + " _pyeval at line 28 column 9\n", + "\n", + "warning: load: classdef element has been converted to a struct\n", + "warning: called from\n", + " _pyeval at line 28 column 9\n", + "\n", + "warning: load: classdef element has been converted to a struct\n", + "warning: called from\n", + " _pyeval at line 28 column 9\n", + "\n", + "warning: load: classdef element has been converted to a struct\n", + "warning: called from\n", + " _pyeval at line 28 column 9\n", + "\n", + "warning: load: classdef element has been converted to a struct\n", + "warning: called from\n", + " _pyeval at line 28 column 9\n", + "\n" + ] + }, + { + "data": { + "text/plain": [ + "{'Delta_T': 1.0,\n", + " 'Storage': {'UnitIdx': [],\n", + " 'ExpectedTerminalStorageAim': [],\n", + " 'ExpectedTerminalStorageMin': [],\n", + " 'ExpectedTerminalStorageMax': [],\n", + " 'rho': [],\n", + " 'TerminalChargingPrice0': [],\n", + " 'TerminalDischargingPrice0': [],\n", + " 'TerminalChargingPriceK': [],\n", + " 'TerminalDischargingPriceK': [],\n", + " 'ForceCyclicStorage': 0.0,\n", + " 'ForceExpectedTerminalStorage': 0.0,\n", + " 'ExpectedStorageState': array([], shape=(0, 1), dtype=float64)},\n", + " 'UC': {'CommitKey': [],\n", + " 'CommitSched': array([[1.],\n", + " [1.],\n", + " [1.],\n", + " [1.]]),\n", + " 'run': 0.0,\n", + " 'CyclicCommitment': 0.0},\n", + " 'idx': {'nt': 1.0,\n", + " 'ng': 4.0,\n", + " 'ns': 0.0,\n", + " 'nj': 1.0,\n", + " 'ntds': 0.0,\n", + " 'nzds': 0.0,\n", + " 'nyds': 0.0,\n", + " 'nb': 3.0,\n", + " 'ny': 0.0,\n", + " 'nc': 0.0,\n", + " 'nf_total': 1.0,\n", + " 'nb_total': 3.0,\n", + " 'ns_total': 0.0,\n", + " 'ntramp': 0.0,\n", + " 'nvars': 24.0},\n", + " 'tstep': {'TransMat': 1.0, 'OpCondSched': {'tab': []}, 'TransMask': 1.0},\n", + " 'mpc': {'version': '2',\n", + " 'baseMVA': 100.0,\n", + " 'bus': array([[ 1. , 3. , 0. , 0. , 0. , 0. , 1. , 1. ,\n", + " 0. , 135. , 1. , 1.05, 0.95, 0. , 0. , 0. ,\n", + " 0. ],\n", + " [ 2. , 2. , 0. , 0. , 0. , 0. , 1. , 1. ,\n", + " 0. , 135. , 1. , 1.05, 0.95, 0. , 0. , 0. ,\n", + " 0. ],\n", + " [ 3. , 2. , 0. , 0. , 0. , 0. , 1. , 1. ,\n", + " 0. , 135. , 1. , 1.05, 0.95, 0. , 0. , 0. ,\n", + " 0. ]]),\n", + " 'gen': array([[ 1., 125., 0., 25., -25., 1., 100., 1., 200.,\n", + " 0., 0., 0., 0., 0., 0., 0., 0., 250.,\n", + " 250., 0., 0., 0., 0., 0., 0.],\n", + " [ 1., 125., 0., 25., -25., 1., 100., 1., 200.,\n", + " 0., 0., 0., 0., 0., 0., 0., 0., 250.,\n", + " 250., 0., 0., 0., 0., 0., 0.],\n", + " [ 2., 200., 0., 50., -50., 1., 100., 1., 500.,\n", + " 0., 0., 0., 0., 0., 0., 0., 0., 600.,\n", + " 600., 0., 0., 0., 0., 0., 0.],\n", + " [ 3., -450., 0., 0., 0., 1., 100., 1., 0.,\n", + " -450., 0., 0., 0., 0., 0., 0., 0., 500.,\n", + " 500., 0., 0., 0., 0., 0., 0.]]),\n", + " 'branch': array([[ 1.0e+00, 2.0e+00, 5.0e-03, 1.0e-02, 0.0e+00, 3.0e+02,\n", + " 3.0e+02, 3.0e+02, 0.0e+00, 0.0e+00, 1.0e+00, -3.6e+02,\n", + " 3.6e+02, 0.0e+00, 0.0e+00, 0.0e+00, 0.0e+00, 0.0e+00,\n", + " 0.0e+00, 0.0e+00, 0.0e+00],\n", + " [ 1.0e+00, 3.0e+00, 5.0e-03, 1.0e-02, 0.0e+00, 2.4e+02,\n", + " 2.4e+02, 2.4e+02, 0.0e+00, 0.0e+00, 1.0e+00, -3.6e+02,\n", + " 3.6e+02, 0.0e+00, 0.0e+00, 0.0e+00, 0.0e+00, 0.0e+00,\n", + " 0.0e+00, 0.0e+00, 0.0e+00],\n", + " [ 2.0e+00, 3.0e+00, 5.0e-03, 1.0e-02, 0.0e+00, 3.0e+02,\n", + " 3.0e+02, 3.0e+02, 0.0e+00, 0.0e+00, 1.0e+00, -3.6e+02,\n", + " 3.6e+02, 0.0e+00, 0.0e+00, 0.0e+00, 0.0e+00, 0.0e+00,\n", + " 0.0e+00, 0.0e+00, 0.0e+00]]),\n", + " 'gencost': array([[2.e+00, 0.e+00, 0.e+00, 3.e+00, 1.e-01, 0.e+00, 0.e+00],\n", + " [2.e+00, 0.e+00, 0.e+00, 3.e+00, 1.e-01, 0.e+00, 0.e+00],\n", + " [2.e+00, 0.e+00, 0.e+00, 3.e+00, 1.e-01, 0.e+00, 0.e+00],\n", + " [2.e+00, 0.e+00, 0.e+00, 3.e+00, 0.e+00, 1.e+03, 0.e+00]]),\n", + " 'reserves': {'zones': array([[1., 1., 1., 0.]]),\n", + " 'req': 150.0,\n", + " 'cost': array([[1.],\n", + " [3.],\n", + " [5.]]),\n", + " 'qty': array([[100.],\n", + " [100.],\n", + " [200.]])},\n", + " 'f': 0.0,\n", + " 'et': 0.0,\n", + " 'success': 1.0},\n", + " 'InitialPg': array([[ 125.],\n", + " [ 125.],\n", + " [ 200.],\n", + " [-450.]]),\n", + " 'RampWearCostCoeff': array([[0.],\n", + " [0.],\n", + " [0.],\n", + " [0.]]),\n", + " 'offer': {'PositiveActiveReservePrice': array([[0.],\n", + " [0.],\n", + " [0.],\n", + " [0.]]),\n", + " 'PositiveActiveReserveQuantity': array([[ 400.],\n", + " [ 400.],\n", + " [1000.],\n", + " [ 900.]]),\n", + " 'NegativeActiveReservePrice': array([[0.],\n", + " [0.],\n", + " [0.],\n", + " [0.]]),\n", + " 'NegativeActiveReserveQuantity': array([[ 400.],\n", + " [ 400.],\n", + " [1000.],\n", + " [ 900.]]),\n", + " 'PositiveActiveDeltaPrice': array([[0.],\n", + " [0.],\n", + " [0.],\n", + " [0.]]),\n", + " 'NegativeActiveDeltaPrice': array([[0.],\n", + " [0.],\n", + " [0.],\n", + " [0.]]),\n", + " 'PositiveLoadFollowReservePrice': array([[0.],\n", + " [0.],\n", + " [0.],\n", + " [0.]]),\n", + " 'PositiveLoadFollowReserveQuantity': array([[ 400.],\n", + " [ 400.],\n", + " [1000.],\n", + " [ 900.]]),\n", + " 'NegativeLoadFollowReservePrice': array([[0.],\n", + " [0.],\n", + " [0.],\n", + " [0.]]),\n", + " 'NegativeLoadFollowReserveQuantity': array([[ 400.],\n", + " [ 400.],\n", + " [1000.],\n", + " [ 900.]])},\n", + " 'cont': {'contab': []},\n", + " 'DCMODEL': 0.0,\n", + " 'IncludeFixedReserves': 0.0,\n", + " 'SecurityConstrained': 0.0,\n", + " 'QCoordination': 0.0,\n", + " 'alpha': 0.0,\n", + " 'OpenEnded': 1.0,\n", + " 'flow': {'mpc': {'version': '2',\n", + " 'baseMVA': 100.0,\n", + " 'bus': array([[ 1. , 3. , 0. , 0. , 0. , 0. , 1. , 1. ,\n", + " 0. , 135. , 1. , 1.05, 0.95, 30. , 0. , 0. ,\n", + " 0. ],\n", + " [ 2. , 2. , 0. , 0. , 0. , 0. , 1. , 1. ,\n", + " 0. , 135. , 1. , 1.05, 0.95, 30. , 0. , 0. ,\n", + " 0. ],\n", + " [ 3. , 2. , 0. , 0. , 0. , 0. , 1. , 1. ,\n", + " 0. , 135. , 1. , 1.05, 0.95, 30. , 0. , 0. ,\n", + " 0. ]]),\n", + " 'gen': array([[ 1., 150., 0., 25., -25., 1., 100., 1., 200.,\n", + " 0., 0., 0., 0., 0., 0., 0., 0., 250.,\n", + " 250., 0., 0., 0., 0., 0., 0.],\n", + " [ 1., 150., 0., 25., -25., 1., 100., 1., 200.,\n", + " 0., 0., 0., 0., 0., 0., 0., 0., 250.,\n", + " 250., 0., 0., 0., 0., 0., 0.],\n", + " [ 2., 150., 0., 50., -50., 1., 100., 1., 500.,\n", + " 0., 0., 0., 0., 0., 0., 0., 0., 600.,\n", + " 600., 0., 0., 0., 0., 0., 0.],\n", + " [ 3., -450., 0., 0., 0., 1., 100., 1., 0.,\n", + " -450., 0., 0., 0., 0., 0., 0., 0., 500.,\n", + " 500., 0., 0., 0., 970., 0., 0.]]),\n", + " 'branch': array([[ 1.0e+00, 2.0e+00, 5.0e-03, 1.0e-02, 0.0e+00, 3.0e+02,\n", + " 3.0e+02, 3.0e+02, 0.0e+00, 0.0e+00, 1.0e+00, -3.6e+02,\n", + " 3.6e+02, 0.0e+00, 0.0e+00, 0.0e+00, 0.0e+00, 0.0e+00,\n", + " 0.0e+00, 0.0e+00, 0.0e+00],\n", + " [ 1.0e+00, 3.0e+00, 5.0e-03, 1.0e-02, 0.0e+00, 2.4e+02,\n", + " 2.4e+02, 2.4e+02, 0.0e+00, 0.0e+00, 1.0e+00, -3.6e+02,\n", + " 3.6e+02, 0.0e+00, 0.0e+00, 0.0e+00, 0.0e+00, 0.0e+00,\n", + " 0.0e+00, 0.0e+00, 0.0e+00],\n", + " [ 2.0e+00, 3.0e+00, 5.0e-03, 1.0e-02, 0.0e+00, 3.0e+02,\n", + " 3.0e+02, 3.0e+02, 0.0e+00, 0.0e+00, 1.0e+00, -3.6e+02,\n", + " 3.6e+02, 0.0e+00, 0.0e+00, 0.0e+00, 0.0e+00, 0.0e+00,\n", + " 0.0e+00, 0.0e+00, 0.0e+00]]),\n", + " 'gencost': array([[2.e+00, 0.e+00, 0.e+00, 3.e+00, 1.e-01, 0.e+00, 0.e+00],\n", + " [2.e+00, 0.e+00, 0.e+00, 3.e+00, 1.e-01, 0.e+00, 0.e+00],\n", + " [2.e+00, 0.e+00, 0.e+00, 3.e+00, 1.e-01, 0.e+00, 0.e+00],\n", + " [2.e+00, 0.e+00, 0.e+00, 3.e+00, 0.e+00, 1.e+03, 0.e+00]]),\n", + " 'reserves': {'zones': array([[1., 1., 1., 0.]]),\n", + " 'req': 150.0,\n", + " 'cost': array([[1.],\n", + " [3.],\n", + " [5.]]),\n", + " 'qty': array([[100.],\n", + " [100.],\n", + " [200.]])},\n", + " 'f': 0.0,\n", + " 'et': 0.0,\n", + " 'success': 1.0}},\n", + " 'StepProb': 1.0,\n", + " 'CostWeights': 1.0,\n", + " 'CostWeightsAdj': 1.0,\n", + " 'QP': {'A': ,\n", + " 'l': array([[0.],\n", + " [0.],\n", + " [0.],\n", + " [0.],\n", + " [0.],\n", + " [0.],\n", + " [0.],\n", + " [0.],\n", + " [0.],\n", + " [0.],\n", + " [0.],\n", + " [0.],\n", + " [0.]]),\n", + " 'u': array([[ 0.],\n", + " [inf],\n", + " [inf],\n", + " [inf],\n", + " [inf],\n", + " [inf],\n", + " [inf],\n", + " [inf],\n", + " [inf],\n", + " [ 0.],\n", + " [ 0.],\n", + " [ 0.],\n", + " [ 0.]]),\n", + " 'x0': array([[ 1.25],\n", + " [ 1.25],\n", + " [ 2. ],\n", + " [-4.5 ],\n", + " [ 0. ],\n", + " [ 0. ],\n", + " [ 0. ],\n", + " [ 0. ],\n", + " [ 0. ],\n", + " [ 0. ],\n", + " [ 0. ],\n", + " [ 0. ],\n", + " [ 0. ],\n", + " [ 0. ],\n", + " [ 0. ],\n", + " [ 0. ],\n", + " [ 0. ],\n", + " [ 0. ],\n", + " [ 0. ],\n", + " [ 0. ],\n", + " [ 0. ],\n", + " [ 0. ],\n", + " [ 0. ],\n", + " [ 0. ]]),\n", + " 'xmin': array([[ 0. ],\n", + " [ 0. ],\n", + " [ 0. ],\n", + " [-4.5],\n", + " [ 0. ],\n", + " [ 0. ],\n", + " [ 0. ],\n", + " [ 0. ],\n", + " [ 0. ],\n", + " [ 0. ],\n", + " [ 0. ],\n", + " [ 0. ],\n", + " [-inf],\n", + " [-inf],\n", + " [-inf],\n", + " [-inf],\n", + " [-inf],\n", + " [-inf],\n", + " [-inf],\n", + " [-inf],\n", + " [-inf],\n", + " [-inf],\n", + " [-inf],\n", + " [-inf]]),\n", + " 'xmax': array([[ 2.],\n", + " [ 2.],\n", + " [ 5.],\n", + " [ 0.],\n", + " [inf],\n", + " [inf],\n", + " [inf],\n", + " [inf],\n", + " [inf],\n", + " [inf],\n", + " [inf],\n", + " [inf],\n", + " [inf],\n", + " [inf],\n", + " [inf],\n", + " [inf],\n", + " [ 4.],\n", + " [ 4.],\n", + " [10.],\n", + " [ 9.],\n", + " [ 4.],\n", + " [ 4.],\n", + " [10.],\n", + " [ 9.]]),\n", + " 'vtype': 'CCCCCCCCCCCCCCCCCCCCCCCC',\n", + " 'H1': ,\n", + " 'C1': array([[ 0.],\n", + " [ 0.],\n", + " [ 0.],\n", + " [100000.],\n", + " [ 0.],\n", + " [ 0.],\n", + " [ 0.],\n", + " [ 0.],\n", + " [ 0.],\n", + " [ 0.],\n", + " [ 0.],\n", + " [ 0.],\n", + " [ 0.],\n", + " [ 0.],\n", + " [ 0.],\n", + " [ 0.],\n", + " [ 0.],\n", + " [ 0.],\n", + " [ 0.],\n", + " [ 0.],\n", + " [ 0.],\n", + " [ 0.],\n", + " [ 0.],\n", + " [ 0.]]),\n", + " 'c1': 0.0,\n", + " 'Cfstor': ,\n", + " 'H': ,\n", + " 'C': array([[ 0.],\n", + " [ 0.],\n", + " [ 0.],\n", + " [100000.],\n", + " [ 0.],\n", + " [ 0.],\n", + " [ 0.],\n", + " [ 0.],\n", + " [ 0.],\n", + " [ 0.],\n", + " [ 0.],\n", + " [ 0.],\n", + " [ 0.],\n", + " [ 0.],\n", + " [ 0.],\n", + " [ 0.],\n", + " [ 0.],\n", + " [ 0.],\n", + " [ 0.],\n", + " [ 0.],\n", + " [ 0.],\n", + " [ 0.],\n", + " [ 0.],\n", + " [ 0.]]),\n", + " 'c': 0.0,\n", + " 'opt': {'alg': 'MIPS',\n", + " 'verbose': 1.0,\n", + " 'mips_opt': {'step_control': 0.0,\n", + " 'linsolver': '',\n", + " 'feastol': 5e-06,\n", + " 'gradtol': 1e-06,\n", + " 'comptol': 1e-06,\n", + " 'costtol': 1e-06,\n", + " 'max_it': 150.0,\n", + " 'sc': {'red_it': 20.0}},\n", + " 'x0': []},\n", + " 'x': array([[ 1.5 ],\n", + " [ 1.5 ],\n", + " [ 1.5 ],\n", + " [-4.5 ],\n", + " [ 1.07383412],\n", + " [ 1.07383412],\n", + " [ 2.2194983 ],\n", + " [ 2.03796577],\n", + " [ 1.07383412],\n", + " [ 1.07383412],\n", + " [ 2.2194983 ],\n", + " [ 2.03796577],\n", + " [ 1.5 ],\n", + " [ 1.5 ],\n", + " [ 1.5 ],\n", + " [-4.5 ],\n", + " [ 2.14766824],\n", + " [ 2.14766824],\n", + " [ 4.43899661],\n", + " [ 4.07593154],\n", + " [ 2.14766824],\n", + " [ 2.14766824],\n", + " [ 4.43899661],\n", + " [ 4.07593154]]),\n", + " 'f': -443249.9999999571,\n", + " 'exitflag': 1.0,\n", + " 'output': {'iterations': 10.0,\n", + " 'hist': 1x11 StructArray containing the fields:\n", + " feascond\n", + " gradcond\n", + " compcond\n", + " costcond\n", + " gamma\n", + " stepsize\n", + " obj\n", + " alphap\n", + " alphad,\n", + " 'message': 'Converged',\n", + " 'alg': 'MIPS'},\n", + " 'lambda': {'mu_l': array([[3000.00000003],\n", + " [ 0. ],\n", + " [ 0. ],\n", + " [ 0. ],\n", + " [ 0. ],\n", + " [ 0. ],\n", + " [ 0. ],\n", + " [ 0. ],\n", + " [ 0. ],\n", + " [ 0. ],\n", + " [ 0. ],\n", + " [ 0. ],\n", + " [ 0. ]]),\n", + " 'mu_u': array([[0.],\n", + " [0.],\n", + " [0.],\n", + " [0.],\n", + " [0.],\n", + " [0.],\n", + " [0.],\n", + " [0.],\n", + " [0.],\n", + " [0.],\n", + " [0.],\n", + " [0.],\n", + " [0.]]),\n", + " 'lower': array([[ 0. ],\n", + " [ 0. ],\n", + " [ 0. ],\n", + " [96999.99999998],\n", + " [ 0. ],\n", + " [ 0. ],\n", + " [ 0. ],\n", + " [ 0. ],\n", + " [ 0. ],\n", + " [ 0. ],\n", + " [ 0. ],\n", + " [ 0. ],\n", + " [ 0. ],\n", + " [ 0. ],\n", + " [ 0. ],\n", + " [ 0. ],\n", + " [ 0. ],\n", + " [ 0. ],\n", + " [ 0. ],\n", + " [ 0. ],\n", + " [ 0. ],\n", + " [ 0. ],\n", + " [ 0. ],\n", + " [ 0. ]]),\n", + " 'upper': array([[0.],\n", + " [0.],\n", + " [0.],\n", + " [0.],\n", + " [0.],\n", + " [0.],\n", + " [0.],\n", + " [0.],\n", + " [0.],\n", + " [0.],\n", + " [0.],\n", + " [0.],\n", + " [0.],\n", + " [0.],\n", + " [0.],\n", + " [0.],\n", + " [0.],\n", + " [0.],\n", + " [0.],\n", + " [0.],\n", + " [0.],\n", + " [0.],\n", + " [0.],\n", + " [0.]])}},\n", + " 'om': ,\n", + " 'results': {'Pc': array([[ 150.],\n", + " [ 150.],\n", + " [ 150.],\n", + " [-450.]]),\n", + " 'Rpp': array([[214.76682448],\n", + " [214.76682448],\n", + " [443.89966099],\n", + " [407.59315363]]),\n", + " 'Rpm': array([[214.76682448],\n", + " [214.76682448],\n", + " [443.89966099],\n", + " [407.59315363]]),\n", + " 'GenPrices': array([[30.],\n", + " [30.],\n", + " [30.],\n", + " [30.]]),\n", + " 'CondGenPrices': array([[30.],\n", + " [30.],\n", + " [30.],\n", + " [30.]]),\n", + " 'RppPrices': array([[0.],\n", + " [0.],\n", + " [0.],\n", + " [0.]]),\n", + " 'RpmPrices': array([[0.],\n", + " [0.],\n", + " [0.],\n", + " [0.]]),\n", + " 'RrpPrices': array([], shape=(4, 0), dtype=float64),\n", + " 'RrmPrices': array([], shape=(4, 0), dtype=float64),\n", + " 'ExpectedRampCost': array([], shape=(4, 0), dtype=float64),\n", + " 'ExpectedDispatch': array([[ 150.],\n", + " [ 150.],\n", + " [ 150.],\n", + " [-450.]]),\n", + " 'f': -443249.9999999571,\n", + " 'success': 1.0,\n", + " 'SolveTime': 0.017119884490966797,\n", + " 'SetupTime': 0.036622047424316406}}" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# TODO:\n", + "# 1. Convert mdo into DataFrame\n", + "# 2. Clean up classdef element has been converted to a struct\n", + "# mdo: MOST data structure output\n", + "mdo = m.most(mdi, mpopt)\n", + "mdo" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "5faf9637", + "metadata": {}, + "outputs": [], + "source": [ + "# TODO:\n", + "# 1. Fix error\n", + "# Oct2PyError: Octave evaluation error:\n", + "# error: get: H must be a graphics handle\n", + "# error: called from:\n", + "# 2. ms is most summary\n", + "\n", + "# ms = m.most_summary(mdo)\n", + "# ms" + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "id": "f798f194", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
INFO
version2
baseMVA100.0
f0.0
et0.0
success1.0
\n", + "
" + ], + "text/plain": [ + " INFO\n", + "version 2\n", + "baseMVA 100.0\n", + "f 0.0\n", + "et 0.0\n", + "success 1.0" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "bus\n" + ] + }, + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
BUS_IBUS_TYPEPDQDGSBSBUS_AREAVMVABASE_KVZONEVMAXVMINLAM_PLAM_QMU_VMAXMU_VMIN
bus
11.03.00.00.00.00.01.01.00.0135.01.01.050.9530.00.00.00.0
22.02.00.00.00.00.01.01.00.0135.01.01.050.9530.00.00.00.0
33.02.00.00.00.00.01.01.00.0135.01.01.050.9530.00.00.00.0
\n", + "
" + ], + "text/plain": [ + " BUS_I BUS_TYPE PD QD GS BS BUS_AREA VM VA BASE_KV ZONE \\\n", + "bus \n", + "1 1.0 3.0 0.0 0.0 0.0 0.0 1.0 1.0 0.0 135.0 1.0 \n", + "2 2.0 2.0 0.0 0.0 0.0 0.0 1.0 1.0 0.0 135.0 1.0 \n", + "3 3.0 2.0 0.0 0.0 0.0 0.0 1.0 1.0 0.0 135.0 1.0 \n", + "\n", + " VMAX VMIN LAM_P LAM_Q MU_VMAX MU_VMIN \n", + "bus \n", + "1 1.05 0.95 30.0 0.0 0.0 0.0 \n", + "2 1.05 0.95 30.0 0.0 0.0 0.0 \n", + "3 1.05 0.95 30.0 0.0 0.0 0.0 " + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "gen\n" + ] + }, + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
GEN_BUSPGQGQMAXQMINVGMBASEGEN_STATUSPMAXPMIN...QC2MAXRAMP_AGCRAMP_10RAMP_30RAMP_QAPFMU_PMAXMU_PMINMU_QMAXMU_QMIN
gen
11.0150.00.025.0-25.01.0100.01.0200.00.0...0.00.0250.0250.00.00.00.00.00.00.0
21.0150.00.025.0-25.01.0100.01.0200.00.0...0.00.0250.0250.00.00.00.00.00.00.0
32.0150.00.050.0-50.01.0100.01.0500.00.0...0.00.0600.0600.00.00.00.00.00.00.0
43.0-450.00.00.00.01.0100.01.00.0-450.0...0.00.0500.0500.00.00.00.0970.00.00.0
\n", + "

4 rows × 25 columns

\n", + "
" + ], + "text/plain": [ + " GEN_BUS PG QG QMAX QMIN VG MBASE GEN_STATUS PMAX PMIN \\\n", + "gen \n", + "1 1.0 150.0 0.0 25.0 -25.0 1.0 100.0 1.0 200.0 0.0 \n", + "2 1.0 150.0 0.0 25.0 -25.0 1.0 100.0 1.0 200.0 0.0 \n", + "3 2.0 150.0 0.0 50.0 -50.0 1.0 100.0 1.0 500.0 0.0 \n", + "4 3.0 -450.0 0.0 0.0 0.0 1.0 100.0 1.0 0.0 -450.0 \n", + "\n", + " ... QC2MAX RAMP_AGC RAMP_10 RAMP_30 RAMP_Q APF MU_PMAX MU_PMIN \\\n", + "gen ... \n", + "1 ... 0.0 0.0 250.0 250.0 0.0 0.0 0.0 0.0 \n", + "2 ... 0.0 0.0 250.0 250.0 0.0 0.0 0.0 0.0 \n", + "3 ... 0.0 0.0 600.0 600.0 0.0 0.0 0.0 0.0 \n", + "4 ... 0.0 0.0 500.0 500.0 0.0 0.0 0.0 970.0 \n", + "\n", + " MU_QMAX MU_QMIN \n", + "gen \n", + "1 0.0 0.0 \n", + "2 0.0 0.0 \n", + "3 0.0 0.0 \n", + "4 0.0 0.0 \n", + "\n", + "[4 rows x 25 columns]" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "branch\n" + ] + }, + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
F_BUST_BUSBR_RBR_XBR_BRATE_ARATE_BRATE_CTAPSHIFT...ANGMINANGMAXPFQFPTQTMU_SFMU_STMU_ANGMINMU_ANGMAX
branch
11.02.00.0050.010.0300.0300.0300.00.00.0...-360.0360.00.00.00.00.00.00.00.00.0
21.03.00.0050.010.0240.0240.0240.00.00.0...-360.0360.00.00.00.00.00.00.00.00.0
32.03.00.0050.010.0300.0300.0300.00.00.0...-360.0360.00.00.00.00.00.00.00.00.0
\n", + "

3 rows × 21 columns

\n", + "
" + ], + "text/plain": [ + " F_BUS T_BUS BR_R BR_X BR_B RATE_A RATE_B RATE_C TAP SHIFT \\\n", + "branch \n", + "1 1.0 2.0 0.005 0.01 0.0 300.0 300.0 300.0 0.0 0.0 \n", + "2 1.0 3.0 0.005 0.01 0.0 240.0 240.0 240.0 0.0 0.0 \n", + "3 2.0 3.0 0.005 0.01 0.0 300.0 300.0 300.0 0.0 0.0 \n", + "\n", + " ... ANGMIN ANGMAX PF QF PT QT MU_SF MU_ST MU_ANGMIN \\\n", + "branch ... \n", + "1 ... -360.0 360.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 \n", + "2 ... -360.0 360.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 \n", + "3 ... -360.0 360.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 \n", + "\n", + " MU_ANGMAX \n", + "branch \n", + "1 0.0 \n", + "2 0.0 \n", + "3 0.0 \n", + "\n", + "[3 rows x 21 columns]" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "gencost\n" + ] + }, + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
MODELSTARTUPSHUTDOWNNCOSTC2C1C0
gen
12.00.00.03.00.10.00.0
22.00.00.03.00.10.00.0
32.00.00.03.00.10.00.0
42.00.00.03.00.01000.00.0
\n", + "
" + ], + "text/plain": [ + " MODEL STARTUP SHUTDOWN NCOST C2 C1 C0\n", + "gen \n", + "1 2.0 0.0 0.0 3.0 0.1 0.0 0.0\n", + "2 2.0 0.0 0.0 3.0 0.1 0.0 0.0\n", + "3 2.0 0.0 0.0 3.0 0.1 0.0 0.0\n", + "4 2.0 0.0 0.0 3.0 0.0 1000.0 0.0" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, { "name": "stdout", "output_type": "stream", @@ -345,10 +2833,41 @@ } ], "source": [ - "cf = CaseFrames(os.path.join(path_most_ex_cases, \"ex_case3a.m\"), load_case_engine=m)\n", - "for attribute in cf.reserves._attributes:\n", - " print(attribute)\n", - " display(getattr(cf.reserves, attribute))" + "cfr2 = CaseFrames(mdo.flow.mpc)\n", + "display_cf(cfr2)" + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "id": "8c9c81c2", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "(gen\n", + " 1 150.0\n", + " 2 150.0\n", + " 3 150.0\n", + " 4 -450.0\n", + " Name: PG, dtype: float64,\n", + " bus\n", + " 1 30.0\n", + " 2 30.0\n", + " 3 30.0\n", + " Name: LAM_P, dtype: float64)" + ] + }, + "execution_count": 27, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "Pg2 = cfr2.gen.loc[:, \"PG\"] # active generation\n", + "lam2 = cfr2.bus.loc[:, \"LAM_P\"] # nodal energy price\n", + "Pg2, lam2" ] }, { @@ -1764,11 +4283,12 @@ }, { "cell_type": "code", - "execution_count": 89, + "execution_count": null, "id": "676057c0", "metadata": {}, "outputs": [], "source": [ + "# TODO: convert profiles into DataFrame\n", "profiles = m.getprofiles(\"ex_wind_profile_d\", iwind) # add wind profiles\n", "profiles = m.getprofiles(\"ex_load_profile\", profiles) # update profiles with load\n", "wind_profile, load_profile = profiles[0], profiles[1]" From 3ac1e8c72174bb64555547e50a7d3fc1e7861236 Mon Sep 17 00:00:00 2001 From: Muhammad Yasirroni Date: Mon, 19 Jan 2026 23:13:26 +1100 Subject: [PATCH 17/17] update display show struct structure --- matpowercaseframes/core.py | 11 +- notebooks/load_most_ex_case3a.ipynb | 215 +++++++++++++--------------- 2 files changed, 107 insertions(+), 119 deletions(-) diff --git a/matpowercaseframes/core.py b/matpowercaseframes/core.py index 6be9588..977c54b 100644 --- a/matpowercaseframes/core.py +++ b/matpowercaseframes/core.py @@ -147,22 +147,23 @@ def infer_numpy(self): elif isinstance(df, DataFramesStruct): df.infer_numpy() - def display(self): + def display(self, prefix=""): data = {"INFO": {}} for attribute in ATTRIBUTES_INFO: if attribute in self._attributes: data["INFO"][attribute] = getattr(self, attribute, None) - display(pd.DataFrame(data=data)) + if data["INFO"]: + print(prefix + "info") + display(pd.DataFrame(data=data)) for attribute in self._attributes: df = getattr(self, attribute) if isinstance(df, pd.DataFrame): - print(attribute) + print(prefix + f"{attribute}") display(df) if isinstance(getattr(self, attribute), DataFramesStruct): - print(attribute) - df.display() + df.display(prefix=prefix + f"{attribute}.") class DataFrameStruct(BaseStruct): diff --git a/notebooks/load_most_ex_case3a.ipynb b/notebooks/load_most_ex_case3a.ipynb index 74edbf7..f04c7c5 100644 --- a/notebooks/load_most_ex_case3a.ipynb +++ b/notebooks/load_most_ex_case3a.ipynb @@ -64,18 +64,16 @@ }, { "cell_type": "code", - "execution_count": 22, + "execution_count": 4, "id": "3116b86b", "metadata": {}, "outputs": [], "source": [ "import os\n", "\n", - "import pandas as pd\n", "from matpower import path_matpower, start_instance\n", "\n", - "from matpowercaseframes import CaseFrames, xGenDataTableFrames\n", - "from matpowercaseframes.constants import ATTRIBUTES_INFO" + "from matpowercaseframes import CaseFrames, xGenDataTableFrames" ] }, { @@ -211,35 +209,17 @@ }, { "cell_type": "code", - "execution_count": 23, - "id": "9cbd4a01", - "metadata": {}, - "outputs": [], - "source": [ - "def display_cf(cf):\n", - " data = {\"INFO\": {}}\n", - " for attribute in ATTRIBUTES_INFO:\n", - " if attribute in cf._attributes:\n", - " data[\"INFO\"][attribute] = getattr(cf, attribute, None)\n", - " display(pd.DataFrame(data=data))\n", - "\n", - " for attribute in cf._attributes:\n", - " if attribute in ATTRIBUTES_INFO or attribute == \"reserves\":\n", - " continue\n", - " print(attribute)\n", - " display(getattr(cf, attribute))\n", - "\n", - " for attribute in cf.reserves._attributes:\n", - " print(attribute)\n", - " display(getattr(cf.reserves, attribute))" - ] - }, - { - "cell_type": "code", - "execution_count": 24, + "execution_count": 8, "id": "87bc9631", "metadata": {}, "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "info\n" + ] + }, { "data": { "text/html": [ @@ -857,7 +837,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "zones\n" + "reserves.zones\n" ] }, { @@ -919,7 +899,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "req\n" + "reserves.req\n" ] }, { @@ -972,7 +952,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "cost\n" + "reserves.cost\n" ] }, { @@ -1035,7 +1015,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "qty\n" + "reserves.qty\n" ] }, { @@ -1097,7 +1077,7 @@ ], "source": [ "cf = CaseFrames(os.path.join(path_most_ex_cases, \"ex_case3a.m\"), load_case_engine=m)\n", - "display_cf(cf)" + "cf.display()" ] }, { @@ -1813,7 +1793,7 @@ " [0.],\n", " [0.],\n", " [0.]])}},\n", - " 'om': ,\n", + " 'om': ,\n", " 'results': {'Pc': array([[ 150.],\n", " [ 150.],\n", " [ 150.],\n", @@ -1851,8 +1831,8 @@ " [-450.]]),\n", " 'f': -443249.9999999571,\n", " 'success': 1.0,\n", - " 'SolveTime': 0.017119884490966797,\n", - " 'SetupTime': 0.036622047424316406}}" + " 'SolveTime': 0.01789093017578125,\n", + " 'SetupTime': 0.03305697441101074}}" ] }, "execution_count": 10, @@ -1889,10 +1869,17 @@ }, { "cell_type": "code", - "execution_count": 25, + "execution_count": 12, "id": "f798f194", "metadata": {}, "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "info\n" + ] + }, { "data": { "text/html": [ @@ -2594,7 +2581,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "zones\n" + "reserves.zones\n" ] }, { @@ -2656,7 +2643,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "req\n" + "reserves.req\n" ] }, { @@ -2709,7 +2696,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "cost\n" + "reserves.cost\n" ] }, { @@ -2772,7 +2759,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "qty\n" + "reserves.qty\n" ] }, { @@ -2834,12 +2821,12 @@ ], "source": [ "cfr2 = CaseFrames(mdo.flow.mpc)\n", - "display_cf(cfr2)" + "cfr2.display()" ] }, { "cell_type": "code", - "execution_count": 27, + "execution_count": 13, "id": "8c9c81c2", "metadata": {}, "outputs": [ @@ -2859,7 +2846,7 @@ " Name: LAM_P, dtype: float64)" ] }, - "execution_count": 27, + "execution_count": 13, "metadata": {}, "output_type": "execute_result" } @@ -2882,7 +2869,7 @@ }, { "cell_type": "code", - "execution_count": 80, + "execution_count": 14, "id": "575be760", "metadata": {}, "outputs": [ @@ -3057,7 +3044,7 @@ "4 800.0 " ] }, - "execution_count": 80, + "execution_count": 14, "metadata": {}, "output_type": "execute_result" } @@ -3075,7 +3062,7 @@ }, { "cell_type": "code", - "execution_count": 81, + "execution_count": 15, "id": "63a198e4", "metadata": {}, "outputs": [ @@ -3275,7 +3262,7 @@ "4 1.0 " ] }, - "execution_count": 81, + "execution_count": 15, "metadata": {}, "output_type": "execute_result" } @@ -3288,7 +3275,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 16, "id": "13caf9af", "metadata": {}, "outputs": [ @@ -3453,26 +3440,6 @@ " 1.0\n", " 1.0\n", " \n", - " \n", - " 6\n", - " 1.0\n", - " 0.0\n", - " 0.0\n", - " 1.000000e-08\n", - " 200.0\n", - " 2.000000e-08\n", - " 200.0\n", - " 1.000000e-09\n", - " 1.000000e-09\n", - " 0.000001\n", - " 200.0\n", - " 0.000001\n", - " 200.0\n", - " 2.0\n", - " inf\n", - " 1.0\n", - " 1.0\n", - " \n", " \n", "\n", "" @@ -3485,7 +3452,6 @@ "3 1.0 200.0 0.0 1.500000e+00 \n", "4 1.0 -450.0 0.0 1.000000e-08 \n", "5 1.0 0.0 0.0 1.000000e-08 \n", - "6 1.0 0.0 0.0 1.000000e-08 \n", "\n", " PositiveActiveReserveQuantity NegativeActiveReservePrice \\\n", "gen \n", @@ -3494,7 +3460,6 @@ "3 600.0 3.000000e+00 \n", "4 800.0 2.000000e-08 \n", "5 200.0 2.000000e-08 \n", - "6 200.0 2.000000e-08 \n", "\n", " NegativeActiveReserveQuantity PositiveActiveDeltaPrice \\\n", "gen \n", @@ -3503,7 +3468,6 @@ "3 600.0 1.000000e-09 \n", "4 800.0 1.000000e-09 \n", "5 200.0 1.000000e-09 \n", - "6 200.0 1.000000e-09 \n", "\n", " NegativeActiveDeltaPrice PositiveLoadFollowReservePrice \\\n", "gen \n", @@ -3512,7 +3476,6 @@ "3 1.000000e-09 10.000000 \n", "4 1.000000e-09 0.000001 \n", "5 1.000000e-09 0.000001 \n", - "6 1.000000e-09 0.000001 \n", "\n", " PositiveLoadFollowReserveQuantity NegativeLoadFollowReservePrice \\\n", "gen \n", @@ -3521,7 +3484,6 @@ "3 100.0 10.000000 \n", "4 800.0 0.000001 \n", "5 200.0 0.000001 \n", - "6 200.0 0.000001 \n", "\n", " NegativeLoadFollowReserveQuantity CommitKey InitialState MinUp \\\n", "gen \n", @@ -3530,7 +3492,6 @@ "3 250.0 1.0 inf 1.0 \n", "4 800.0 2.0 inf 1.0 \n", "5 200.0 2.0 inf 1.0 \n", - "6 200.0 2.0 inf 1.0 \n", "\n", " MinDown \n", "gen \n", @@ -3538,8 +3499,7 @@ "2 1.0 \n", "3 1.0 \n", "4 1.0 \n", - "5 1.0 \n", - "6 1.0 " + "5 1.0 " ] }, "metadata": {}, @@ -3734,33 +3694,9 @@ " 0.0\n", " 0.0\n", " \n", - " \n", - " 6\n", - " 2.0\n", - " 0.0\n", - " 0.0\n", - " 50.0\n", - " -50.0\n", - " 1.0\n", - " 100.0\n", - " 1.0\n", - " 100.0\n", - " 0.0\n", - " ...\n", - " 0.0\n", - " 0.0\n", - " 0.0\n", - " 0.0\n", - " 0.0\n", - " 0.0\n", - " 200.0\n", - " 200.0\n", - " 0.0\n", - " 0.0\n", - " \n", " \n", "\n", - "

6 rows × 21 columns

\n", + "

5 rows × 21 columns

\n", "" ], "text/plain": [ @@ -3771,7 +3707,6 @@ "3 2.0 200.0 0.0 50.0 -50.0 1.0 100.0 1.0 500.0 0.0 \n", "4 3.0 -450.0 0.0 0.0 0.0 1.0 100.0 1.0 0.0 -450.0 \n", "5 2.0 0.0 0.0 50.0 -50.0 1.0 100.0 1.0 100.0 0.0 \n", - "6 2.0 0.0 0.0 50.0 -50.0 1.0 100.0 1.0 100.0 0.0 \n", "\n", " ... PC2 QC1MIN QC1MAX QC2MIN QC2MAX RAMP_AGC RAMP_10 RAMP_30 \\\n", "gen ... \n", @@ -3780,7 +3715,6 @@ "3 ... 0.0 0.0 0.0 0.0 0.0 0.0 600.0 600.0 \n", "4 ... 0.0 0.0 0.0 0.0 0.0 0.0 500.0 500.0 \n", "5 ... 0.0 0.0 0.0 0.0 0.0 0.0 200.0 200.0 \n", - "6 ... 0.0 0.0 0.0 0.0 0.0 0.0 200.0 200.0 \n", "\n", " RAMP_Q APF \n", "gen \n", @@ -3789,9 +3723,8 @@ "3 0.0 0.0 \n", "4 0.0 0.0 \n", "5 0.0 0.0 \n", - "6 0.0 0.0 \n", "\n", - "[6 rows x 21 columns]" + "[5 rows x 21 columns]" ] }, "metadata": {}, @@ -3812,7 +3745,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 17, "id": "126c9a34", "metadata": {}, "outputs": [ @@ -3977,6 +3910,26 @@ " 1.0\n", " 1.0\n", " \n", + " \n", + " 6\n", + " 1.0\n", + " 0.0\n", + " 0.0\n", + " 1.000000e-08\n", + " 160.0\n", + " 2.000000e-08\n", + " 160.0\n", + " 1.000000e-09\n", + " 1.000000e-09\n", + " 0.000001\n", + " 160.0\n", + " 0.000001\n", + " 160.0\n", + " 2.0\n", + " inf\n", + " 1.0\n", + " 1.0\n", + " \n", " \n", "\n", "" @@ -3989,6 +3942,7 @@ "3 1.0 200.0 0.0 1.500000e+00 \n", "4 1.0 -450.0 0.0 1.000000e-08 \n", "5 1.0 0.0 0.0 1.000000e-08 \n", + "6 1.0 0.0 0.0 1.000000e-08 \n", "\n", " PositiveActiveReserveQuantity NegativeActiveReservePrice \\\n", "gen \n", @@ -3997,6 +3951,7 @@ "3 600.0 3.000000e+00 \n", "4 800.0 2.000000e-08 \n", "5 200.0 2.000000e-08 \n", + "6 160.0 2.000000e-08 \n", "\n", " NegativeActiveReserveQuantity PositiveActiveDeltaPrice \\\n", "gen \n", @@ -4005,6 +3960,7 @@ "3 600.0 1.000000e-09 \n", "4 800.0 1.000000e-09 \n", "5 200.0 1.000000e-09 \n", + "6 160.0 1.000000e-09 \n", "\n", " NegativeActiveDeltaPrice PositiveLoadFollowReservePrice \\\n", "gen \n", @@ -4013,6 +3969,7 @@ "3 1.000000e-09 10.000000 \n", "4 1.000000e-09 0.000001 \n", "5 1.000000e-09 0.000001 \n", + "6 1.000000e-09 0.000001 \n", "\n", " PositiveLoadFollowReserveQuantity NegativeLoadFollowReservePrice \\\n", "gen \n", @@ -4021,6 +3978,7 @@ "3 100.0 10.000000 \n", "4 800.0 0.000001 \n", "5 200.0 0.000001 \n", + "6 160.0 0.000001 \n", "\n", " NegativeLoadFollowReserveQuantity CommitKey InitialState MinUp \\\n", "gen \n", @@ -4029,6 +3987,7 @@ "3 250.0 1.0 inf 1.0 \n", "4 800.0 2.0 inf 1.0 \n", "5 200.0 2.0 inf 1.0 \n", + "6 160.0 2.0 inf 1.0 \n", "\n", " MinDown \n", "gen \n", @@ -4036,7 +3995,8 @@ "2 1.0 \n", "3 1.0 \n", "4 1.0 \n", - "5 1.0 " + "5 1.0 \n", + "6 1.0 " ] }, "metadata": {}, @@ -4231,9 +4191,33 @@ " 0.0\n", " 0.0\n", " \n", + " \n", + " 6\n", + " 3.0\n", + " 0.0\n", + " 0.0\n", + " 0.0\n", + " 0.0\n", + " 1.0\n", + " 100.0\n", + " 1.0\n", + " 80.0\n", + " -80.0\n", + " ...\n", + " 0.0\n", + " 0.0\n", + " 0.0\n", + " 0.0\n", + " 0.0\n", + " 0.0\n", + " 20.0\n", + " 20.0\n", + " 0.0\n", + " 0.0\n", + " \n", " \n", "\n", - "

5 rows × 21 columns

\n", + "

6 rows × 21 columns

\n", "" ], "text/plain": [ @@ -4244,6 +4228,7 @@ "3 2.0 200.0 0.0 50.0 -50.0 1.0 100.0 1.0 500.0 0.0 \n", "4 3.0 -450.0 0.0 0.0 0.0 1.0 100.0 1.0 0.0 -450.0 \n", "5 2.0 0.0 0.0 50.0 -50.0 1.0 100.0 1.0 100.0 0.0 \n", + "6 3.0 0.0 0.0 0.0 0.0 1.0 100.0 1.0 80.0 -80.0 \n", "\n", " ... PC2 QC1MIN QC1MAX QC2MIN QC2MAX RAMP_AGC RAMP_10 RAMP_30 \\\n", "gen ... \n", @@ -4252,6 +4237,7 @@ "3 ... 0.0 0.0 0.0 0.0 0.0 0.0 600.0 600.0 \n", "4 ... 0.0 0.0 0.0 0.0 0.0 0.0 500.0 500.0 \n", "5 ... 0.0 0.0 0.0 0.0 0.0 0.0 200.0 200.0 \n", + "6 ... 0.0 0.0 0.0 0.0 0.0 0.0 20.0 20.0 \n", "\n", " RAMP_Q APF \n", "gen \n", @@ -4260,8 +4246,9 @@ "3 0.0 0.0 \n", "4 0.0 0.0 \n", "5 0.0 0.0 \n", + "6 0.0 0.0 \n", "\n", - "[5 rows x 21 columns]" + "[6 rows x 21 columns]" ] }, "metadata": {}, @@ -4283,7 +4270,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 18, "id": "676057c0", "metadata": {}, "outputs": [], @@ -4296,7 +4283,7 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 19, "id": "159b9137", "metadata": {}, "outputs": [],