|
| 1 | +# SPDX-FileCopyrightText: Contributors to the Power Grid Model project <[email protected]> |
| 2 | +# |
| 3 | +# SPDX-License-Identifier: MPL-2.0 |
| 4 | + |
| 5 | +""" |
| 6 | +Error handling |
| 7 | +""" |
| 8 | + |
| 9 | +import re |
| 10 | + |
| 11 | +from power_grid_model.errors import ( |
| 12 | + AutomaticTapCalculationError, |
| 13 | + ConflictID, |
| 14 | + ConflictVoltage, |
| 15 | + IDNotFound, |
| 16 | + IDWrongType, |
| 17 | + InvalidArguments, |
| 18 | + InvalidBranch, |
| 19 | + InvalidBranch3, |
| 20 | + InvalidCalculationMethod, |
| 21 | + InvalidMeasuredObject, |
| 22 | + InvalidRegulatedObject, |
| 23 | + InvalidShortCircuitPhaseOrType, |
| 24 | + InvalidTransformerClock, |
| 25 | + IterationDiverge, |
| 26 | + MaxIterationReached, |
| 27 | + MissingCaseForEnumError, |
| 28 | + NotObservableError, |
| 29 | + PowerGridBatchError, |
| 30 | + PowerGridDatasetError, |
| 31 | + PowerGridError, |
| 32 | + PowerGridNotImplementedError, |
| 33 | + PowerGridSerializationError, |
| 34 | + PowerGridUnreachableHitError, |
| 35 | + SparseMatrixError, |
| 36 | +) |
| 37 | + |
| 38 | +from power_grid_model_io_native._core.power_grid_model_io_core import pgm_io_core as pgmic |
| 39 | + |
| 40 | +VALIDATOR_MSG = "\nTry validate_input_data() or validate_batch_data() to validate your data.\n" |
| 41 | +# error codes |
| 42 | +PGM_NO_ERROR = 0 |
| 43 | +PGM_REGULAR_ERROR = 1 |
| 44 | +PGM_BATCH_ERROR = 2 |
| 45 | +PGM_SERIALIZATION_ERROR = 3 |
| 46 | + |
| 47 | +_MISSING_CASE_FOR_ENUM_RE = re.compile(r" is not implemented for (.+) #(-?\d+)!\n") |
| 48 | +_INVALID_ARGUMENTS_RE = re.compile(r" is not implemented for ") # multiple different flavors |
| 49 | +_CONFLICT_VOLTAGE_RE = re.compile( |
| 50 | + r"Conflicting voltage for line (-?\d+)\n voltage at from node (-?\d+) is (.*)\n" |
| 51 | + r" voltage at to node (-?\d+) is (.*)\n" |
| 52 | +) |
| 53 | +_INVALID_BRANCH_RE = re.compile(r"Branch (-?\d+) has the same from- and to-node (-?\d+),\n This is not allowed!\n") |
| 54 | +_INVALID_BRANCH3_RE = re.compile( |
| 55 | + r"Branch3 (-?\d+) is connected to the same node at least twice. Node 1\/2\/3: (-?\d+)\/(-?\d+)\/(-?\d+),\n" |
| 56 | + r" This is not allowed!\n" |
| 57 | +) |
| 58 | +_INVALID_TRANSFORMER_CLOCK_RE = re.compile(r"Invalid clock for transformer (-?\d+), clock (-?\d+)\n") |
| 59 | +_SPARSE_MATRIX_ERROR_RE = re.compile(r"Sparse matrix error") # multiple different flavors |
| 60 | +_NOT_OBSERVABLE_ERROR_RE = re.compile(r"Not enough measurements available for state estimation.\n") |
| 61 | +_ITERATION_DIVERGE_RE = re.compile(r"Iteration failed to converge") # potentially multiple different flavors |
| 62 | +_MAX_ITERATION_REACHED_RE = re.compile(r"Maximum number of iterations reached") |
| 63 | +_CONFLICT_ID_RE = re.compile(r"Conflicting id detected: (-?\d+)\n") |
| 64 | +_ID_NOT_FOUND_RE = re.compile(r"The id cannot be found: (-?\d+)\n") |
| 65 | +_INVALID_MEASURED_OBJECT_RE = re.compile(r"(\w+) measurement is not supported for object of type (\w+)") |
| 66 | +_INVALID_REGULATED_OBJECT_RE = re.compile( |
| 67 | + r"(\w+) regulator is not supported for object " |
| 68 | +) # potentially multiple different flavors |
| 69 | +_AUTOMATIC_TAP_CALCULATION_ERROR_RE = re.compile( |
| 70 | + r"Automatic tap changing regulator with tap_side at LV side is not supported. Found at id (-?\d+)\n" |
| 71 | +) |
| 72 | +_ID_WRONG_TYPE_RE = re.compile(r"Wrong type for object with id (-?\d+)\n") |
| 73 | +_INVALID_CALCULATION_METHOD_RE = re.compile(r"The calculation method is invalid for this calculation!") |
| 74 | +_INVALID_SHORT_CIRCUIT_PHASE_OR_TYPE_RE = re.compile(r"short circuit type") # multiple different flavors |
| 75 | +_POWER_GRID_DATASET_ERROR_RE = re.compile(r"Dataset error: ") # multiple different flavors |
| 76 | +_POWER_GRID_UNREACHABLE_HIT_RE = re.compile(r"Unreachable code hit when executing ") # multiple different flavors |
| 77 | +_POWER_GRID_SEARCH_OPT_INCMPT_RE = re.compile(r"Search method is incompatible with optimization strategy: ") |
| 78 | +_POWER_GRID_NOT_IMPLEMENTED_ERROR_RE = re.compile(r"The functionality is either not supported or not yet implemented!") |
| 79 | + |
| 80 | +_ERROR_MESSAGE_PATTERNS = { |
| 81 | + _MISSING_CASE_FOR_ENUM_RE: MissingCaseForEnumError, |
| 82 | + _INVALID_ARGUMENTS_RE: InvalidArguments, |
| 83 | + _CONFLICT_VOLTAGE_RE: ConflictVoltage, |
| 84 | + _INVALID_BRANCH_RE: InvalidBranch, |
| 85 | + _INVALID_BRANCH3_RE: InvalidBranch3, |
| 86 | + _INVALID_TRANSFORMER_CLOCK_RE: InvalidTransformerClock, |
| 87 | + _SPARSE_MATRIX_ERROR_RE: SparseMatrixError, |
| 88 | + _NOT_OBSERVABLE_ERROR_RE: NotObservableError, |
| 89 | + _ITERATION_DIVERGE_RE: IterationDiverge, |
| 90 | + _MAX_ITERATION_REACHED_RE: MaxIterationReached, |
| 91 | + _CONFLICT_ID_RE: ConflictID, |
| 92 | + _ID_NOT_FOUND_RE: IDNotFound, |
| 93 | + _INVALID_MEASURED_OBJECT_RE: InvalidMeasuredObject, |
| 94 | + _INVALID_REGULATED_OBJECT_RE: InvalidRegulatedObject, |
| 95 | + _AUTOMATIC_TAP_CALCULATION_ERROR_RE: AutomaticTapCalculationError, |
| 96 | + _ID_WRONG_TYPE_RE: IDWrongType, |
| 97 | + _INVALID_CALCULATION_METHOD_RE: InvalidCalculationMethod, |
| 98 | + _INVALID_SHORT_CIRCUIT_PHASE_OR_TYPE_RE: InvalidShortCircuitPhaseOrType, |
| 99 | + _POWER_GRID_DATASET_ERROR_RE: PowerGridDatasetError, |
| 100 | + _POWER_GRID_UNREACHABLE_HIT_RE: PowerGridUnreachableHitError, |
| 101 | + _POWER_GRID_SEARCH_OPT_INCMPT_RE: PowerGridUnreachableHitError, |
| 102 | + _POWER_GRID_NOT_IMPLEMENTED_ERROR_RE: PowerGridNotImplementedError, |
| 103 | +} |
| 104 | + |
| 105 | + |
| 106 | +def _interpret_error(message: str, decode_error: bool = True) -> PowerGridError: |
| 107 | + if decode_error: |
| 108 | + for pattern, type_ in _ERROR_MESSAGE_PATTERNS.items(): |
| 109 | + if pattern.search(message) is not None: |
| 110 | + return type_(message) |
| 111 | + |
| 112 | + return PowerGridError(message) |
| 113 | + |
| 114 | + |
| 115 | +def find_error(batch_size: int = 1, decode_error: bool = True) -> RuntimeError | None: |
| 116 | + """ |
| 117 | + Check if there is an error and return it |
| 118 | +
|
| 119 | + Args: |
| 120 | + batch_size: (int, optional): Size of batch. Defaults to 1. |
| 121 | + decode_error (bool, optional): Decode the error message(s) to derived error classes. Defaults to True |
| 122 | +
|
| 123 | + Returns: error object, can be none |
| 124 | +
|
| 125 | + """ |
| 126 | + error_code: int = pgmic.error_code() |
| 127 | + if error_code == PGM_NO_ERROR: |
| 128 | + return None |
| 129 | + if error_code == PGM_REGULAR_ERROR: |
| 130 | + error_message = pgmic.error_message() |
| 131 | + error_message += VALIDATOR_MSG |
| 132 | + return _interpret_error(error_message, decode_error=decode_error) |
| 133 | + if error_code == PGM_BATCH_ERROR: |
| 134 | + _ = batch_size |
| 135 | + error_message = "There are errors in the batch calculation." + VALIDATOR_MSG |
| 136 | + error = PowerGridBatchError(error_message) |
| 137 | + return error |
| 138 | + if error_code == PGM_SERIALIZATION_ERROR: |
| 139 | + return PowerGridSerializationError(pgmic.error_message()) |
| 140 | + return RuntimeError("Unknown error!") |
| 141 | + |
| 142 | + |
| 143 | +def assert_no_error(batch_size: int = 1, decode_error: bool = True): |
| 144 | + """ |
| 145 | + Assert there is no error in the last operation |
| 146 | + If there is an error, raise it |
| 147 | +
|
| 148 | + Args: |
| 149 | + batch_size (int, optional): Size of batch. Defaults to 1. |
| 150 | + decode_error (bool, optional): Decode the error message(s) to derived error classes. Defaults to True |
| 151 | +
|
| 152 | + Returns: |
| 153 | +
|
| 154 | + """ |
| 155 | + error = find_error(batch_size=batch_size, decode_error=decode_error) |
| 156 | + if error is not None: |
| 157 | + raise error |
| 158 | + |
| 159 | + |
| 160 | +def handle_errors( |
| 161 | + continue_on_batch_error: bool, batch_size: int = 1, decode_error: bool = True |
| 162 | +) -> PowerGridBatchError | None: |
| 163 | + """ |
| 164 | + Handle any errors in the way that is specified. |
| 165 | +
|
| 166 | + Args: |
| 167 | + continue_on_batch_error (bool): Return the error when the error type is a batch error instead of reraising it. |
| 168 | + batch_size (int, optional): Size of batch. Defaults to 1. |
| 169 | + decode_error (bool, optional): Decode the error message(s) to derived error classes. Defaults to True |
| 170 | +
|
| 171 | + Raises: |
| 172 | + error: Any errors previously encountered, unless it was a batch error and continue_on_batch_error was True. |
| 173 | +
|
| 174 | + Returns: |
| 175 | + PowerGridBatchError | None: None if there were no errors, or the previously encountered |
| 176 | + error if it was a batch error and continue_on_batch_error was True. |
| 177 | + """ |
| 178 | + error: RuntimeError | None = find_error(batch_size=batch_size, decode_error=decode_error) |
| 179 | + if error is None: |
| 180 | + return None |
| 181 | + |
| 182 | + if continue_on_batch_error and isinstance(error, PowerGridBatchError): |
| 183 | + # continue on batch error |
| 184 | + return error |
| 185 | + |
| 186 | + # raise normal error |
| 187 | + raise error |
0 commit comments