Skip to content
Snippets Groups Projects
Calculation.py 13.5 KiB
Newer Older
  • Learn to ignore specific revisions
  • import numpy as np
    from .Ordinate import Ordinate
    
    from .Absolute import Absolute
    
    from .Angle import to_dms
    
    from .MeasurementType import MeasurementType as mt
    
    from pydantic import BaseModel
    
    class Calculate(BaseModel):
    
        """
        Class object for performing calculations.
        Contains the following:
        angle: average angle across a measurement type
        residual: average residual across a measurement type
        hs: Multiplier for inclination claculations. +1 if measurment was taken in northern hemisphere, -1 if measurement was taken in the southern hemishpere.
        ordinate: Variometer data. Ordinate object(contains a datapoint for H, E, Z, and F)
        ud: Multiplier for inclination calculations. +1 if instrument is oriented upward, -1 if instrument if oriented downward.
        shift: Degree shift in inclination measurements.
        """
    
    
        angle: float = None
        residual: float = None
        ordinate: Ordinate = None
        f: float = None
        ud: int = None
        pm: int = None
        shift: int = None
    
    def calculate(reading):
    
        """
        Calculate/recalculate absolute from a Reading object's
        ordinates, measurements, and metadata.
        Outputs a list of absolutes containing baseline, absolute,
        and element name. Also reutrns the scale value.
        """
    
        # gather oridinates, measuremenets, and metadata objects from reading
        metadata = reading.metadata
        ordinates = reading.ordinates
        ordinate_index = reading.ordinate_index()
        measurements = reading.measurements
        measurement_index = reading.measurement_index()
    
        # define measurement types used to calculate inclination
        inclination_types = [mt.NORTH_DOWN, mt.NORTH_UP, mt.SOUTH_DOWN, mt.SOUTH_UP]
        # get ordinate values across h, e, z, and f for inclination measurement types
    
            o for o in ordinates if o.measurement_type in inclination_types
    
        # get average ordinate values across h, e, z, and f
    
        mean = average_ordinate(inclination_ordinates, None)
    
        # calculate inclination
        inclination, f = calculate_I(
    
            measurement_index, inclination_ordinates, ordinate_index, mean, metadata,
    
        h_abs, z_abs = calculate_absolutes(f, inclination)
    
        # calculate baselines for H and Z
    
        h_b, z_b = calculate_baselines(h_abs, z_abs, mean, reading.pier_correction)
    
        # calculate scale value
    
        scale_ordinates = ordinate_index[mt.NORTH_DOWN_SCALE]
        scale_measurements = measurement_index[mt.NORTH_DOWN_SCALE]
    
        scale = calculate_scale(f, scale_ordinates, scale_measurements, inclination,)
    
        # calculate declination absolute and baseline
    
        d_b, d_abs = calculate_D(
            ordinate_index, measurements, measurement_index, metadata["mark_azimuth"], h_b,
        )
    
    
        # return results as a set of Absolute objects along with the calculated scale value
    
        resultH = Absolute(element="H", baseline=h_b, absolute=h_abs)
        resultD = Absolute(element="D", baseline=d_b, absolute=d_abs)
        resultZ = Absolute(element="Z", baseline=z_b, absolute=z_abs)
    
        result = [resultH, resultD, resultZ]
    
        return result, scale
    
    
    
    def calculate_I(measurements, ordinates, ordinates_index, mean, metadata):
    
        """
        Calculate inclination angles from measurements, ordinates,
        average ordinates from every measurement, and metadata.
        Returns inclination angle and calculated average f
        """
    
        # get first inclination angle, assumed to be the southdown angle
    
        Iprime = average_angle(measurements, mt.SOUTH_DOWN)
    
        if Iprime >= 100:
            Iprime -= 200
    
        # get multiplier for hempisphere the observatory is located in
        # 1 if observatory is in northern hemisphere
        # -1 if observatory is in southern hemisphere
        hs = metadata["hemisphere"]
    
        # gather calculation objects for each measurement type
    
            angle=average_angle(measurements, mt.SOUTH_DOWN),
            residual=average_residual(measurements, mt.SOUTH_DOWN),
            ordinate=average_ordinate(ordinates_index, mt.SOUTH_DOWN),
    
            shift=200,
    
            pm=-1,
    
            angle=average_angle(measurements, mt.SOUTH_UP),
            residual=average_residual(measurements, mt.SOUTH_UP),
            ordinate=average_ordinate(ordinates_index, mt.SOUTH_UP),
    
            angle=average_angle(measurements, mt.NORTH_UP),
            residual=average_residual(measurements, mt.NORTH_UP),
            ordinate=average_ordinate(ordinates_index, mt.NORTH_UP),
    
            ud=1,
            pm=-1,
    
            angle=average_angle(measurements, mt.NORTH_DOWN),
            residual=average_residual(measurements, mt.NORTH_DOWN),
            ordinate=average_ordinate(ordinates_index, mt.NORTH_DOWN),
    
        # set inclination value for looping = Iprime
    
        inclination = Iprime
    
        # add one to inclination value to enter the loop
    
        # loop condition
    
        while abs(Inclination - inclination) > 0.0001:
    
            # set temporary inclination variable to hold previous step's inclination
    
            # calculate f for inclination measurement types
    
            southdown.f = calculate_f(southdown.ordinate, mean, inclination)
            northdown.f = calculate_f(northdown.ordinate, mean, inclination)
            southup.f = calculate_f(southup.ordinate, mean, inclination)
            northup.f = calculate_f(northup.ordinate, mean, inclination)
    
            # gather measurements into array(necessary because f were recalculated)
    
            measurements = [southup, southdown, northup, northdown]
    
            # average f values for inclination measurement types
    
            f = np.average([i.f for i in measurements])
    
            # calculation inclination for each inclination measurement type and average
            inclination = np.average(
                [calculate_measurement_inclination(m, hs) for m in measurements]
            )
        # loop exits once the difference in average inclination between steps is lower than 0.0001
    
        return inclination, f
    
    def calculate_D(ordinates_index, measurements, measurements_index, azimuth, h_b):
    
        """
        Calculate declination absolute and declination baseline from
        ordinates, measurements, measurement_index(dictionary), azimuth and H baseline
        Returns absolute and baseline for declination.
        """
    
        # specify mark measurement types
        mark_types = [
            mt.FIRST_MARK_DOWN,
            mt.FIRST_MARK_UP,
            mt.SECOND_MARK_DOWN,
            mt.SECOND_MARK_UP,
        ]
        # average mark angles
        # note that angles are being converted to geon
    
            [
                convert_to_geon(m.angle)
                for m in measurements
    
                if m.measurement_type in mark_types
    
        # add 100 if mark up is greater than mark down
        # subtract 100 otherwise
    
        if (
            measurements_index["FirstMarkUp"][0].angle
            < measurements_index["FirstMarkDown"][0].angle
        ):
            average_mark += 100
        else:
            average_mark -= 100
    
    
        # gather calculation objects for each declination measurement type
        # note that the pm(plus minus) multiplier has been repurposed.
        # West facing measurements have a multiplier of -1
        # East facing measurements have a multipllier of 1
    
            angle=average_angle(measurements_index, mt.WEST_DOWN),
            residual=average_residual(measurements_index, mt.WEST_DOWN),
            ordinate=average_ordinate(ordinates_index, mt.WEST_DOWN),
    
            pm=-1,
    
            angle=average_angle(measurements_index, mt.WEST_UP),
            residual=average_residual(measurements_index, mt.WEST_UP),
            ordinate=average_ordinate(ordinates_index, mt.WEST_UP),
    
            pm=-1,
    
            angle=average_angle(measurements_index, mt.EAST_DOWN),
            residual=average_residual(measurements_index, mt.EAST_DOWN),
            ordinate=average_ordinate(ordinates_index, mt.EAST_DOWN),
    
            angle=average_angle(measurements_index, mt.EAST_UP),
            residual=average_residual(measurements_index, mt.EAST_UP),
            ordinate=average_ordinate(ordinates_index, mt.EAST_UP),
    
        # convert azimuth to geon
    
        azimuth = (int(azimuth / 100) + (azimuth % 100) / 60) / 0.9
    
        # gather declination measurements into array
    
        measurements = [westdown, westup, eastdown, eastup]
    
        # average meridian terms calculated from each declination measurements
    
        meridian = np.average([calculate_meridian_term(i, h_b) for i in measurements])
    
        # add subtract average mark angle from average meridian angle and add
    
        # azimuth(in geon) to get the declination baseline
    
        D_b = (meridian - average_mark) + azimuth
    
        # convert decimal baseline into dms baseline
    
        d_b = round(D_b * 54, 2)
        d_b_dms = int(d_b / 60) * 100 + ((d_b / 60) % 1) * 60
    
        # gather first declination measurements' H ans E ordinates
    
        wd_E_1 = ordinates_index[mt.WEST_DOWN][0].e
        wd_H_1 = ordinates_index[mt.WEST_DOWN][0].h
    
        # calculate Declination baseline
    
        d_abs = D_b + np.arctan(wd_E_1 / (h_b + wd_H_1)) * (200 / np.pi)
        d_abs = round(d_abs * 54, 1)
    
        # convert decimal absolute into dms absolute
    
        d_abs_dms = int(d_abs / 60) * 100 + ((d_abs / 60) % 1) * 60
    
        return d_b_dms, d_abs_dms
    
    def calculate_absolutes(f, inclination):
    
        """
        Calculate absolutes for H, Z and F from computed
    
        average f value(from inclination computations) and
        calculated inclination angle.
        Returns baselines for H and Z
    
        # convert inclination to radians
    
        i = (np.pi / 200) * (inclination)
    
        h_abs = f * np.cos(i)
        z_abs = f * np.sin(i)
    
        return h_abs, z_abs
    
    def calculate_baselines(h_abs, z_abs, mean, pier_correction):
    
        Calculate baselines with H and Z absolutes,
        average ordinates across all measurements,
        and pier correction(metadata).
    
        correction = pier_correction / 5
    
        h_b = round(np.sqrt(h_abs ** 2 - mean.e ** 2) - mean.h, 1) - correction
        z_b = round(z_abs - mean.z, 1) - correction
    
        return h_b, z_b
    
    def calculate_scale(f, ordinates, measurements, inclination):
    
        """
        Calculate scale value from calulated f(from inclination computations),
    
        inclination, and the measurements/ordinates taken for scaling purposes.
        Such measurements usually present themselves as a set of three North Down
        measurements, where the final two measuremets(and ordinates) are used for scaling.
    
        first_ord = ordinates[0]
        second_ord = ordinates[1]
        first_measurement = measurements[0]
        second_measurement = measurements[1]
    
        field_change = (
            (
                -np.sin(inclination * np.pi / 200) * (second_ord.h - first_ord.h) / f
                + np.cos(inclination * np.pi / 200) * (second_ord.z - first_ord.z) / f
            )
            * 200
            / np.pi
        )
    
        field_change += 0.1852
    
        residual_change = abs(second_measurement.residual - first_measurement.residual)
    
        scale_value = (f * field_change / residual_change) * np.pi / 200
    
    
        return scale_value
    
    
    def average_angle(measurements, type):
    
        """
        Compute average angle from a dictionary of
        measurements and specified measurement type.
        """
    
        return np.average([convert_to_geon(m.angle) for m in measurements[type]])
    
        """
        Compute average residual from a dictionary
        of measurements and specified measurement type.
        """
    
        return np.average([m.residual for m in measurements[type]])
    
    Cain, Payton David's avatar
    Cain, Payton David committed
    def average_ordinate(ordinates, type):
    
        """
        Compute average ordinate from a dictionary
        of ordinates and specified measurement type.
        """
    
    Cain, Payton David's avatar
    Cain, Payton David committed
            ordinates = ordinates[type]
    
        o = Ordinate(measurement_type=type)
    
    Cain, Payton David's avatar
    Cain, Payton David committed
        avgs = np.average([[o.h, o.e, o.z, o.f] for o in ordinates], axis=0,)
    
        o.h, o.e, o.z, o.f = avgs
        return o
    
    def calculate_f(ordinate, mean, inclination):
    
        """
        calculate f for a measurement type using a measurement's
        average ordinates, average ordinate across all measurements,
        and calculated inclination.
        """
    
        # get channel means form all ordinates
        # calculate f using current step's inclination angle
        f = (
    
            + (ordinate.h - mean.h) * np.cos(inclination * np.pi / 200)
            + (ordinate.z - mean.z) * np.sin(inclination * np.pi / 200)
            + ((ordinate.e) ** 2 - (mean.e) ** 2) / (2 * mean.f)
    
    def calculate_measurement_inclination(calculation, hs):
    
        """
        Calculate a measurement's inclination value using
        Calculate items' elements.
        """
    
        return calculation.shift + calculation.pm * (
            +calculation.angle
            + calculation.ud
            * (hs * np.arcsin(calculation.residual / calculation.f) * 200 / np.pi)
    
    def calculate_meridian_term(calculation, h_b):
    
        """
        Calculate meridian value from a measurement type
        using a Calculate object and H's baseline value.
        """
    
    Cain, Payton David's avatar
    Cain, Payton David committed
        A1 = np.arcsin(
    
            / np.sqrt((calculation.ordinate.h + h_b) ** 2 + (calculation.ordinate.e) ** 2)
    
        A2 = np.arctan(calculation.ordinate.e / (calculation.ordinate.h + h_b))
    
        A1 = (200 / np.pi) * (A1)
        A2 = (200 / np.pi) * (A2)
    
        meridian_term = calculation.angle + (calculation.pm * A1) - A2
    
    def convert_to_geon(angle, include_seconds=True):
    
        """
        Convert angles from measurements to geon
        """
    
        degrees = int(angle)
        minutes = int((angle % 1) * 100) / 60
    
        if include_seconds:
            seconds = ((angle * 100) % 1) / 36
        else:
            seconds = 0
    
        dms = (degrees + minutes + seconds) / 0.9