Example #1
0
    def test_nose_down_multiple_climbs(self):
        node = NoseDownAttitudeAdoption()
        pitch = np.concatenate([np.ones(15) * 2, np.linspace(2, -11, num=15),
                                np.linspace(-11, 2, num=10),
                                np.ones(20) * 2, np.linspace(2, -11, num=15),
                                np.ones(10) * -11])
        climbs = buildsections('Initial Climb', [10, 40], [60, 85])
        node.derive(P('Pitch', pitch), climbs)

        self.assertEqual(len(node), 2)
        self.assertEqual(node[0], Section('Nose Down Attitude Adoption', slice(15, 28, None), 15, 28))
        self.assertEqual(node[1], Section('Nose Down Attitude Adoption', slice(60, 73, None), 60, 73))
Example #2
0
def buildsection(name, begin, end):
    '''from FlightDataAnalyzer tests
       A little routine to make building Sections for testing easier.
       Example: land = buildsection('Landing', 100, 120)
    '''
    result = Section(name, slice(begin, end, None), begin, end)
    return SectionNode(name, items=[result])
Example #3
0
    def setUp(self):
        self.node_class = ApproachInformation
        self.alt_aal = P(name='Altitude AAL', array=np.ma.array([
            10, 5, 0, 0, 5, 10, 20, 30, 40, 50,      # Touch & Go
            50, 45, 30, 35, 30, 30, 35, 40, 40, 40,  # Go Around
            30, 20, 10, 0, 0, 0, 0, 0, 0, 0,         # Landing
        ]))
        self.app = ApproachAndLanding()
        self.fast = S(name='Fast', items=[
            Section(name='Fast', slice=slice(0, 22), start_edge=0,
                    stop_edge=22.5),
        ])

        self.land_hdg = KPV(name='Heading During Landing', items=[
            KeyPointValue(index=22, value=60),
        ])
        self.land_lat = KPV(name='Latitude At Touchdown', items=[
            KeyPointValue(index=22, value=10),
        ])
        self.land_lon = KPV(name='Longitude At Touchdown', items=[
            KeyPointValue(index=22, value=-2),
        ])
        self.appr_hdg = KPV(name='Heading At Lowest Altitude During Approach', items=[
            KeyPointValue(index=5, value=25),
            KeyPointValue(index=12, value=35),
        ])
        self.appr_lat = KPV(name='Latitude At Lowest Altitude During Approach', items=[
            KeyPointValue(index=5, value=8),
        ])
        self.appr_lon = KPV(name='Longitude At Lowest Altitude During Approach', items=[
            KeyPointValue(index=5, value=4),
        ])
        self.land_afr_apt_none = A(name='AFR Landing Airport', value=None)
        self.land_afr_rwy_none = A(name='AFR Landing Runway', value=None)
def align_section(result, duration, section_list):
    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)
    return aligned_section, section_list
Example #5
0
    def test_nose_down_insufficient_pitch(self):
        node = NoseDownAttitudeAdoption()
        pitch = np.concatenate([np.ones(15) * 2, np.linspace(2, -6, num=15), np.ones(10) * -6])

        node.derive(P('Pitch', pitch), self.climbs)

        self.assertEqual(len(node), 1)
        self.assertEqual(node[0], Section('Nose Down Attitude Adoption', slice(15, 29, None), 15, 29))
Example #6
0
def buildsections(*args):
    '''from FlightDataAnalyzer tests
       Example: approach = buildsections('Approach', [80,90], [100,110])
    '''
    built_list = []
    name = args[0]
    for a in args[1:]:
        begin, end = a[0], a[1]
        new_section = Section(name, slice(begin, end, None), begin, end)
        built_list.append(new_section)
    return SectionNode(name, items=built_list)
    def test_nose_down_basic(self):
        node = NoseDownAttitudeAdoption()
        pitch = np.concatenate(
            [np.ones(15) * 2,
             np.linspace(2, -11, num=15),
             np.ones(10) * -11])

        node.derive(P('Pitch', pitch), self.climbs, self.offshore)

        self.assertEqual(len(node), 1)
        self.assertEqual(
            node[0],
            Section('Nose Down Attitude Adoption', slice(15, 28, None), 15,
                    28))
