def ieee1159_voltage(mauka_message: protobuf.mauka_pb2.MaukaMessage, rms_features: np.ndarray, opq_mongo_client: mongo.OpqMongoClient = None): """ Calculate the ieee1159 voltage incidents and add them to the mongo database """ incidents, cycle_offsets = classify_ieee1159_voltage(rms_features) for idx, incident in enumerate(incidents): start_idx = cycle_offsets[idx][0] end_idx = cycle_offsets[idx][1] deviations = np.abs( rms_features[start_idx:end_idx]) - constants.NOMINAL_VRMS # The absolute value of the rms_features here and elsewhere is unecessary provided the rms has been applied max_deviation = np.amax(deviations) max_deviation_neg = np.amax(-deviations) if max_deviation < max_deviation_neg: max_deviation = -max_deviation_neg mongo.store_incident( mauka_message.payload.event_id, mauka_message.payload.box_id, mauka_message.payload.start_timestamp_ms + analysis.c_to_ms(start_idx), mauka_message.payload.start_timestamp_ms + analysis.c_to_ms(end_idx), mongo.IncidentMeasurementType.VOLTAGE, max_deviation, [incident], [], {}, opq_mongo_client)
def test_itic_region_prohibited(self): """ All test results should return a prohibited ITIC region. First we'll test a few that are clearly prohibited and then we will probe around the edge of the prohibited region to test edge cases. """ self.assertEqual(IticRegion.PROHIBITED, itic_region(percent_nominal_to_rms(300), 500)) self.assertEqual(IticRegion.PROHIBITED, itic_region(percent_nominal_to_rms(600), 3)) self.assertEqual(IticRegion.PROHIBITED, itic_region(percent_nominal_to_rms(140), 20)) self.assertEqual( IticRegion.PROHIBITED, itic_region(percent_nominal_to_rms(500), analysis.c_to_ms(.02))) self.assertEqual(IticRegion.PROHIBITED, itic_region(percent_nominal_to_rms(200), 1)) self.assertEqual(IticRegion.PROHIBITED, itic_region(percent_nominal_to_rms(140), 3)) self.assertEqual(IticRegion.PROHIBITED, itic_region(percent_nominal_to_rms(121), 3)) self.assertEqual(IticRegion.PROHIBITED, itic_region(percent_nominal_to_rms(120), 500)) self.assertEqual(IticRegion.PROHIBITED, itic_region(percent_nominal_to_rms(110), 501)) self.assertEqual(IticRegion.PROHIBITED, itic_region(percent_nominal_to_rms(110), 501)) self.assertEqual(IticRegion.PROHIBITED, itic_region(percent_nominal_to_rms(110), 1000000)) self.assertEqual( IticRegion.PROHIBITED, itic_region(percent_nominal_to_rms(10000), analysis.c_to_ms(.02)))
def itic(mauka_message: protobuf.mauka_pb2.MaukaMessage, segment_threshold: float, logger=None, opq_mongo_client: mongo.OpqMongoClient = None): """ Computes the ITIC region for a given waveform. :param mauka_message: :param segment_threshold: Threshold for segmentation :param logger: Optional logger to use to print information :param opq_mongo_client: Optional DB client to re-use (otherwise new one will be created) :return: ITIC region. """ mongo_client = mongo.get_default_client(opq_mongo_client) if len(mauka_message.payload.data) < 0.01: return segments = analysis.segment(mauka_message.payload.data, segment_threshold) if logger is not None: logger.debug("Calculating ITIC with {} segments.".format( len(segments))) for segment in segments: start_idx = segment[0] end_idx = segment[1] + 1 subarray = mauka_message.payload.data[start_idx:end_idx] mean_rms = numpy.mean(subarray) itic_enum = itic_region(mean_rms, analysis.c_to_ms(len(subarray))) if itic_enum == IticRegion.NO_INTERRUPTION: continue else: incident_start_timestamp_ms = mauka_message.payload.start_timestamp_ms + analysis.c_to_ms( start_idx) incident_end_timestamp_ms = mauka_message.payload.start_timestamp_ms + analysis.c_to_ms( end_idx) if itic_enum is IticRegion.PROHIBITED: incident_classification = mongo.IncidentClassification.ITIC_PROHIBITED else: incident_classification = mongo.IncidentClassification.ITIC_NO_DAMAGE mongo.store_incident(mauka_message.event_id, mauka_message.box_id, incident_start_timestamp_ms, incident_end_timestamp_ms, mongo.IncidentMeasurementType.VOLTAGE, mean_rms - 120.0, [incident_classification], [], {}, mongo_client) if logger is not None: logger.debug( "Found ITIC incident [{}] from event {} and box {}".format( itic_enum, mauka_message.event_id, mauka_message.box_id))
def itic_region(rms_voltage: float, duration_ms: float) -> IticRegion: """ Returns the ITIC region of a given RMS voltage and duration. The reference curve is at http://www.keysight.com/upload/cmc_upload/All/1.pdf :param rms_voltage: The RMS voltage value :param duration_ms: The duration of the voltage event in milliseconds :return: The appropriate ITIC region enum """ percent_nominal = (rms_voltage / 120.0) * 100.0 # First, let's check the extreme edge cases. This can save us some time computing # point in polygon if we can identify an extreme edge case first. if duration_ms < analysis.c_to_ms(0.01): return IticRegion.NO_INTERRUPTION if rms_voltage <= 0: if duration_ms <= 20: return IticRegion.NO_INTERRUPTION return IticRegion.NO_DAMAGE # In the x and y directions if duration_ms >= 10000 and percent_nominal >= 500: return IticRegion.PROHIBITED # In the x-direction if duration_ms >= 10000: if percent_nominal >= 110: return IticRegion.PROHIBITED elif percent_nominal <= 90: return IticRegion.NO_DAMAGE return IticRegion.NO_INTERRUPTION # In the y-direction if percent_nominal >= 500: if duration_ms <= HUNDREDTH_OF_A_CYCLE: return IticRegion.NO_INTERRUPTION return IticRegion.PROHIBITED # If the voltage is not an extreme case, we run point in polygon calculations to determine which region its in if point_in_polygon(duration_ms, percent_nominal, NO_INTERRUPTION_REGION_POLYGON): return IticRegion.NO_INTERRUPTION if point_in_polygon(duration_ms, percent_nominal, PROHIBITED_REGION_POLYGON): return IticRegion.PROHIBITED if point_in_polygon(duration_ms, percent_nominal, NO_DAMAGE_REGION_POLYGON): return IticRegion.NO_DAMAGE # If it's directly on the line of one of the polygons, its easiest to just say no_interruption return IticRegion.NO_INTERRUPTION
def test_itic_region_very_short_duration(self): """ Any duration less than 0.01 cycles are short enough that they should always return no interruption irrelevant of the voltage. """ short_duration = analysis.c_to_ms(0.009) self.assertEqual(IticRegion.NO_INTERRUPTION, itic_region(percent_nominal_to_rms(120), short_duration)) self.assertEqual(IticRegion.NO_INTERRUPTION, itic_region(percent_nominal_to_rms(400), short_duration)) self.assertEqual(IticRegion.NO_INTERRUPTION, itic_region(percent_nominal_to_rms(4000), short_duration)) self.assertEqual(IticRegion.NO_INTERRUPTION, itic_region(percent_nominal_to_rms(0), short_duration))
def get_ieee_duration(duration_ms: float) -> IncidentIeeeDuration: """ Given a duration in milliseconds, return the corresponding IEEE duration classification. :param duration_ms: Duration in milliseconds. :return: IEEE duration classification. """ ms_half_c = analysis.c_to_ms(0.5) ms_30_c = analysis.c_to_ms(30) ms_3_s = 3_000 ms_1_m = 60_000 if ms_half_c < duration_ms <= ms_30_c: return IncidentIeeeDuration.INSTANTANEOUS elif ms_30_c < duration_ms <= ms_3_s: return IncidentIeeeDuration.MOMENTARY elif ms_3_s < duration_ms <= ms_1_m: return IncidentIeeeDuration.TEMPORARY elif duration_ms > ms_1_m: return IncidentIeeeDuration.SUSTAINED return IncidentIeeeDuration.UNDEFINED
def test_itic_region_no_interruption(self): """ All test results should return a no interruption ITIC region. First we'll test a few that are clearly no damage and then we will probe around the edge of the no damage region to test edge cases. """ self.assertEqual(IticRegion.NO_INTERRUPTION, itic_region(percent_nominal_to_rms(100), 0)) self.assertEqual(IticRegion.NO_INTERRUPTION, itic_region(percent_nominal_to_rms(100), 1)) self.assertEqual(IticRegion.NO_INTERRUPTION, itic_region(percent_nominal_to_rms(100), 10000)) self.assertEqual(IticRegion.NO_INTERRUPTION, itic_region(percent_nominal_to_rms(100), 1000000)) self.assertEqual( IticRegion.NO_INTERRUPTION, itic_region(percent_nominal_to_rms(500), analysis.c_to_ms(0.01))) self.assertEqual( IticRegion.NO_INTERRUPTION, itic_region(percent_nominal_to_rms(1000), analysis.c_to_ms(0.01))) self.assertEqual(IticRegion.NO_INTERRUPTION, itic_region(0, analysis.c_to_ms(0.01))) self.assertEqual(IticRegion.NO_INTERRUPTION, itic_region(0, 20)) self.assertEqual(IticRegion.NO_INTERRUPTION, itic_region(0, 20)) self.assertEqual(IticRegion.NO_INTERRUPTION, itic_region(percent_nominal_to_rms(200), .9)) self.assertEqual(IticRegion.NO_INTERRUPTION, itic_region(percent_nominal_to_rms(140), 2.9)) self.assertEqual(IticRegion.NO_INTERRUPTION, itic_region(percent_nominal_to_rms(120), 2.9)) self.assertEqual(IticRegion.NO_INTERRUPTION, itic_region(percent_nominal_to_rms(110), 0.5)) self.assertEqual(IticRegion.NO_INTERRUPTION, itic_region(percent_nominal_to_rms(90), 9999)) self.assertEqual(IticRegion.NO_INTERRUPTION, itic_region(percent_nominal_to_rms(80), 499)) self.assertEqual(IticRegion.NO_INTERRUPTION, itic_region(percent_nominal_to_rms(70), 19)) self.assertEqual(IticRegion.NO_INTERRUPTION, itic_region(percent_nominal_to_rms(40), 19))
def test_on_line(self): self.assertEqual(IticRegion.NO_INTERRUPTION, itic_region(percent_nominal_to_rms(500), analysis.c_to_ms(0.01))) self.assertEqual(IticRegion.NO_INTERRUPTION, itic_region(percent_nominal_to_rms(1_000_000), analysis.c_to_ms(0.01))) self.assertEqual(IticRegion.NO_INTERRUPTION, itic_region(percent_nominal_to_rms(200), 1)) self.assertEqual(IticRegion.NO_INTERRUPTION, itic_region(percent_nominal_to_rms(140), 3)) self.assertEqual(IticRegion.NO_INTERRUPTION, itic_region(percent_nominal_to_rms(120), 3)) self.assertEqual(IticRegion.NO_INTERRUPTION, itic_region(percent_nominal_to_rms(120), 500)) self.assertEqual(IticRegion.NO_INTERRUPTION, itic_region(percent_nominal_to_rms(110), 500)) # self.assertEqual(IticRegion.NO_INTERRUPTION, itic_region(percent_nominal_to_rms(110), 800)) self.assertEqual(IticRegion.NO_INTERRUPTION, itic_region(percent_nominal_to_rms(0), 20)) self.assertEqual(IticRegion.NO_INTERRUPTION, itic_region(percent_nominal_to_rms(40), 20)) self.assertEqual(IticRegion.NO_INTERRUPTION, itic_region(percent_nominal_to_rms(70), 20)) self.assertEqual(IticRegion.NO_INTERRUPTION, itic_region(percent_nominal_to_rms(70), 500)) self.assertEqual(IticRegion.NO_INTERRUPTION, itic_region(percent_nominal_to_rms(80), 500)) self.assertEqual(IticRegion.NO_INTERRUPTION, itic_region(percent_nominal_to_rms(80), 10_000)) self.assertEqual(IticRegion.NO_INTERRUPTION, itic_region(percent_nominal_to_rms(90), 10_000)) self.assertEqual(IticRegion.NO_INTERRUPTION, itic_region(percent_nominal_to_rms(90), 100_000)) self.assertEqual(IticRegion.NO_INTERRUPTION, itic_region(percent_nominal_to_rms(90), 1_000_000))
import plugins.base_plugin import protobuf.mauka_pb2 import protobuf.util class IticRegion(enum.Enum): """ Enumerations of ITIC regions. """ NO_INTERRUPTION = "NO_INTERRUPTION" PROHIBITED = "PROHIBITED" NO_DAMAGE = "NO_DAMAGE" OTHER = "OTHER" HUNDREDTH_OF_A_CYCLE = analysis.c_to_ms(0.01) """Hundredth of a power cycle in milliseconds""" PROHIBITED_REGION_POLYGON = [[HUNDREDTH_OF_A_CYCLE, 500], [1, 200], [3, 140], [3, 120], [20, 120], [500, 120], [500, 110], [10000, 110], [10000, 500], [HUNDREDTH_OF_A_CYCLE, 500]] """Polygon representing the prohibited region""" NO_DAMAGE_REGION_POLYGON = [[20, 0], [20, 40], [20, 70], [500, 70], [500, 80], [10000, 80], [10000, 90], [10000, 0], [20, 0]] """Polygon representing the no damage region""" NO_INTERRUPTION_REGION_POLYGON = [[0, 0], [0, 500], [HUNDREDTH_OF_A_CYCLE, 500], [1, 200], [3, 140], [3, 120], [20, 120], [500, 120],
def itic(mauka_message: protobuf.mauka_pb2.MaukaMessage, segment_threshold: float, itic_plugin: typing.Optional['IticPlugin'] = None, opq_mongo_client: typing.Optional[mongo.OpqMongoClient] = None) -> typing.List[int]: """ Computes the ITIC region for a given waveform. :param itic_plugin: An instance of this plugin. :param mauka_message: A mauka message. :param segment_threshold: Threshold for segmentation :param opq_mongo_client: Optional DB client to re-use (otherwise new one will be created) :return: ITIC region. """ mongo_client = mongo.get_default_client(opq_mongo_client) if len(mauka_message.payload.data) < 0.01: maybe_debug(itic_plugin, "Bad payload data length: %d" % len(mauka_message.payload.data)) maybe_debug(itic_plugin, "Preparing to get segments for %d Vrms values" % len(mauka_message.payload.data)) # segments = analysis.segment(mauka_message.payload.data, segment_threshold) try: segments = analysis.segment_array(numpy.array(list(mauka_message.payload.data))) except Exception as exception: itic_plugin.logger.error("Error segmenting data for ITIC plugin: %s", str(exception)) segments = [] if len(segments) == 0: maybe_debug(itic_plugin, "No segments found. Ignoring") return [] maybe_debug(itic_plugin, "Calculating ITIC with {} segments.".format(len(segments))) incident_ids = [] for i, segment in enumerate(segments): try: segment_len = analysis.c_to_ms(len(segment)) start_t = analysis.c_to_ms(sum([len(segments[x]) for x in range(0, i)])) end_t = start_t + segment_len mean_rms = segment.mean() maybe_debug(itic_plugin, "start=%f end=%f mean=%f" % (start_t, end_t, mean_rms)) itic_enum = itic_region(mean_rms, segment_len) if itic_enum == IticRegion.NO_INTERRUPTION: maybe_debug(itic_plugin, "NO_INTERRUPTION") continue else: incident_start_timestamp_ms = mauka_message.payload.start_timestamp_ms + start_t incident_end_timestamp_ms = mauka_message.payload.start_timestamp_ms + end_t if itic_enum is IticRegion.PROHIBITED: maybe_debug(itic_plugin, "PROHIBITED") incident_classification = mongo.IncidentClassification.ITIC_PROHIBITED else: maybe_debug(itic_plugin, "NO_DAMAGE") incident_classification = mongo.IncidentClassification.ITIC_NO_DAMAGE incident_id = mongo.store_incident( itic_plugin.request_next_available_incident_id(), mauka_message.payload.event_id, mauka_message.payload.box_id, incident_start_timestamp_ms, incident_end_timestamp_ms, mongo.IncidentMeasurementType.VOLTAGE, mean_rms - 120.0, [incident_classification], [], {}, mongo_client) maybe_debug(itic_plugin, "Stored incident") maybe_debug(itic_plugin, "Found ITIC incident [{}] from event {} and box {}".format( itic_enum, mauka_message.event_id, mauka_message.box_id)) incident_ids.append(incident_id) except Exception as exception: itic_plugin.logger.error("Error storing ITIC incident: %s", str(exception)) return incident_ids