def derive(self, src_1=P('Latitude (1)'), src_2=P('Latitude (2)'), src_3=P('Latitude (3)')): sources = [ source for source in [src_1, src_2, src_3] if source is not None \ and np.count_nonzero(source.array) > len(source.array)/2 ] if len(sources) == 1: self.offset = sources[0].offset self.frequency = sources[0].frequency self.array = sources[0].array elif len(sources) == 2: self.array, self.frequency, self.offset = blend_two_parameters( sources[0], sources[1]) elif len(sources) > 2: self.offset = 0.0 self.frequency = 1.0 self.array = blend_parameters(sources, offset=self.offset, frequency=self.frequency) else: self.array = np_ma_masked_zeros_like(src_1.array)
def check_derived_array(param_name, result, duration): # check that the right number of results were returned # Allow a small tolerance. For example if duration in seconds # is 2822, then there will be an array length of 1411 at 0.5Hz and 706 # at 0.25Hz (rounded upwards). If we combine two 0.25Hz # parameters then we will have an array length of 1412. expected_length = duration * result.frequency if result.array is None: logger.debug("No array set; creating a fully masked array for %s", param_name) array_length = expected_length # Where a parameter is wholly masked, we fill the HDF # file with masked zeros to maintain structure. result.array = \ np_ma_masked_zeros_like(np.ma.arange(expected_length)) else: array_length = len(result.array) length_diff = array_length - expected_length if length_diff == 0: pass elif 0 < length_diff < 5: logger.debug( "Cutting excess data for parameter '%s'. Expected length was '%s' while resulting " "array length was '%s'.", param_name, expected_length, len(result.array)) result.array = result.array[:expected_length] else: #pdb.set_trace() raise ValueError("Array length mismatch for parameter " "'%s'. Expected '%s', resulting array length '%s'." % (param_name, expected_length, array_length)) return result
def derive(self, src_1=P('Longitude (1)'), src_2=P('Longitude (2)'), src_3=P('Longitude (3)')): sources = [ source for source in [src_1, src_2, src_3] if source is not None \ and np.count_nonzero(source.array) > len(source.array)/2 ] if len(sources) > 1: for source in sources: source.array = repair_mask( straighten_longitude(source.array) + 180.0) if len(sources) == 1: self.offset = sources[0].offset self.frequency = sources[0].frequency self.array = sources[0].array elif len(sources) == 2: blended, self.frequency, self.offset = blend_two_parameters( sources[0], sources[1]) self.array = blended % 360 - 180.0 elif len(sources) > 2: self.offset = 0.0 self.frequency = 1.0 blended = blend_parameters(sources, offset=self.offset, frequency=self.frequency) self.array = blended % 360 - 180.0 else: self.array = np_ma_masked_zeros_like(src_1.array)
def derive(self, gl=M('Gear (L) Position'), gn=M('Gear (N) Position'), gr=M('Gear (R) Position'), gc=M('Gear (C) Position')): up_state = vstack_params_where_state( (gl, 'Up'), (gn, 'Up'), (gr, 'Up'), (gc, 'Up'), ).all(axis=0) down_state = vstack_params_where_state( (gl, 'Down'), (gn, 'Down'), (gr, 'Down'), (gc, 'Down'), ).all(axis=0) transit_state = vstack_params_where_state( (gl, 'In Transit'), (gn, 'In Transit'), (gr, 'In Transit'), (gc, 'In Transit'), ).any(axis=0) param = first_valid_parameter(gl, gn, gr, gc) self.array = np_ma_masked_zeros_like(param.array) self.array[repair_mask(up_state, repair_duration=None)] = 'Up' self.array[repair_mask(down_state, repair_duration=None)] = 'Down' self.array[repair_mask(transit_state, repair_duration=None)] = 'In Transit' self.array = nearest_neighbour_mask_repair(self.array)
def seek_deck(rad, hdot, min_idx, rad_hz): def one_direction(rad, hdot, sence, rad_hz): # Stairway to Heaven is getting a bit old. Getting with the times? b_diffs = hdot / 60 r_diffs = np.ma.ediff1d(rad, to_begin=b_diffs[0]) diffs = np.ma.where( np.ma.abs(r_diffs - b_diffs) > 6.0, b_diffs, r_diffs) height = integrate(diffs, frequency=rad_hz, direction=sence, repair=False) return height height_from_rig = np_ma_masked_zeros_like(rad) if len(rad[:min_idx]) > 0: height_from_rig[:min_idx] = one_direction( rad[:min_idx], hdot[:min_idx], "backwards", rad_hz) if len(rad[min_idx:]) > 0: height_from_rig[min_idx:] = one_direction( rad[min_idx:], hdot[min_idx:], "forwards", rad_hz) ''' # And we are bound to want to know the rig height somewhere, so here's how to work that out. rig_height = rad[0]-height_from_rig[0] # I checked this and it seems pretty consistent. # See Library\Projects\Helicopter FDM\Algorithm Development\Rig height estimates from Bond initial test data.xlsx #lat=hdf['Latitude'].array[app_slice][-1] #lon=hdf['Longitude'].array[app_slice][-1] #print(lat, lon, rig_height) ''' return height_from_rig
def manage_parameter_length(param_name, duration, result): # check that the right number of results were returned # Allow a small tolerance. For example if duration in seconds # is 2822, then there will be an array length of 1411 at 0.5Hz and 706 # at 0.25Hz (rounded upwards). If we combine two 0.25Hz # parameters then we will have an array length of 1412. expected_length = duration * result.frequency if result.array is None: # Where a parameter is wholly masked, fill the HDF file with masked zeros to maintain structure. array_length = expected_length result.array = np_ma_masked_zeros_like(np.ma.arange(expected_length)) else: array_length = len(result.array) length_diff = array_length - expected_length if length_diff == 0: pass elif 0 < length_diff < 5: logger.warning("Cutting excess data for parameter '%s'. Expected length was '%s' while resulting " "array length was '%s'.", param_name, expected_length, len(result.array)) result.array = result.array[:expected_length] else: raise ValueError("Array length mismatch for parameter '%s'. Expected '%s', resulting array " "length '%s'." % (param_name, expected_length, array_length)) return result
def derive( self, rad=P('Altitude Radio'), hdot=P('Vertical Speed'), ): def seek_deck(rad, hdot, min_idx, rad_hz): def one_direction(rad, hdot, sence, rad_hz): # Stairway to Heaven is getting a bit old. Getting with the times? b_diffs = hdot / 60 r_diffs = np.ma.ediff1d(rad, to_begin=b_diffs[0]) diffs = np.ma.where( np.ma.abs(r_diffs - b_diffs) > 6.0, b_diffs, r_diffs) height = integrate(diffs, frequency=rad_hz, direction=sence, repair=False) return height height_from_rig = np_ma_masked_zeros_like(rad) if len(rad[:min_idx]) > 0: height_from_rig[:min_idx] = one_direction( rad[:min_idx], hdot[:min_idx], "backwards", rad_hz) if len(rad[min_idx:]) > 0: height_from_rig[min_idx:] = one_direction( rad[min_idx:], hdot[min_idx:], "forwards", rad_hz) ''' # And we are bound to want to know the rig height somewhere, so here's how to work that out. rig_height = rad[0]-height_from_rig[0] # I checked this and it seems pretty consistent. # See Library\Projects\Helicopter FDM\Algorithm Development\Rig height estimates from Bond initial test data.xlsx #lat=hdf['Latitude'].array[app_slice][-1] #lon=hdf['Longitude'].array[app_slice][-1] #print(lat, lon, rig_height) ''' return height_from_rig self.array = np_ma_masked_zeros_like(rad.array) rad_peak_idxs, rad_peak_vals = cycle_finder(rad.array, min_step=150.0) if len(rad_peak_idxs) < 4: return slice_idxs = zip(rad_peak_idxs[:-2], rad_peak_idxs[1:-1], rad_peak_idxs[2:], rad_peak_vals[1:]) for slice_idx in slice_idxs[1:-1]: this_deck_slice = slice(slice_idx[0] + 1, slice_idx[2] - 1) if slice_idx[3] > 5.0: # We didn't land in this period continue else: self.array[this_deck_slice] = seek_deck( rad.array[this_deck_slice], hdot.array[this_deck_slice], slice_idx[1] - slice_idx[0], rad.frequency) '''
def derive(self, alt_aal=P('Altitude AAL'), lat=P('Latitude Smoothed'), lon=P('Longitude Smoothed'), tdwns=KTI('Touchdown')): app_range = np_ma_masked_zeros_like(alt_aal.array) #Helicopter compuation does not rely on runways! stop_delay = 10 # To make sure the helicopter has stopped moving for tdwn in tdwns: end = tdwn.index endpoint = {'latitude': lat.array[int(end)], 'longitude': lon.array[int(end)]} prev_tdwn = tdwns.get_previous(end) begin = prev_tdwn.index + stop_delay if prev_tdwn else 0 this_leg = slices_int(begin, end+stop_delay) _, app_range[this_leg] = bearings_and_distances(lat.array[this_leg], lon.array[this_leg], endpoint) self.array = app_range
def seek_deck(rad, hdot, min_idx, rad_hz): def one_direction(rad, hdot, sence, rad_hz): # Stairway to Heaven is getting a bit old. Getting with the times? # Vertical Speed / 60 = Pressure alt V/S in feet per second b_diffs = hdot / 60 # Rate of change on radalt array = Rad alt V/S in feet per second r_diffs = np.ma.ediff1d(rad * rad_hz, to_begin=b_diffs[0]) # Difference between ROC greater than 6fps will mean flying over # the deck; use pressure alt roc when that happens and radio alt # roc in all other cases diffs = np.ma.where( np.ma.abs(r_diffs - b_diffs) > 6.0 * rad_hz, b_diffs, r_diffs) height = integrate(diffs, frequency=rad_hz, direction=sence, repair=False) return height height_from_rig = np_ma_masked_zeros_like(rad) if len(rad[:min_idx]) > 0: height_from_rig[:min_idx] = one_direction( rad[:min_idx], hdot[:min_idx], "backwards", rad_hz) if len(rad[min_idx:]) > 0: height_from_rig[min_idx:] = one_direction( rad[min_idx:], hdot[min_idx:], "forwards", rad_hz) ''' # And we are bound to want to know the rig height somewhere, so here's how to work that out. rig_height = rad[0]-height_from_rig[0] # I checked this and it seems pretty consistent. # See Library\Projects\Helicopter FDM\Algorithm Development\Rig height estimates from Bond initial test data.xlsx #lat=hdf['Latitude'].array[app_slice][-1] #lon=hdf['Longitude'].array[app_slice][-1] #print(lat, lon, rig_height) ''' return height_from_rig
def seek_deck(rad, hdot, min_idx, rad_hz): def one_direction(rad, hdot, sence, rad_hz): # Stairway to Heaven is getting a bit old. Getting with the times? # Vertical Speed / 60 = Pressure alt V/S in feet per second b_diffs = hdot/60 # Rate of change on radalt array = Rad alt V/S in feet per second r_diffs = np.ma.ediff1d(rad*rad_hz, to_begin=b_diffs[0]) # Difference between ROC greater than 6fps will mean flying over # the deck; use pressure alt roc when that happens and radio alt # roc in all other cases diffs = np.ma.where(np.ma.abs(r_diffs-b_diffs)>6.0*rad_hz, b_diffs, r_diffs) height = integrate(diffs, frequency=rad_hz, direction=sence, repair=False) return height height_from_rig = np_ma_masked_zeros_like(rad) if len(rad[:min_idx]) > 0: height_from_rig[:min_idx] = one_direction(rad[:min_idx], hdot[:min_idx], "backwards", rad_hz) if len(rad[min_idx:]) > 0: height_from_rig[min_idx:] = one_direction(rad[min_idx:], hdot[min_idx:], "forwards", rad_hz) ''' # And we are bound to want to know the rig height somewhere, so here's how to work that out. rig_height = rad[0]-height_from_rig[0] # I checked this and it seems pretty consistent. # See Library\Projects\Helicopter FDM\Algorithm Development\Rig height estimates from Bond initial test data.xlsx #lat=hdf['Latitude'].array[app_slice][-1] #lon=hdf['Longitude'].array[app_slice][-1] #print(lat, lon, rig_height) ''' return height_from_rig
def derive( self, air_spd=P('Airspeed'), flap=P('Flap'), #conf=P('Configuration'), gw=P('Gross Weight Smoothed'), touchdowns=KTI('Touchdown'), series=A('Series'), family=A('Family'), engine=A('Engine Series'), engine_type=A('Engine Type'), eng_np=P('Eng (*) Np Avg'), vref=P('Vref'), afr_vref=A('AFR Vref'), approaches=S('Approach And Landing')): if vref: # Use recorded Vref parameter: self.array = vref.array elif afr_vref: # Use provided Vref from achieved flight record: afr_vspeed = afr_vref self.array = np.ma.zeros(len(air_spd.array), np.double) self.array.mask = True for approach in approaches: self.array[approach.slice] = afr_vspeed.value else: # Use speed card lookups x = map(lambda x: x.value if x else None, (series, family, engine, engine_type)) vspeed_class_test = get_vspeed_map_mitre(*x) if vspeed_class_test: vspeed_class = vspeed_class_test else: vspeed_class = get_vspeed_map(*x) if gw is not None: # and you must have eng_np try: # Allow up to 2 superframe values to be repaired: # (64 * 2 = 128 + a bit) repaired_gw = repair_mask(gw.array, repair_duration=130, copy=True, extrapolate=True) except: self.warning( "'Airspeed Reference' will be fully masked " "because 'Gross Weight Smoothed' array could not be " "repaired.") return setting_param = flap #or conf vspeed_table = vspeed_class() for approach in approaches: _slice = approach.slice '''TODO: Only uses max Vref setting, doesn't account for late config changes''' index = np.ma.argmax(setting_param.array[_slice]) setting = setting_param.array[_slice][index] weight = repaired_gw[_slice][ index] if gw is not None else None if setting in vspeed_table.vref_settings: ##and is_index_within_slice(touchdowns.get_last().index, _slice): # setting in vspeed table: vspeed = vspeed_table.vref(setting, weight) else: ''' Do not like the default of using max Vref for go arounds... ''' ## No landing and max setting not in vspeed table: #if setting_param.name == 'Flap': #setting = max(get_flap_map(series.value, family.value)) #else: #setting = max(get_conf_map(series.value, family.value).keys()) #vspeed = vspeed_table.vref(setting, weight) self.warning( "'Airspeed Reference' will be fully masked " "because Vref lookup table does not have corresponding values." ) return self.array = np_ma_masked_zeros_like(air_spd.array) self.array[_slice] = vspeed
def derive(self, air_spd=P('Airspeed'), flap=P('Flap'), #conf=P('Configuration'), gw=P('Gross Weight Smoothed'), touchdowns=KTI('Touchdown'), series=A('Series'), family=A('Family'), engine=A('Engine Series'), engine_type=A('Engine Type'), eng_np=P('Eng (*) Np Avg'), vref=P('Vref'), afr_vref=A('AFR Vref'), approaches=S('Approach And Landing')): if vref: # Use recorded Vref parameter: self.array=vref.array elif afr_vref: # Use provided Vref from achieved flight record: afr_vspeed = afr_vref self.array = np.ma.zeros(len(air_spd.array), np.double) self.array.mask = True for approach in approaches: self.array[approach.slice] = afr_vspeed.value else: # Use speed card lookups x = map(lambda x: x.value if x else None, (series, family, engine, engine_type)) vspeed_class_test = get_vspeed_map_mitre(*x) if vspeed_class_test: vspeed_class = vspeed_class_test else: vspeed_class = get_vspeed_map(*x) if gw is not None: # and you must have eng_np try: # Allow up to 2 superframe values to be repaired: # (64 * 2 = 128 + a bit) repaired_gw = repair_mask(gw.array, repair_duration=130, copy=True, extrapolate=True) except: self.warning("'Airspeed Reference' will be fully masked " "because 'Gross Weight Smoothed' array could not be " "repaired.") return setting_param = flap #or conf vspeed_table = vspeed_class() for approach in approaches: _slice = approach.slice '''TODO: Only uses max Vref setting, doesn't account for late config changes''' index = np.ma.argmax(setting_param.array[_slice]) setting = setting_param.array[_slice][index] weight = repaired_gw[_slice][index] if gw is not None else None if setting in vspeed_table.vref_settings: ##and is_index_within_slice(touchdowns.get_last().index, _slice): # setting in vspeed table: vspeed = vspeed_table.vref(setting, weight) else: ''' Do not like the default of using max Vref for go arounds... ''' ## No landing and max setting not in vspeed table: #if setting_param.name == 'Flap': #setting = max(get_flap_map(series.value, family.value)) #else: #setting = max(get_conf_map(series.value, family.value).keys()) #vspeed = vspeed_table.vref(setting, weight) self.warning("'Airspeed Reference' will be fully masked " "because Vref lookup table does not have corresponding values.") return self.array = np_ma_masked_zeros_like(air_spd.array) self.array[_slice] = vspeed
def derive(self, rad=P('Altitude Radio'), hdot=P('Vertical Speed'), ): def seek_deck(rad, hdot, min_idx, rad_hz): def one_direction(rad, hdot, sence, rad_hz): # Stairway to Heaven is getting a bit old. Getting with the times? # Vertical Speed / 60 = Pressure alt V/S in feet per second b_diffs = hdot/60 # Rate of change on radalt array = Rad alt V/S in feet per second r_diffs = np.ma.ediff1d(rad*rad_hz, to_begin=b_diffs[0]) # Difference between ROC greater than 6fps will mean flying over # the deck; use pressure alt roc when that happens and radio alt # roc in all other cases diffs = np.ma.where(np.ma.abs(r_diffs-b_diffs)>6.0*rad_hz, b_diffs, r_diffs) height = integrate(diffs, frequency=rad_hz, direction=sence, repair=False) return height height_from_rig = np_ma_masked_zeros_like(rad) if len(rad[:min_idx]) > 0: height_from_rig[:min_idx] = one_direction(rad[:min_idx], hdot[:min_idx], "backwards", rad_hz) if len(rad[min_idx:]) > 0: height_from_rig[min_idx:] = one_direction(rad[min_idx:], hdot[min_idx:], "forwards", rad_hz) ''' # And we are bound to want to know the rig height somewhere, so here's how to work that out. rig_height = rad[0]-height_from_rig[0] # I checked this and it seems pretty consistent. # See Library\Projects\Helicopter FDM\Algorithm Development\Rig height estimates from Bond initial test data.xlsx #lat=hdf['Latitude'].array[app_slice][-1] #lon=hdf['Longitude'].array[app_slice][-1] #print(lat, lon, rig_height) ''' return height_from_rig # Prepare a masked array filled with zeros for the parameter (same length as radalt array) self.array = np_ma_masked_zeros_like(rad.array) rad_peak_idxs, rad_peak_vals = cycle_finder(rad.array, min_step=150.0) if len(rad_peak_idxs)<4: return slice_idxs = list(zip(rad_peak_idxs[:-2], rad_peak_idxs[1:-1], rad_peak_idxs[2:], rad_peak_vals[1:])) for slice_idx in slice_idxs[1:-1]: this_deck_slice = slice(slice_idx[0]+1, slice_idx[2]-1) if slice_idx[3] > 5.0: # We didn't land in this period continue else: self.array[this_deck_slice] = seek_deck(rad.array[this_deck_slice], hdot.array[this_deck_slice], slice_idx[1]-slice_idx[0], rad.frequency) '''
def derive_parameters(hdf, node_mgr, process_order): ''' Derives parameters in process_order. Dependencies are sourced via the node_mgr. :param hdf: Data file accessor used to get and save parameter data and attributes :type hdf: hdf_file :param node_mgr: Used to determine the type of node in the process_order :type node_mgr: NodeManager :param process_order: Parameter / Node class names in the required order to be processed :type process_order: list of strings ''' # store all derived params that aren't masked arrays params = {} approach_list = ApproachNode(restrict_names=False) # duplicate storage, but maintaining types kpv_list = KeyPointValueNode(restrict_names=False) kti_list = KeyTimeInstanceNode(restrict_names=False) # 'Node Name' : node() pass in node.get_accessor() section_list = SectionNode() flight_attrs = [] duration = hdf.duration for param_name in process_order: if param_name in node_mgr.hdf_keys: continue elif node_mgr.get_attribute(param_name) is not None: # add attribute to dictionary of available params ###params[param_name] = node_mgr.get_attribute(param_name) #TODO: optimise with only one call to get_attribute continue #NB raises KeyError if Node is "unknown" node_class = node_mgr.derived_nodes[param_name] # build ordered dependencies deps = [] node_deps = node_class.get_dependency_names() for dep_name in node_deps: if dep_name in params: # already calculated KPV/KTI/Phase deps.append(params[dep_name]) elif node_mgr.get_attribute(dep_name) is not None: deps.append(node_mgr.get_attribute(dep_name)) elif dep_name in node_mgr.hdf_keys: # LFL/Derived parameter # all parameters (LFL or other) need get_aligned which is # available on DerivedParameterNode try: dp = derived_param_from_hdf( hdf.get_param(dep_name, valid_only=True)) except KeyError: # Parameter is invalid. dp = None deps.append(dp) else: # dependency not available deps.append(None) if all([d is None for d in deps]): raise RuntimeError("No dependencies available - Nodes cannot " "operate without ANY dependencies available! " "Node: %s" % node_class.__name__) # initialise node node = node_class() # shhh, secret accessors for developing nodes in debug mode node._p = params node._h = hdf node._n = node_mgr logger.info("Processing %s `%s`", get_node_type(node), param_name) # Derive the resulting value result = node.get_derived(deps) del node._p del node._h del node._n if node.node_type is KeyPointValueNode: #Q: track node instead of result here?? params[param_name] = result for one_hz in result.get_aligned(P(frequency=1, offset=0)): if not (0 <= one_hz.index <= duration + 4): raise IndexError( "KPV '%s' index %.2f is not between 0 and %d" % (one_hz.name, one_hz.index, duration)) kpv_list.append(one_hz) elif node.node_type is KeyTimeInstanceNode: params[param_name] = result for one_hz in result.get_aligned(P(frequency=1, offset=0)): if not (0 <= one_hz.index <= duration + 4): raise IndexError( "KTI '%s' index %.2f is not between 0 and %d" % (one_hz.name, one_hz.index, duration)) kti_list.append(one_hz) elif node.node_type is FlightAttributeNode: params[param_name] = result try: # only has one Attribute result flight_attrs.append(Attribute(result.name, result.value)) except: logger.warning( "Flight Attribute Node '%s' returned empty " "handed.", param_name) elif issubclass(node.node_type, SectionNode): aligned_section = result.get_aligned(P(frequency=1, offset=0)) for index, one_hz in enumerate(aligned_section): # SectionNodes allow slice starts and stops being None which # signifies the beginning and end of the data. To avoid # TypeErrors in subsequent derive methods which perform # arithmetic on section slice start and stops, replace with 0 # or hdf.duration. fallback = lambda x, y: x if x is not None else y duration = fallback(duration, 0) start = fallback(one_hz.slice.start, 0) stop = fallback(one_hz.slice.stop, duration) start_edge = fallback(one_hz.start_edge, 0) stop_edge = fallback(one_hz.stop_edge, duration) slice_ = slice(start, stop) one_hz = Section(one_hz.name, slice_, start_edge, stop_edge) aligned_section[index] = one_hz if not (0 <= start <= duration and 0 <= stop <= duration + 4): msg = "Section '%s' (%.2f, %.2f) not between 0 and %d" raise IndexError(msg % (one_hz.name, start, stop, duration)) if not 0 <= start_edge <= duration: msg = "Section '%s' start_edge (%.2f) not between 0 and %d" raise IndexError(msg % (one_hz.name, start_edge, duration)) if not 0 <= stop_edge <= duration + 4: msg = "Section '%s' stop_edge (%.2f) not between 0 and %d" raise IndexError(msg % (one_hz.name, stop_edge, duration)) section_list.append(one_hz) params[param_name] = aligned_section elif issubclass(node.node_type, DerivedParameterNode): if duration: # check that the right number of results were returned Allow a # small tolerance. For example if duration in seconds is 2822, # then there will be an array length of 1411 at 0.5Hz and 706 # at 0.25Hz (rounded upwards). If we combine two 0.25Hz # parameters then we will have an array length of 1412. expected_length = duration * result.frequency if result.array is None: logger.warning( "No array set; creating a fully masked " "array for %s", param_name) array_length = expected_length # Where a parameter is wholly masked, we fill the HDF # file with masked zeros to maintain structure. result.array = \ np_ma_masked_zeros_like(np.ma.arange(expected_length)) else: array_length = len(result.array) length_diff = array_length - expected_length if length_diff == 0: pass elif 0 < length_diff < 5: logger.warning( "Cutting excess data for parameter '%s'. " "Expected length was '%s' while resulting " "array length was '%s'.", param_name, expected_length, len(result.array)) result.array = result.array[:expected_length] else: raise ValueError( "Array length mismatch for parameter " "'%s'. Expected '%s', resulting array " "length '%s'." % (param_name, expected_length, array_length)) hdf.set_param(result) # Keep hdf_keys up to date. node_mgr.hdf_keys.append(param_name) elif issubclass(node.node_type, ApproachNode): aligned_approach = result.get_aligned(P(frequency=1, offset=0)) for approach in aligned_approach: # Does not allow slice start or stops to be None. valid_turnoff = (not approach.turnoff or (0 <= approach.turnoff <= duration)) valid_slice = ((0 <= approach.slice.start <= duration) and (0 <= approach.slice.stop <= duration)) valid_gs_est = (not approach.gs_est or ((0 <= approach.gs_est.start <= duration) and (0 <= approach.gs_est.stop <= duration))) valid_loc_est = (not approach.loc_est or ((0 <= approach.loc_est.start <= duration) and (0 <= approach.loc_est.stop <= duration))) if not all( [valid_turnoff, valid_slice, valid_gs_est, valid_loc_est]): raise ValueError('ApproachItem contains index outside of ' 'flight data: %s' % approach) approach_list.append(approach) params[param_name] = aligned_approach else: raise NotImplementedError("Unknown Type %s" % node.__class__) continue return kti_list, kpv_list, section_list, approach_list, flight_attrs
def derive_parameters_mitre(hdf, node_mgr, process_order, precomputed_parameters={}): ''' Derives the parameter values and if limits are available, applies parameter validation upon each param before storing the resulting masked array back into the hdf file. :param hdf: Data file accessor used to get and save parameter data and attributes :type hdf: hdf_file :param node_mgr: Used to determine the type of node in the process_order :type node_mgr: NodeManager :param process_order: Parameter / Node class names in the required order to be processed :type process_order: list of strings ''' params = precomputed_parameters # dictionary of derived params that aren't masked arrays approach_list = ApproachNode(restrict_names=False) kpv_list = KeyPointValueNode(restrict_names=False) # duplicate storage, but maintaining types kti_list = KeyTimeInstanceNode(restrict_names=False) section_list = SectionNode() # 'Node Name' : node() pass in node.get_accessor() flight_attrs = [] duration = hdf.duration for param_name in process_order: if param_name in node_mgr.hdf_keys: logger.debug(' derive_: hdf '+param_name) continue elif node_mgr.get_attribute(param_name) is not None: logger.debug(' derive_: get_attribute '+param_name) continue elif param_name in params: # already calculated KPV/KTI/Phase ***********************NEW logger.debug(' derive_parameters: re-using '+param_name) continue logger.debug(' derive_: computing '+param_name) node_class = node_mgr.derived_nodes[param_name] #NB raises KeyError if Node is "unknown" # build ordered dependencies deps = [] node_deps = node_class.get_dependency_names() for dep_name in node_deps: if dep_name in params: # already calculated KPV/KTI/Phase deps.append(params[dep_name]) elif node_mgr.get_attribute(dep_name) is not None: deps.append(node_mgr.get_attribute(dep_name)) elif dep_name in node_mgr.hdf_keys: # LFL/Derived parameter # all parameters (LFL or other) need get_aligned which is # available on DerivedParameterNode try: dp = derived_param_from_hdf(hdf.get_param(dep_name, valid_only=True)) except KeyError: # Parameter is invalid. dp = None deps.append(dp) else: # dependency not available deps.append(None) if all([d is None for d in deps]): raise RuntimeError("No dependencies available - Nodes cannot " "operate without ANY dependencies available! " "Node: %s" % node_class.__name__) # initialise node node = node_class() logger.info("Processing parameter %s", param_name) # Derive the resulting value result = node.get_derived(deps) if node.node_type is KeyPointValueNode: #Q: track node instead of result here?? params[param_name] = result for one_hz in result.get_aligned(P(frequency=1, offset=0)): if not (0 <= one_hz.index <= duration): raise IndexError( "KPV '%s' index %.2f is not between 0 and %d" % (one_hz.name, one_hz.index, duration)) kpv_list.append(one_hz) elif node.node_type is KeyTimeInstanceNode: params[param_name] = result for one_hz in result.get_aligned(P(frequency=1, offset=0)): if not (0 <= one_hz.index <= duration): raise IndexError( "KTI '%s' index %.2f is not between 0 and %d" % (one_hz.name, one_hz.index, duration)) kti_list.append(one_hz) elif node.node_type is FlightAttributeNode: params[param_name] = result try: flight_attrs.append(Attribute(result.name, result.value)) # only has one Attribute result except: logger.warning("Flight Attribute Node '%s' returned empty " "handed.", param_name) elif issubclass(node.node_type, SectionNode): aligned_section = result.get_aligned(P(frequency=1, offset=0)) for index, one_hz in enumerate(aligned_section): # SectionNodes allow slice starts and stops being None which # signifies the beginning and end of the data. To avoid TypeErrors # in subsequent derive methods which perform arithmetic on section # slice start and stops, replace with 0 or hdf.duration. fallback = lambda x, y: x if x is not None else y duration = fallback(duration, 0) start = fallback(one_hz.slice.start, 0) stop = fallback(one_hz.slice.stop, duration) start_edge = fallback(one_hz.start_edge, 0) stop_edge = fallback(one_hz.stop_edge, duration) slice_ = slice(start, stop) one_hz = Section(one_hz.name, slice_, start_edge, stop_edge) aligned_section[index] = one_hz if not (0 <= start <= duration and 0 <= stop <= duration + 1): msg = "Section '%s' (%.2f, %.2f) not between 0 and %d" raise IndexError(msg % (one_hz.name, start, stop, duration)) if not 0 <= start_edge <= duration: msg = "Section '%s' start_edge (%.2f) not between 0 and %d" raise IndexError(msg % (one_hz.name, start_edge, duration)) if not 0 <= stop_edge <= duration + 1: msg = "Section '%s' stop_edge (%.2f) not between 0 and %d" raise IndexError(msg % (one_hz.name, stop_edge, duration)) section_list.append(one_hz) params[param_name] = aligned_section elif issubclass(node.node_type, DerivedParameterNode): if duration: # check that the right number of results were returned # Allow a small tolerance. For example if duration in seconds # is 2822, then there will be an array length of 1411 at 0.5Hz and 706 # at 0.25Hz (rounded upwards). If we combine two 0.25Hz # parameters then we will have an array length of 1412. expected_length = duration * result.frequency if result.array is None: logger.warning("No array set; creating a fully masked array for %s", param_name) array_length = expected_length # Where a parameter is wholly masked, we fill the HDF # file with masked zeros to maintain structure. result.array = \ np_ma_masked_zeros_like(np.ma.arange(expected_length)) else: array_length = len(result.array) length_diff = array_length - expected_length if length_diff == 0: pass elif 0 < length_diff < 5: logger.warning("Cutting excess data for parameter '%s'. " "Expected length was '%s' while resulting " "array length was '%s'.", param_name, expected_length, len(result.array)) result.array = result.array[:expected_length] else: raise ValueError("Array length mismatch for parameter " "'%s'. Expected '%s', resulting array " "length '%s'." % (param_name, expected_length, array_length)) hdf.set_param(result) # Keep hdf_keys up to date. node_mgr.hdf_keys.append(param_name) elif issubclass(node.node_type, ApproachNode): aligned_approach = result.get_aligned(P(frequency=1, offset=0)) for approach in aligned_approach: # Does not allow slice start or stops to be None. valid_turnoff = (not approach.turnoff or (0 <= approach.turnoff <= duration)) valid_slice = ((0 <= approach.slice.start <= duration) and (0 <= approach.slice.stop <= duration)) valid_gs_est = (not approach.gs_est or ((0 <= approach.gs_est.start <= duration) and (0 <= approach.gs_est.stop <= duration))) valid_loc_est = (not approach.loc_est or ((0 <= approach.loc_est.start <= duration) and (0 <= approach.loc_est.stop <= duration))) if not all([valid_turnoff, valid_slice, valid_gs_est, valid_loc_est]): raise ValueError('ApproachItem contains index outside of ' 'flight data: %s' % approach) approach_list.append(approach) params[param_name] = aligned_approach else: raise NotImplementedError("Unknown Type %s" % node.__class__) continue return kti_list, kpv_list, section_list, approach_list, flight_attrs, params
def derive( self, rad=P('Altitude Radio'), hdot=P('Vertical Speed'), ): def seek_deck(rad, hdot, min_idx, rad_hz): def one_direction(rad, hdot, sence, rad_hz): # Stairway to Heaven is getting a bit old. Getting with the times? # Vertical Speed / 60 = Pressure alt V/S in feet per second b_diffs = hdot / 60 # Rate of change on radalt array = Rad alt V/S in feet per second r_diffs = np.ma.ediff1d(rad * rad_hz, to_begin=b_diffs[0]) # Difference between ROC greater than 6fps will mean flying over # the deck; use pressure alt roc when that happens and radio alt # roc in all other cases diffs = np.ma.where( np.ma.abs(r_diffs - b_diffs) > 6.0 * rad_hz, b_diffs, r_diffs) height = integrate(diffs, frequency=rad_hz, direction=sence, repair=False) return height height_from_rig = np_ma_masked_zeros_like(rad) if len(rad[:min_idx]) > 0: height_from_rig[:min_idx] = one_direction( rad[:min_idx], hdot[:min_idx], "backwards", rad_hz) if len(rad[min_idx:]) > 0: height_from_rig[min_idx:] = one_direction( rad[min_idx:], hdot[min_idx:], "forwards", rad_hz) ''' # And we are bound to want to know the rig height somewhere, so here's how to work that out. rig_height = rad[0]-height_from_rig[0] # I checked this and it seems pretty consistent. # See Library\Projects\Helicopter FDM\Algorithm Development\Rig height estimates from Bond initial test data.xlsx #lat=hdf['Latitude'].array[app_slice][-1] #lon=hdf['Longitude'].array[app_slice][-1] #print(lat, lon, rig_height) ''' return height_from_rig # Prepare a masked array filled with zeros for the parameter (same length as radalt array) self.array = np_ma_masked_zeros_like(rad.array) rad_peak_idxs, rad_peak_vals = cycle_finder(rad.array, min_step=150.0) if len(rad_peak_idxs) < 4: return slice_idxs = list( zip(rad_peak_idxs[:-2], rad_peak_idxs[1:-1], rad_peak_idxs[2:], rad_peak_vals[1:])) for slice_idx in slice_idxs[1:-1]: this_deck_slice = slice(slice_idx[0] + 1, slice_idx[2] - 1) if slice_idx[3] > 5.0: # We didn't land in this period continue else: self.array[this_deck_slice] = seek_deck( rad.array[this_deck_slice], hdot.array[this_deck_slice], slice_idx[1] - slice_idx[0], rad.frequency) '''