diff --git a/docs/examples/Asymmetric Calculation Example.ipynb b/docs/examples/Asymmetric Calculation Example.ipynb index 54724adde..0b3175711 100644 --- a/docs/examples/Asymmetric Calculation Example.ipynb +++ b/docs/examples/Asymmetric Calculation Example.ipynb @@ -815,7 +815,7 @@ ], "metadata": { "kernelspec": { - "display_name": "Python 3 (ipykernel)", + "display_name": "venv", "language": "python", "name": "python3" }, @@ -829,12 +829,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.12.2" - }, - "vscode": { - "interpreter": { - "hash": "f2534ebf7a1a13ecc51c3a04ce741b49cf1feb97b5ca55170ed7b2036b4058c0" - } + "version": "3.13.0" } }, "nbformat": 4, diff --git a/docs/examples/Make Test Dataset.ipynb b/docs/examples/Make Test Dataset.ipynb index 54e15205a..1258600be 100644 --- a/docs/examples/Make Test Dataset.ipynb +++ b/docs/examples/Make Test Dataset.ipynb @@ -431,7 +431,7 @@ ], "metadata": { "kernelspec": { - "display_name": "Python 3 (ipykernel)", + "display_name": "venv", "language": "python", "name": "python3" }, @@ -445,12 +445,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.12.2" - }, - "vscode": { - "interpreter": { - "hash": "39f035c5e58f53e973c1999fdd4393d6db2dd3f04fe05f524988f90f4bbdb576" - } + "version": "3.13.0" } }, "nbformat": 4, diff --git a/docs/examples/Power Flow Example.ipynb b/docs/examples/Power Flow Example.ipynb index c084ac3a6..251332de1 100644 --- a/docs/examples/Power Flow Example.ipynb +++ b/docs/examples/Power Flow Example.ipynb @@ -51,9 +51,9 @@ " # suppress warning about pyarrow as future required dependency\n", " import pandas as pd\n", "\n", - "from power_grid_model import LoadGenType, ComponentType, DatasetType\n", + "from power_grid_model import LoadGenType, ComponentType, DatasetType, ComponentAttributeFilterOptions\n", "from power_grid_model import PowerGridModel, CalculationMethod, CalculationType\n", - "from power_grid_model import initialize_array" + "from power_grid_model import initialize_array, power_grid_meta_data" ] }, { @@ -122,6 +122,42 @@ "}" ] }, + { + "cell_type": "markdown", + "id": "102d862d", + "metadata": {}, + "source": [ + "It is also possible to specify a component input data in a columnar data format.\n", + "\n", + "A columnar data format better integrates with most databases. In addition, it may bring memory and, in some cases, even computational performance improvements, because unused attribute columns can be omitted.\n", + "\n", + "Make sure to provide the correct `dtype` to the numpy arrays, exposed for each dataset type, component and attribute via the `power_grid_meta_data` object." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "1034f6f3", + "metadata": {}, + "outputs": [], + "source": [ + "source_attribute_dtypes = power_grid_meta_data[DatasetType.input][ComponentType.source].dtype\n", + "source_columns = {\n", + " \"id\": np.array([10], dtype=source_attribute_dtypes[\"id\"]),\n", + " \"node\": np.array([1], dtype=source_attribute_dtypes[\"node\"]),\n", + " \"status\": np.array([1], dtype=source_attribute_dtypes[\"status\"]),\n", + " \"u_ref\": np.array([1.0], dtype=source_attribute_dtypes[\"u_ref\"]),\n", + " # We're not creating columns for u_ref_angle, sk, ... Instead, the default values are used. This saves us memory.\n", + "}\n", + "\n", + "input_data = {\n", + " ComponentType.node: node,\n", + " ComponentType.line: line,\n", + " ComponentType.sym_load: sym_load,\n", + " ComponentType.source: source_columns,\n", + "}" + ] + }, { "attachments": {}, "cell_type": "markdown", @@ -133,7 +169,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 4, "id": "f0c8c3e8", "metadata": {}, "outputs": [ @@ -174,7 +210,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 5, "id": "40509eaf", "metadata": {}, "outputs": [], @@ -207,7 +243,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 6, "id": "7ef134e9", "metadata": {}, "outputs": [], @@ -244,7 +280,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 7, "id": "44c2de63", "metadata": {}, "outputs": [], @@ -267,7 +303,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 8, "id": "a581a36e", "metadata": {}, "outputs": [ @@ -306,7 +342,7 @@ "id": "24893218", "metadata": {}, "source": [ - "### Select component types in the result dataset\n", + "### Select components / attributes and format of result dataset\n", "\n", "By default `power-grid-model` calculates the result attributes for all components. If you do not need all the component types in your results, for example if you are only interested in `node`, you can specify the list of the component types you want in the result dataset in the argument `output_component_types`.\n", "\n", @@ -315,7 +351,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 9, "id": "30fdf18a", "metadata": {}, "outputs": [ @@ -324,7 +360,7 @@ "output_type": "stream", "text": [ "List of component types in result dataset\n", - "['node']\n", + "[]\n", "------node result------\n", " id energized u_pu u u_angle p q\n", "0 1 1 0.998988 10489.375043 -0.003039 3.121451e+07 6.991358e+06\n", @@ -348,6 +384,155 @@ "print(pd.DataFrame(output_data[ComponentType.node]))" ] }, + { + "cell_type": "markdown", + "id": "d129b8de", + "metadata": {}, + "source": [ + "You can also request the output as a dictionary of attribute and their values columns for each component type." + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "95ee7a35", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "List of component types in result dataset\n", + "[]\n", + "List of attribute types in line result\n", + "['id', 'p_from']\n", + "------line result------\n", + "{'id': array([3, 5, 8], dtype=int32), 'p_from': array([17360100.20222374, -3365613.74450156, 13854413.52498137])}\n" + ] + } + ], + "source": [ + "output_data = model.calculate_power_flow(\n", + " symmetric=True,\n", + " error_tolerance=1e-8,\n", + " max_iterations=20,\n", + " calculation_method=CalculationMethod.newton_raphson,\n", + " output_component_types={\n", + " ComponentType.line: [\"id\", \"p_from\"], # line output columns id and p_from\n", + " },\n", + ")\n", + "print(\"List of component types in result dataset\")\n", + "print(list(output_data))\n", + "print(\"List of attribute types in line result\")\n", + "print(list(output_data[ComponentType.line]))\n", + "print(\"------line result------\")\n", + "print(output_data[ComponentType.line])" + ] + }, + { + "cell_type": "markdown", + "id": "e564346b", + "metadata": {}, + "source": [ + "You can also mix output types between components. In this example:\n", + "\n", + "* `None` requests row-based data\n", + "* a list of attribute names requests columns for those attributes (as before)\n", + "* `ComponentAttributeFilterOptions.everything` requests columns for all supported attributes of a component." + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "f52dbdd7", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "List of component types in result dataset and associated data types\n", + "{'ComponentType.node': , 'ComponentType.line': , 'ComponentType.sym_load': }\n", + "------node result------\n", + "('id', 'energized', 'u_pu', 'u', 'u_angle', 'p', 'q')\n", + "------line result attributes------\n", + "['id', 'p_from']\n", + "------sym_load result attributes------\n", + "['id', 'energized', 'p', 'q', 'i', 's', 'pf']\n" + ] + } + ], + "source": [ + "output_data = model.calculate_power_flow(\n", + " symmetric=True,\n", + " error_tolerance=1e-8,\n", + " max_iterations=20,\n", + " calculation_method=CalculationMethod.newton_raphson,\n", + " output_component_types={\n", + " ComponentType.node: None, # node output as row-based\n", + " ComponentType.line: [\"id\", \"p_from\"], # line output columns id and p_from\n", + " ComponentType.sym_load: ComponentAttributeFilterOptions.everything, # all sym_load attributes as columns\n", + " },\n", + ")\n", + "\n", + "print(\"List of component types in result dataset and associated data types\")\n", + "print({str(component_type): type(component_data) for component_type, component_data in output_data.items()})\n", + "print(\"------node result------\")\n", + "print(output_data[ComponentType.node].dtype.names)\n", + "print(\"------line result attributes------\")\n", + "print(list(output_data[ComponentType.line].keys()))\n", + "print(\"------sym_load result attributes------\")\n", + "print(list(output_data[ComponentType.sym_load].keys()))" + ] + }, + { + "cell_type": "markdown", + "id": "14a34c07", + "metadata": {}, + "source": [ + "Finally, it is possible to request columnar data for all attributes using the `ComponentAttributeFilterOptions.everything` as a shorthand." + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "e89882f5", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "List of component types in result dataset and associated data types\n", + "{'ComponentType.node': , 'ComponentType.line': , 'ComponentType.sym_load': , 'ComponentType.source': }\n", + "------node result------\n", + "['id', 'energized', 'u_pu', 'u', 'u_angle', 'p', 'q']\n", + "------line result attributes------\n", + "['id', 'energized', 'loading', 'p_from', 'q_from', 'i_from', 's_from', 'p_to', 'q_to', 'i_to', 's_to']\n", + "------sym_load result attributes------\n", + "['id', 'energized', 'p', 'q', 'i', 's', 'pf']\n" + ] + } + ], + "source": [ + "output_data = model.calculate_power_flow(\n", + " symmetric=True,\n", + " error_tolerance=1e-8,\n", + " max_iterations=20,\n", + " calculation_method=CalculationMethod.newton_raphson,\n", + " output_component_types=ComponentAttributeFilterOptions.everything, # all attributes for all component types as columns\n", + ")\n", + "\n", + "print(\"List of component types in result dataset and associated data types\")\n", + "print({str(component_type): type(component_data) for component_type, component_data in output_data.items()})\n", + "print(\"------node result------\")\n", + "print(list(output_data[ComponentType.node].keys()))\n", + "print(\"------line result attributes------\")\n", + "print(list(output_data[ComponentType.line].keys()))\n", + "print(\"------sym_load result attributes------\")\n", + "print(list(output_data[ComponentType.sym_load].keys()))" + ] + }, { "attachments": {}, "cell_type": "markdown", @@ -376,7 +561,7 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 13, "id": "ecbb8eeb", "metadata": {}, "outputs": [ @@ -435,7 +620,7 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 14, "id": "1d035c65", "metadata": {}, "outputs": [], @@ -453,6 +638,31 @@ "update_data = {ComponentType.sym_load: update_sym_load, ComponentType.line: update_line}" ] }, + { + "cell_type": "markdown", + "id": "fcc57354", + "metadata": {}, + "source": [ + "Columnar data is also supported" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "aaf8fe64", + "metadata": {}, + "outputs": [], + "source": [ + "line_update_dtype = power_grid_meta_data[DatasetType.update][ComponentType.line].dtype\n", + "columnar_update_line = {\n", + " \"id\": np.array([3], dtype=line_update_dtype[\"id\"]), # change line ID 3\n", + " \"from_status\": np.array([0], dtype=line_update_dtype[\"from_status\"]), # switch off at from side\n", + "}\n", + "# leave to-side swiching status the same, no need to specify\n", + "\n", + "update_data = {ComponentType.sym_load: update_sym_load, ComponentType.line: columnar_update_line}" + ] + }, { "attachments": {}, "cell_type": "markdown", @@ -466,7 +676,7 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 16, "id": "63ea4cc3", "metadata": {}, "outputs": [], @@ -489,7 +699,7 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 17, "id": "188f6663", "metadata": {}, "outputs": [], @@ -511,7 +721,7 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 18, "id": "a93c1e16", "metadata": {}, "outputs": [ @@ -600,7 +810,7 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 19, "id": "42d5cd8f", "metadata": {}, "outputs": [], @@ -634,7 +844,7 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 20, "id": "4e25006f", "metadata": { "scrolled": true @@ -677,7 +887,7 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 21, "id": "afccf7a2", "metadata": { "scrolled": true @@ -714,7 +924,7 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 22, "id": "9af1be38", "metadata": { "scrolled": true @@ -757,7 +967,7 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 23, "id": "041368dc", "metadata": {}, "outputs": [], @@ -787,7 +997,7 @@ }, { "cell_type": "code", - "execution_count": 19, + "execution_count": 24, "id": "34338ce3", "metadata": {}, "outputs": [ @@ -819,7 +1029,7 @@ }, { "cell_type": "code", - "execution_count": 20, + "execution_count": 25, "id": "04e56690", "metadata": {}, "outputs": [], @@ -851,7 +1061,7 @@ }, { "cell_type": "code", - "execution_count": 21, + "execution_count": 26, "id": "0d5b94c2", "metadata": {}, "outputs": [ @@ -934,7 +1144,7 @@ }, { "cell_type": "code", - "execution_count": 22, + "execution_count": 27, "id": "b5f10bae", "metadata": {}, "outputs": [ @@ -994,7 +1204,7 @@ }, { "cell_type": "code", - "execution_count": 23, + "execution_count": 28, "id": "1a221507", "metadata": {}, "outputs": [ @@ -1036,7 +1246,7 @@ }, { "cell_type": "code", - "execution_count": 24, + "execution_count": 29, "id": "541af620", "metadata": {}, "outputs": [ @@ -1084,7 +1294,7 @@ }, { "cell_type": "code", - "execution_count": 25, + "execution_count": 30, "id": "20d8285c", "metadata": {}, "outputs": [], @@ -1095,7 +1305,7 @@ }, { "cell_type": "code", - "execution_count": 26, + "execution_count": 31, "id": "b702eb15", "metadata": {}, "outputs": [ @@ -1130,7 +1340,7 @@ }, { "cell_type": "code", - "execution_count": 27, + "execution_count": 32, "id": "1ba71901", "metadata": {}, "outputs": [ @@ -1139,16 +1349,16 @@ "output_type": "stream", "text": [ "Node data with invalid results\n", - "[[0.99940117 0.99268579 0.99452137]\n", - " [0.99934769 0.98622639 0.98935286]\n", - " [0.99928838 0.97965401 0.98409554]\n", - " [0. 0. 0. ]\n", - " [0.99915138 0.96614948 0.97329879]\n", - " [0.99907317 0.95920586 0.96775071]\n", - " [0.9989881 0.95212621 0.96209647]\n", - " [0. 0. 0. ]\n", - " [0.99879613 0.93753005 0.95044796]\n", - " [0.9986885 0.92999747 0.94444167]]\n", + "[[9.99401170e-001 9.92685785e-001 9.94521366e-001]\n", + " [9.99347687e-001 9.86226389e-001 9.89352855e-001]\n", + " [9.99288384e-001 9.79654011e-001 9.84095542e-001]\n", + " [0.00000000e+000 3.04369633e+101 3.41345331e+241]\n", + " [9.99151380e-001 9.66149483e-001 9.73298790e-001]\n", + " [9.99073166e-001 9.59205860e-001 9.67750710e-001]\n", + " [9.98988099e-001 9.52126208e-001 9.62096474e-001]\n", + " [1.66994188e-321 3.12878333e-312 9.75772002e+199]\n", + " [9.98796126e-001 9.37530046e-001 9.50447962e-001]\n", + " [9.98688504e-001 9.29997471e-001 9.44441670e-001]]\n", "Node data with only valid results\n", "[[0.99940117 0.99268579 0.99452137]\n", " [0.99934769 0.98622639 0.98935286]\n", @@ -1165,7 +1375,6 @@ "# we run the batch calculation with continue_on_batch_error=True,\n", "# it will return the results with partially valid data\n", "\n", - "\n", "output_data = model.calculate_power_flow(update_data=time_series_mutation, continue_on_batch_error=True)\n", "\n", "# print node data for u_pu, note that the data is rubbish for scenario 3 and 7\n", @@ -1191,7 +1400,7 @@ ], "metadata": { "kernelspec": { - "display_name": "Python 3 (ipykernel)", + "display_name": "venv", "language": "python", "name": "python3" }, @@ -1205,12 +1414,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.12.2" - }, - "vscode": { - "interpreter": { - "hash": "f2534ebf7a1a13ecc51c3a04ce741b49cf1feb97b5ca55170ed7b2036b4058c0" - } + "version": "3.13.0" } }, "nbformat": 4, diff --git a/docs/examples/Serialization Example.ipynb b/docs/examples/Serialization Example.ipynb index 201324c0d..b718946da 100644 --- a/docs/examples/Serialization Example.ipynb +++ b/docs/examples/Serialization Example.ipynb @@ -30,7 +30,7 @@ " # suppress warning about pyarrow as future required dependency\n", " from pandas import DataFrame\n", "\n", - "from power_grid_model import PowerGridModel, ComponentType\n", + "from power_grid_model import PowerGridModel, ComponentType, ComponentAttributeFilterOptions\n", "from power_grid_model.utils import json_deserialize, json_serialize" ] }, @@ -42,7 +42,9 @@ "\n", "The data is in the `data/serialized-input.json` file.\n", "\n", - "### Load the JSON file" + "### Load the JSON file\n", + "\n", + "This is just for illustration purposes." ] }, { @@ -122,9 +124,19 @@ "name": "stdout", "output_type": "stream", "text": [ - "components: dict_keys(['node', 'line', 'source', 'sym_load'])\n" + "components: [, , , ]\n" ] }, + { + "data": { + "text/plain": [ + "array([(1, 10500.), (2, 10500.), (3, 10500.)],\n", + " dtype={'names': ['id', 'u_rated'], 'formats': [', , , ]\n" + ] + }, + { + "data": { + "text/plain": [ + "{'id': array([1, 2, 3], dtype=int32),\n", + " 'u_rated': array([10500., 10500., 10500.])}" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "dataset = json_deserialize(data, data_filter=ComponentAttributeFilterOptions.everything)\n", + "\n", + "print(\"components:\", list(dataset.keys()))\n", + "display(dataset[ComponentType.node])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Deserialized data format selection per component type\n", + "\n", + "To select specific components and data formats for the deserialized data, provide a dictionary of components and their desired output types to the `data_filter`." + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "components: [, , , ]\n", + "node attributes: ['id', 'u_rated']\n", + "source attributes: ['id', 'node', 'status', 'u_ref', 'sk']\n", + "sym_load attributes: ['id', 'node', 'status', 'type', 'p_specified', 'q_specified']\n", + "line attributes: ['id', 'from_node', 'to_node', 'from_status', 'to_status', 'r1', 'x1', 'c1', 'tan1', 'i_n']\n" + ] + } + ], + "source": [ + "dataset = json_deserialize(\n", + " data,\n", + " data_filter={\n", + " ComponentType.node: None, # nodes in a row-based data format\n", + " ComponentType.source: [\"id\", \"node\", \"status\", \"u_ref\", \"sk\"], # only specific attributes\n", + " ComponentType.sym_load: ComponentAttributeFilterOptions.everything, # all attributes as columns\n", + " ComponentType.line: ComponentAttributeFilterOptions.relevant, # only attributes that are not null/nan\n", + " },\n", + ")\n", + "\n", + "print(\"components:\", list(dataset.keys()))\n", + "print(\"node attributes:\", list(dataset[ComponentType.node].dtype.names))\n", + "print(\"source attributes:\", list(dataset[ComponentType.source].keys()))\n", + "print(\"sym_load attributes:\", list(dataset[ComponentType.sym_load].keys()))\n", + "print(\"line attributes:\", list(dataset[ComponentType.line].keys()))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "A columnar dataset can also be serialized again, as one would expect." + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{\n", + " \"version\": \"1.0\",\n", + " \"type\": \"sym_output\",\n", + " \"is_batch\": false,\n", + " \"attributes\": {},\n", + " \"data\": {\n", + " \"node\": [\n", + " {\"id\": 1, \"energized\": 1, \"u_pu\": 1.030000000001025, \"u\": 10815.00000001077, \"u_angle\": -2.530316910142707e-14, \"p\": 2408997.839438867, \"q\": -2863495.364674167},\n", + " {\"id\": 2, \"energized\": 1, \"u_pu\": 1.029996969815606, \"u\": 10814.96818306386, \"u_angle\": -0.004397999804754745, \"p\": -1009999.99999997, \"q\": -210000.0000000655},\n", + " {\"id\": 3, \"energized\": 1, \"u_pu\": 1.029483905569345, \"u\": 10809.58100847812, \"u_angle\": -0.006839956175380238, \"p\": -1019999.999999999, \"q\": -219999.9999999689}\n", + " ],\n", + " \"line\": [\n", + " {\"id\": 4, \"energized\": 1, \"loading\": 0.3995319091937107, \"p_from\": 2408997.839438867, \"q_from\": -2863495.364674167, \"i_from\": 199.7659545968554, \"s_from\": 3742041.7279784, \"p_to\": -2252625.764367544, \"q_to\": 1403928.536947823, \"i_to\": 141.6984332838951, \"s_to\": 2654305.591138465},\n", + " {\"id\": 5, \"energized\": 1, \"loading\": 0.1977047433812966, \"p_from\": 1242625.764367574, \"q_from\": -1613928.536947772, \"i_from\": 108.7376088597131, \"s_from\": 2036880.976553238, \"p_to\": -1019999.999999999, \"q_to\": -219999.9999999689, \"i_to\": 55.73199226981006, \"s_to\": 1043455.796859639}\n", + " ],\n", + " \"source\": [\n", + " {\"id\": 15, \"energized\": 1, \"p\": -7836685.751732428, \"q\": -105348495.343833, \"i\": 5639.485452974508, \"s\": 105639571.7275539, \"pf\": -0.07418324046166491},\n", + " {\"id\": 16, \"energized\": 1, \"p\": 10248883.05839198, \"q\": 102488830.5811954, \"i\": 5498.573991718361, \"s\": 102999999.9895415, \"pf\": 0.09950371902361781},\n", + " {\"id\": 17, \"energized\": 1, \"p\": -0.001807829591278542, \"q\": -0.0104081191056169, \"i\": 5.639485452974507e-07, \"s\": 0.01056395717275539, \"pf\": -0.1711318553942043}\n", + " ],\n", + " \"sym_load\": [\n", + " {\"id\": 7, \"energized\": 1, \"p\": 1010000, \"q\": 210000, \"i\": 55.07135393955915, \"s\": 1031600.697944704, \"pf\": 0.979060989404389},\n", + " {\"id\": 8, \"energized\": 1, \"p\": 1020000, \"q\": 220000, \"i\": 55.73199226981046, \"s\": 1043455.796859647, \"pf\": 0.9775210440823288}\n", + " ]\n", + " }\n", + "}\n" + ] + } + ], + "source": [ + "serialized_output = json_serialize(output)\n", + "\n", + "print(serialized_output)" + ] } ], "metadata": { "kernelspec": { - "display_name": "Python 3 (ipykernel)", + "display_name": "venv", "language": "python", "name": "python3" }, @@ -609,7 +758,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.12.2" + "version": "3.13.0" } }, "nbformat": 4, diff --git a/docs/examples/Short Circuit Example.ipynb b/docs/examples/Short Circuit Example.ipynb index 4fd209f1e..bf3479305 100644 --- a/docs/examples/Short Circuit Example.ipynb +++ b/docs/examples/Short Circuit Example.ipynb @@ -327,7 +327,7 @@ ], "metadata": { "kernelspec": { - "display_name": "Python 3 (ipykernel)", + "display_name": "venv", "language": "python", "name": "python3" }, @@ -341,12 +341,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.12.2" - }, - "vscode": { - "interpreter": { - "hash": "f2534ebf7a1a13ecc51c3a04ce741b49cf1feb97b5ca55170ed7b2036b4058c0" - } + "version": "3.13.0" } }, "nbformat": 4, diff --git a/docs/examples/State Estimation Example.ipynb b/docs/examples/State Estimation Example.ipynb index 8bec41a6e..5066ac316 100644 --- a/docs/examples/State Estimation Example.ipynb +++ b/docs/examples/State Estimation Example.ipynb @@ -1089,7 +1089,7 @@ ], "metadata": { "kernelspec": { - "display_name": "Python 3 (ipykernel)", + "display_name": "venv", "language": "python", "name": "python3" }, @@ -1103,7 +1103,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.12.2" + "version": "3.13.0" } }, "nbformat": 4, diff --git a/docs/examples/Transformer Examples.ipynb b/docs/examples/Transformer Examples.ipynb index 147dd1e38..e949cc0da 100644 --- a/docs/examples/Transformer Examples.ipynb +++ b/docs/examples/Transformer Examples.ipynb @@ -1722,7 +1722,7 @@ ], "metadata": { "kernelspec": { - "display_name": "Python 3 (ipykernel)", + "display_name": "venv", "language": "python", "name": "python3" }, @@ -1736,12 +1736,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.14" - }, - "vscode": { - "interpreter": { - "hash": "f2534ebf7a1a13ecc51c3a04ce741b49cf1feb97b5ca55170ed7b2036b4058c0" - } + "version": "3.13.0" } }, "nbformat": 4, diff --git a/docs/examples/Validation Examples.ipynb b/docs/examples/Validation Examples.ipynb index c041efeb7..6b09acf8c 100644 --- a/docs/examples/Validation Examples.ipynb +++ b/docs/examples/Validation Examples.ipynb @@ -113,8 +113,8 @@ "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[1;31mIDWrongType\u001b[0m Traceback (most recent call last)", "Cell \u001b[1;32mIn[2], line 2\u001b[0m\n\u001b[0;32m 1\u001b[0m \u001b[38;5;66;03m# Without validation\u001b[39;00m\n\u001b[1;32m----> 2\u001b[0m model \u001b[38;5;241m=\u001b[39m \u001b[43mPowerGridModel\u001b[49m\u001b[43m(\u001b[49m\u001b[43merror_data\u001b[49m\u001b[43m)\u001b[49m\n\u001b[0;32m 3\u001b[0m output_data \u001b[38;5;241m=\u001b[39m model\u001b[38;5;241m.\u001b[39mcalculate_state_estimation(symmetric\u001b[38;5;241m=\u001b[39m\u001b[38;5;28;01mTrue\u001b[39;00m)\n", - "File \u001b[1;32m~\\Documents\\power-grid-model\\src\\power_grid_model\\core\\power_grid_model.py:114\u001b[0m, in \u001b[0;36mPowerGridModel.__init__\u001b[1;34m(self, input_data, system_frequency)\u001b[0m\n\u001b[0;32m 112\u001b[0m prepared_input \u001b[38;5;241m=\u001b[39m prepare_input_view(input_data)\n\u001b[0;32m 113\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_model_ptr \u001b[38;5;241m=\u001b[39m pgc\u001b[38;5;241m.\u001b[39mcreate_model(system_frequency, input_data\u001b[38;5;241m=\u001b[39mprepared_input\u001b[38;5;241m.\u001b[39mget_dataset_ptr())\n\u001b[1;32m--> 114\u001b[0m \u001b[43massert_no_error\u001b[49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\n\u001b[0;32m 115\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_all_component_count \u001b[38;5;241m=\u001b[39m {k: v \u001b[38;5;28;01mfor\u001b[39;00m k, v \u001b[38;5;129;01min\u001b[39;00m prepared_input\u001b[38;5;241m.\u001b[39mget_info()\u001b[38;5;241m.\u001b[39mtotal_elements()\u001b[38;5;241m.\u001b[39mitems() \u001b[38;5;28;01mif\u001b[39;00m v \u001b[38;5;241m>\u001b[39m \u001b[38;5;241m0\u001b[39m}\n", - "File \u001b[1;32m~\\Documents\\power-grid-model\\src\\power_grid_model\\core\\error_handling.py:164\u001b[0m, in \u001b[0;36massert_no_error\u001b[1;34m(batch_size, decode_error)\u001b[0m\n\u001b[0;32m 162\u001b[0m error \u001b[38;5;241m=\u001b[39m find_error(batch_size\u001b[38;5;241m=\u001b[39mbatch_size, decode_error\u001b[38;5;241m=\u001b[39mdecode_error)\n\u001b[0;32m 163\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m error \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m:\n\u001b[1;32m--> 164\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m error\n", + "File \u001b[1;32m~\\Documents\\power-grid-model\\src\\power_grid_model\\core\\power_grid_model.py:125\u001b[0m, in \u001b[0;36mPowerGridModel.__init__\u001b[1;34m(self, input_data, system_frequency)\u001b[0m\n\u001b[0;32m 123\u001b[0m prepared_input \u001b[38;5;241m=\u001b[39m prepare_input_view(_map_to_component_types(input_data))\n\u001b[0;32m 124\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_model_ptr \u001b[38;5;241m=\u001b[39m pgc\u001b[38;5;241m.\u001b[39mcreate_model(system_frequency, input_data\u001b[38;5;241m=\u001b[39mprepared_input\u001b[38;5;241m.\u001b[39mget_dataset_ptr())\n\u001b[1;32m--> 125\u001b[0m \u001b[43massert_no_error\u001b[49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\n\u001b[0;32m 126\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_all_component_count \u001b[38;5;241m=\u001b[39m {k: v \u001b[38;5;28;01mfor\u001b[39;00m k, v \u001b[38;5;129;01min\u001b[39;00m prepared_input\u001b[38;5;241m.\u001b[39mget_info()\u001b[38;5;241m.\u001b[39mtotal_elements()\u001b[38;5;241m.\u001b[39mitems() \u001b[38;5;28;01mif\u001b[39;00m v \u001b[38;5;241m>\u001b[39m \u001b[38;5;241m0\u001b[39m}\n", + "File \u001b[1;32m~\\Documents\\power-grid-model\\src\\power_grid_model\\core\\error_handling.py:169\u001b[0m, in \u001b[0;36massert_no_error\u001b[1;34m(batch_size, decode_error)\u001b[0m\n\u001b[0;32m 167\u001b[0m error \u001b[38;5;241m=\u001b[39m find_error(batch_size\u001b[38;5;241m=\u001b[39mbatch_size, decode_error\u001b[38;5;241m=\u001b[39mdecode_error)\n\u001b[0;32m 168\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m error \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m:\n\u001b[1;32m--> 169\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m error\n", "\u001b[1;31mIDWrongType\u001b[0m: Wrong type for object with id 4\n\nTry validate_input_data() or validate_batch_data() to validate your data.\n" ] } @@ -133,14 +133,14 @@ "outputs": [ { "ename": "ValidationException", - "evalue": "There are 5 validation errors in input_data:\n 1. Fields line.id and sym_power_sensor.id are not unique for 2 lines/sym_power_sensors.\n 2. Field 'to_node' does not contain a valid node id for 1 line.\n 3. Field 'power_sigma' is not greater than zero for 2 sym_power_sensors.\n 4. Field 'measured_object' does not contain a valid line/transformer id for 1 sym_power_sensor. (measured_terminal_type=branch_from)\n 5. Field 'measured_object' does not contain a valid source id for 1 sym_power_sensor. (measured_terminal_type=source)", + "evalue": "There are 5 validation errors in input_data:\n 1. Fields line.id and sym_power_sensor.id are not unique for 2 lines/sym_power_sensors.\n 2. Field 'to_node' does not contain a valid node id for 1 line.\n 3. Field 'power_sigma' is not greater than zero for 2 sym_power_sensors.\n 4. Field 'measured_object' does not contain a valid line/generic_branch/transformer id for 1 sym_power_sensor. (measured_terminal_type=branch_from)\n 5. Field 'measured_object' does not contain a valid source id for 1 sym_power_sensor. (measured_terminal_type=source)", "output_type": "error", "traceback": [ "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[1;31mValidationException\u001b[0m Traceback (most recent call last)", "Cell \u001b[1;32mIn[3], line 4\u001b[0m\n\u001b[0;32m 1\u001b[0m \u001b[38;5;28;01mfrom\u001b[39;00m \u001b[38;5;21;01mpower_grid_model\u001b[39;00m\u001b[38;5;21;01m.\u001b[39;00m\u001b[38;5;21;01mvalidation\u001b[39;00m \u001b[38;5;28;01mimport\u001b[39;00m assert_valid_input_data\n\u001b[0;32m 3\u001b[0m \u001b[38;5;66;03m# Assert valid data\u001b[39;00m\n\u001b[1;32m----> 4\u001b[0m \u001b[43massert_valid_input_data\u001b[49m\u001b[43m(\u001b[49m\u001b[43merror_data\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43msymmetric\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;28;43;01mTrue\u001b[39;49;00m\u001b[43m)\u001b[49m\n\u001b[0;32m 5\u001b[0m model \u001b[38;5;241m=\u001b[39m PowerGridModel(error_data)\n\u001b[0;32m 6\u001b[0m output_data \u001b[38;5;241m=\u001b[39m model\u001b[38;5;241m.\u001b[39mcalculate_state_estimation(symmetric\u001b[38;5;241m=\u001b[39m\u001b[38;5;28;01mTrue\u001b[39;00m)\n", "File \u001b[1;32m~\\Documents\\power-grid-model\\src\\power_grid_model\\validation\\assertions.py:57\u001b[0m, in \u001b[0;36massert_valid_input_data\u001b[1;34m(input_data, calculation_type, symmetric)\u001b[0m\n\u001b[0;32m 53\u001b[0m validation_errors \u001b[38;5;241m=\u001b[39m validate_input_data(\n\u001b[0;32m 54\u001b[0m input_data\u001b[38;5;241m=\u001b[39minput_data, calculation_type\u001b[38;5;241m=\u001b[39mcalculation_type, symmetric\u001b[38;5;241m=\u001b[39msymmetric\n\u001b[0;32m 55\u001b[0m )\n\u001b[0;32m 56\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m validation_errors:\n\u001b[1;32m---> 57\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m ValidationException(validation_errors, \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124minput_data\u001b[39m\u001b[38;5;124m\"\u001b[39m)\n", - "\u001b[1;31mValidationException\u001b[0m: There are 5 validation errors in input_data:\n 1. Fields line.id and sym_power_sensor.id are not unique for 2 lines/sym_power_sensors.\n 2. Field 'to_node' does not contain a valid node id for 1 line.\n 3. Field 'power_sigma' is not greater than zero for 2 sym_power_sensors.\n 4. Field 'measured_object' does not contain a valid line/transformer id for 1 sym_power_sensor. (measured_terminal_type=branch_from)\n 5. Field 'measured_object' does not contain a valid source id for 1 sym_power_sensor. (measured_terminal_type=source)" + "\u001b[1;31mValidationException\u001b[0m: There are 5 validation errors in input_data:\n 1. Fields line.id and sym_power_sensor.id are not unique for 2 lines/sym_power_sensors.\n 2. Field 'to_node' does not contain a valid node id for 1 line.\n 3. Field 'power_sigma' is not greater than zero for 2 sym_power_sensors.\n 4. Field 'measured_object' does not contain a valid line/generic_branch/transformer id for 1 sym_power_sensor. (measured_terminal_type=branch_from)\n 5. Field 'measured_object' does not contain a valid source id for 1 sym_power_sensor. (measured_terminal_type=source)" ] } ], @@ -163,11 +163,11 @@ "name": "stdout", "output_type": "stream", "text": [ - "MultiComponentNotUniqueError ['line', 'sym_power_sensor'] : [('line', 6), ('sym_power_sensor', 6)]\n", - "InvalidIdError line : [6]\n", - "NotGreaterThanError sym_power_sensor : [6, 7]\n", - "InvalidIdError sym_power_sensor : [6]\n", - "InvalidIdError sym_power_sensor : [7]\n" + "MultiComponentNotUniqueError [, ] : [(, np.int32(6)), (, np.int32(6))]\n", + "InvalidIdError ComponentType.line : [6]\n", + "NotGreaterThanError ComponentType.sym_power_sensor : [6, 7]\n", + "InvalidIdError ComponentType.sym_power_sensor : [6]\n", + "InvalidIdError ComponentType.sym_power_sensor : [7]\n" ] } ], @@ -198,7 +198,7 @@ " 1. Fields line.id and sym_power_sensor.id are not unique for 2 lines/sym_power_sensors.\n", " 2. Field 'to_node' does not contain a valid node id for 1 line.\n", " 3. Field 'power_sigma' is not greater than zero for 2 sym_power_sensors.\n", - " 4. Field 'measured_object' does not contain a valid line/transformer id for 1 sym_power_sensor. (measured_terminal_type=branch_from)\n", + " 4. Field 'measured_object' does not contain a valid line/generic_branch/transformer id for 1 sym_power_sensor. (measured_terminal_type=branch_from)\n", " 5. Field 'measured_object' does not contain a valid source id for 1 sym_power_sensor. (measured_terminal_type=source)\n" ] } @@ -230,7 +230,7 @@ "\tFields line.id and sym_power_sensor.id are not unique for 2 lines/sym_power_sensors.\n", "\t\tcomponent: line/sym_power_sensor\n", "\t\tfield: line.id and sym_power_sensor.id\n", - "\t\tids: [('line', 6), ('sym_power_sensor', 6)]\n", + "\t\tids: [(, np.int32(6)), (, np.int32(6))]\n", "\n", "\tField 'to_node' does not contain a valid node id for 1 line.\n", "\t\tcomponent: line\n", @@ -245,11 +245,11 @@ "\t\tids: [6, 7]\n", "\t\tref_value: zero\n", "\n", - "\tField 'measured_object' does not contain a valid line/transformer id for 1 sym_power_sensor. (measured_terminal_type=branch_from)\n", + "\tField 'measured_object' does not contain a valid line/generic_branch/transformer id for 1 sym_power_sensor. (measured_terminal_type=branch_from)\n", "\t\tcomponent: sym_power_sensor\n", "\t\tfield: 'measured_object'\n", "\t\tids: [6]\n", - "\t\tref_components: line/transformer\n", + "\t\tref_components: line/generic_branch/transformer\n", "\t\tfilters: (measured_terminal_type=branch_from)\n", "\n", "\tField 'measured_object' does not contain a valid source id for 1 sym_power_sensor. (measured_terminal_type=source)\n", @@ -271,7 +271,7 @@ ], "metadata": { "kernelspec": { - "display_name": "Python 3 (ipykernel)", + "display_name": "venv", "language": "python", "name": "python3" }, @@ -285,12 +285,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.12.2" - }, - "vscode": { - "interpreter": { - "hash": "39f035c5e58f53e973c1999fdd4393d6db2dd3f04fe05f524988f90f4bbdb576" - } + "version": "3.13.0" } }, "nbformat": 4, diff --git a/src/power_grid_model/validation/errors.py b/src/power_grid_model/validation/errors.py index a956810a5..58bc84649 100644 --- a/src/power_grid_model/validation/errors.py +++ b/src/power_grid_model/validation/errors.py @@ -58,14 +58,26 @@ def component_str(self) -> str: """ A string representation of the component to which this error applies """ - return str(self.component) + if self.component is None: + return str(None) + if isinstance(self.component, list): + return "/".join(component.value for component in self.component) + return self.component.value @property def field_str(self) -> str: """ A string representation of the field to which this error applies """ - return f"'{self.field}'" + + def _unpack(field: str | tuple[ComponentType, str]) -> str: + if isinstance(field, str): + return f"'{field}'" + return ".".join(field) + + if isinstance(self.field, list): + return self._delimiter.join(_unpack(field) for field in self.field) + return _unpack(self.field) if self.field else str(self.field) def get_context(self, id_lookup: Optional[list[str] | dict[int, str]] = None) -> dict[str, Any]: """ @@ -155,10 +167,6 @@ def __init__(self, component: ComponentType, fields: list[str], ids: list[int]): if len(self.field) < 2: raise ValueError(f"{type(self).__name__} expects at least two fields: {self.field}") - @property - def field_str(self) -> str: - return self._delimiter.join(f"'{field}'" for field in self.field) - class MultiComponentValidationError(ValidationError): """ @@ -187,15 +195,6 @@ def __init__(self, fields: list[tuple[ComponentType, str]], ids: list[tuple[Comp if len(self.component) < 2: raise ValueError(f"{type(self).__name__} expects at least two components: {self.component}") - @property - def component_str(self) -> str: - str_components = [str(component) for component in self.component] - return "/".join(str_components) - - @property - def field_str(self) -> str: - return self._delimiter.join(f"{component}.{field}" for component, field in self.field) - class NotIdenticalError(SingleFieldValidationError): """ diff --git a/tests/unit/validation/test_errors.py b/tests/unit/validation/test_errors.py index 5c0d87968..bb76d64bc 100644 --- a/tests/unit/validation/test_errors.py +++ b/tests/unit/validation/test_errors.py @@ -6,6 +6,7 @@ import pytest +from power_grid_model import ComponentType from power_grid_model.validation.errors import ( ComparisonError, InvalidAssociatedEnumValueError, @@ -21,65 +22,91 @@ def test_validation_error(): error = ValidationError() assert str(error) == "An unknown validation error occurred." + assert error.component_str == str(None) + assert error.field_str == str(None) + assert not error.ids def test_single_field_validation_error(): - error = SingleFieldValidationError(component="alpha", field="bravo", ids=[0, 1, 1, 2, 3, 5]) - assert error.component_str == "alpha" + error = SingleFieldValidationError(component=ComponentType.node, field="bravo", ids=[0, 1, 1, 2, 3, 5]) + assert error.component_str == "node" assert error.field_str == "'bravo'" - assert str(error) == "Field 'bravo' is not valid for 6 alphas." + assert str(error) == "Field 'bravo' is not valid for 6 nodes." def test_multi_field_validation_error(): - error = MultiFieldValidationError(component="charlie", fields=["delta", "echo"], ids=[0, 1, 1, 2, 3, 5]) - assert error.component_str == "charlie" + error = MultiFieldValidationError(component=ComponentType.node, fields=["delta", "echo"], ids=[0, 1, 1, 2, 3, 5]) + assert error.component_str == "node" assert error.field_str == "'delta' and 'echo'" - assert str(error) == "Combination of fields 'delta' and 'echo' is not valid for 6 charlies." + assert str(error) == "Combination of fields 'delta' and 'echo' is not valid for 6 nodes." with pytest.raises(ValueError, match="at least two fields"): - MultiFieldValidationError(component="charlie", fields=["delta"], ids=[]) + MultiFieldValidationError(component=ComponentType.node, fields=["delta"], ids=[]) def test_multi_component_validation_error(): error = MultiComponentValidationError( - fields=[("foxtrot", "golf"), ("hotel", "india")], - ids=[("foxtrot", 0), ("foxtrot", 1), ("foxtrot", 1), ("hotel", 2), ("hotel", 3), ("hotel", 5)], + fields=[(ComponentType.node, "golf"), (ComponentType.line, "india")], + ids=[ + (ComponentType.node, 0), + (ComponentType.node, 1), + (ComponentType.node, 1), + (ComponentType.line, 2), + (ComponentType.line, 3), + (ComponentType.line, 5), + ], ) - assert error.component_str == "foxtrot/hotel" - assert error.field_str == "foxtrot.golf and hotel.india" - assert str(error) == "Fields foxtrot.golf and hotel.india are not valid for 6 foxtrots/hotels." + assert error.component_str == "line/node" + assert error.field_str == "line.india and node.golf" + assert str(error) == "Fields line.india and node.golf are not valid for 6 lines/nodes." with pytest.raises(ValueError, match="at least two fields"): - MultiComponentValidationError(fields=[("foxtrot", "golf")], ids=[]) + MultiComponentValidationError(fields=[(ComponentType.node, "golf")], ids=[]) with pytest.raises(ValueError, match="at least two components"): - MultiComponentValidationError(fields=[("foxtrot", "golf"), ("foxtrot", "india")], ids=[]) + MultiComponentValidationError(fields=[(ComponentType.node, "golf"), (ComponentType.node, "india")], ids=[]) def test_invalid_enum_value_error(): class CustomType(IntEnum): pass - error = InvalidEnumValueError(component="kilo", field="lima", ids=[1, 2, 3], enum=CustomType) - assert error.component == "kilo" + error = InvalidEnumValueError(component=ComponentType.node, field="lima", ids=[1, 2, 3], enum=CustomType) + assert error.component == "node" assert error.field == "lima" assert error.ids == [1, 2, 3] assert error.enum is CustomType - assert str(error) == "Field 'lima' contains invalid CustomType values for 3 kilos." + assert str(error) == "Field 'lima' contains invalid CustomType values for 3 nodes." def test_invalid_id_error(): - error = InvalidIdError(component="mike", field="november", ids=[1, 2, 3], ref_components=["oscar", "papa"]) - assert error.component == "mike" + error = InvalidIdError(ComponentType.node, field="november", ids=[1, 2, 3], ref_components=["oscar", "papa"]) + assert error.component == "node" assert error.field == "november" assert error.ids == [1, 2, 3] assert error.ref_components == ["oscar", "papa"] - assert str(error) == "Field 'november' does not contain a valid oscar/papa id for 3 mikes." + assert str(error) == "Field 'november' does not contain a valid oscar/papa id for 3 nodes." + + +def test_invalid_id_error_with_filters(): + error = InvalidIdError( + ComponentType.node, + field="november", + ids=[1, 2, 3], + ref_components=["oscar", "papa"], + filters={"foo": "bar", "baz": ComponentType.node}, + ) + assert error.component == "node" + assert error.field == "november" + assert error.ids == [1, 2, 3] + assert error.ref_components == ["oscar", "papa"] + assert error.filters_str == "(foo=bar, baz=node)" + assert str(error) == "Field 'november' does not contain a valid oscar/papa id for 3 nodes. (foo=bar, baz=node)" def test_comparison_error(): - error = ComparisonError(component="quebec", field="romeo", ids=[1, 2, 3], ref_value=0) - assert error.component == "quebec" + error = ComparisonError(component=ComponentType.node, field="romeo", ids=[1, 2, 3], ref_value=0) + assert error.component == "node" assert error.field == "romeo" assert error.ids == [1, 2, 3] assert error.ref_value == 0 @@ -93,10 +120,10 @@ def test_comparison_error(): def test_error_context(): - error = ComparisonError(component="sierra", field="tango", ids=[1, 2, 3], ref_value=0) + error = ComparisonError(component=ComponentType.node, field="tango", ids=[1, 2, 3], ref_value=0) context = error.get_context() assert len(context) == 4 - assert context["component"] == "sierra" + assert context["component"] == "node" assert context["field"] == "'tango'" assert context["ids"] == [1, 2, 3] assert context["ref_value"] == "zero" @@ -109,20 +136,24 @@ def test_error_context(): def test_error_context_tuple_ids(): - error = MultiComponentValidationError(fields=[("a", "x"), ("b", "y")], ids=[("a", 1), ("b", 2), ("a", 3)]) + error = MultiComponentValidationError( + fields=[(ComponentType.node, "x"), ("b", "y")], ids=[(ComponentType.node, 1), ("b", 2), (ComponentType.node, 3)] + ) context = error.get_context(id_lookup={1: "Victor", 3: "Whiskey"}) - assert context["ids"] == {("a", 1): "Victor", ("b", 2): None, ("a", 3): "Whiskey"} + assert context["ids"] == {("node", 1): "Victor", ("b", 2): None, ("node", 3): "Whiskey"} def test_invalid_associated_enum_value_error(): class CustomType(IntEnum): pass - error = InvalidAssociatedEnumValueError(component="foo", fields=["bar", "baz"], ids=[1, 2], enum=[CustomType]) - assert error.component == "foo" + error = InvalidAssociatedEnumValueError( + component=ComponentType.node, fields=["bar", "baz"], ids=[1, 2], enum=[CustomType] + ) + assert error.component == "node" assert error.field == ["bar", "baz"] assert error.ids == [1, 2] assert len(error.enum) == 1 for actual, expected in zip(error.enum, [CustomType]): assert actual is expected - assert str(error) == "The combination of fields 'bar' and 'baz' results in invalid CustomType values for 2 foos." + assert str(error) == "The combination of fields 'bar' and 'baz' results in invalid CustomType values for 2 nodes." diff --git a/tests/unit/validation/test_rules.py b/tests/unit/validation/test_rules.py index 6f258826e..ff22676e0 100644 --- a/tests/unit/validation/test_rules.py +++ b/tests/unit/validation/test_rules.py @@ -7,7 +7,7 @@ import numpy as np import pytest -from power_grid_model import LoadGenType, initialize_array, power_grid_meta_data +from power_grid_model import ComponentType, LoadGenType, initialize_array, power_grid_meta_data from power_grid_model.enum import Branch3Side, BranchSide, FaultPhase, FaultType from power_grid_model.validation.errors import ( ComparisonError, @@ -304,14 +304,16 @@ def test_all_cross_unique(cross_only): "node": np.array([(1, 0.1), (2, 0.2), (3, 0.3)], dtype=[("id", "i4"), ("foo", "f8")]), "line": np.array([(4, 0.4), (5, 0.5), (6, 0.6), (7, 0.7)], dtype=[("id", "i4"), ("bar", "f8")]), } - errors = all_cross_unique(valid, [("node", "foo"), ("line", "bar")], cross_only=cross_only) + errors = all_cross_unique(valid, [(ComponentType.node, "foo"), (ComponentType.line, "bar")], cross_only=cross_only) assert not errors invalid = { "node": np.array([(1, 0.1), (2, 0.2), (3, 0.2)], dtype=[("id", "i4"), ("foo", "f8")]), "line": np.array([(4, 0.2), (5, 0.1), (6, 0.3), (7, 0.3)], dtype=[("id", "i4"), ("bar", "f8")]), } - errors = all_cross_unique(invalid, [("node", "foo"), ("line", "bar")], cross_only=cross_only) + errors = all_cross_unique( + invalid, [(ComponentType.node, "foo"), (ComponentType.line, "bar")], cross_only=cross_only + ) assert len(errors) == 1 error = errors.pop() assert isinstance(error, MultiComponentNotUniqueError)