Example #8
0
    def test__controls_in_use(self):
        pitch_capt = Mock()
        pitch_fo = Mock()
        roll_capt = Mock()
        roll_fo = Mock()

        section = Section('Takeoff', slice(0, 3), 0, 3)

        # Note: We instantiate one of the subclasses of DeterminePilot as we
        #       use logging methods not defined in this abstract superclass.
        determine_pilot = LandingPilot()
        determine_pilot._controls_changed = Mock()

        # Neither pilot's controls changed:
        determine_pilot._controls_changed.reset_mock()
        determine_pilot._controls_changed.side_effect = [False, False]
        pilot = determine_pilot._controls_in_use(pitch_capt, pitch_fo, roll_capt, roll_fo, section)
        determine_pilot._controls_changed.assert_has_calls([
            call(section.slice, pitch_capt, roll_capt),
            call(section.slice, pitch_fo, roll_fo),
        ])
        self.assertEqual(pilot, None)
         # Only captain's controls changed:
        determine_pilot._controls_changed.reset_mock()
        determine_pilot._controls_changed.side_effect = [True, False]
        pilot = determine_pilot._controls_in_use(pitch_capt, pitch_fo, roll_capt, roll_fo, section)
        determine_pilot._controls_changed.assert_has_calls([
            call(section.slice, pitch_capt, roll_capt),
            call(section.slice, pitch_fo, roll_fo),
        ])
        self.assertEqual(pilot, 'Captain')
        # Only first Officer's controls changed:
        determine_pilot._controls_changed.reset_mock()
        determine_pilot._controls_changed.side_effect = [False, True]
        pilot = determine_pilot._controls_in_use(pitch_capt, pitch_fo, roll_capt, roll_fo, section)
        determine_pilot._controls_changed.assert_has_calls([
            call(section.slice, pitch_capt, roll_capt),
            call(section.slice, pitch_fo, roll_fo),
        ])
        self.assertEqual(pilot, 'First Officer')
        # Both pilot's controls changed:
        determine_pilot._controls_changed.reset_mock()
        determine_pilot._controls_changed.side_effect = [True, True]
        pilot = determine_pilot._controls_in_use(pitch_capt, pitch_fo, roll_capt, roll_fo, section)
        determine_pilot._controls_changed.assert_has_calls([
            call(section.slice, pitch_capt, roll_capt),
            call(section.slice, pitch_fo, roll_fo),
        ])
        self.assertEqual(pilot, None)
def builditem(name, begin, end, start_edge=None, stop_edge=None):
    '''
    This code more accurately represents the aligned section values, but is
    not suitable for test cases where the data does not get aligned.

    if begin is None:
        ib = None
    else:
        ib = int(begin)
        if ib < begin:
            ib += 1
    if end is None:
        ie = None
    else:
        ie = int(end)
        if ie < end:
            ie += 1

    :param begin: index at start of section
    :param end: index at end of section
    '''
    slice_end = end if end is None else end + 1
    return Section(name, slice(begin, slice_end, None), start_edge or begin,
                   stop_edge or end)
Example #10
0
def derive_parameters(hdf, node_mgr, process_order, params=None, force=False):
    '''
    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
    '''
    if not params:
        params = {}
    # OPT: local lookup is faster than module-level (small).
    node_subclasses = NODE_SUBCLASSES

    # store all derived params that aren't masked arrays
    approaches = {}
    # duplicate storage, but maintaining types
    kpvs = {}
    ktis = {}
    # 'Node Name' : node()  pass in node.get_accessor()
    sections = {}
    flight_attrs = {}
    # cache of nodes to avoid repeated array alignment
    cache = {} if NODE_CACHE else None
    duration = hdf.duration

    for param_name in process_order:
        if param_name in node_mgr.hdf_keys:
            continue

        elif param_name in params:
            node = params[param_name]
            # populate output already at 1Hz
            if node.node_type is KeyPointValueNode:
                kpvs[param_name] = list(node)
            elif node.node_type is KeyTimeInstanceNode:
                ktis[param_name] = list(node)
            elif node.node_type is FlightAttributeNode:
                flight_attrs[param_name] = [Attribute(node.name, node.value)]
            elif node.node_type is SectionNode:
                sections[param_name] = list(node)
            # DerivedParameterNodes are not supported in initial data.
            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),
                                                cache=cache)
                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(cache=cache)
        # shhh, secret accessors for developing nodes in debug mode
        node._p = params
        node._h = hdf
        node._n = node_mgr
        logger.debug("Processing %s `%s`",
                     get_node_type(node, node_subclasses), param_name)
        # Derive the resulting value

        try:
            node = node.get_derived(deps)
        except:
            if not force:
                raise

        del node._p
        del node._h
        del node._n

        if node.node_type is KeyPointValueNode:
            params[param_name] = node

            aligned_kpvs = []
            for one_hz in node.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))
                aligned_kpvs.append(one_hz)
            kpvs[param_name] = aligned_kpvs
        elif node.node_type is KeyTimeInstanceNode:
            params[param_name] = node

            aligned_ktis = []
            for one_hz in node.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))
                aligned_ktis.append(one_hz)
            ktis[param_name] = aligned_ktis
        elif node.node_type is FlightAttributeNode:
            params[param_name] = node
            try:
                # only has one Attribute node, store as a list for consistency
                flight_attrs[param_name] = [Attribute(node.name, node.value)]
            except:
                logger.warning(
                    "Flight Attribute Node '%s' returned empty "
                    "handed.", param_name)
        elif issubclass(node.node_type, SectionNode):
            aligned_section = node.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
            sections[param_name] = list(aligned_section)
        elif issubclass(node.node_type, DerivedParameterNode):
            if duration:
                # check that the right number of nodes 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 * node.frequency
                if node.array is None or (force and len(node.array) == 0):
                    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.
                    node.array = \
                        np_ma_masked_zeros(expected_length)
                else:
                    array_length = len(node.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(node.array))
                    node.array = node.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(node)
            # Keep hdf_keys up to date.
            node_mgr.hdf_keys.append(param_name)
        elif issubclass(node.node_type, ApproachNode):
            aligned_approach = node.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)
            params[param_name] = aligned_approach
            approaches[param_name] = list(aligned_approach)
        else:
            raise NotImplementedError("Unknown Type %s" % node.__class__)
        continue
    return ktis, kpvs, sections, approaches, flight_attrs