Skip to content
Snippets Groups Projects
Calculation.py 10.63 KiB
import numpy as np
from .Ordinate import Ordinate


class Calculate(object):
    """
    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.
    """

    def __init__(
        self,
        angle: float = None,
        residual: float = None,
        ordinate: Ordinate = None,
        hs: int = None,
        ud: int = None,
        shift: int = None,
    ):
        self.angle = angle
        self.residual = residual
        self.ordinate = ordinate
        self.hs = hs
        self.shift = shift


def calculate_I(measurements, ordinates, ordinates_index, total_ordinate, 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 southdown
    Iprime = average_angle(measurements, "SouthDown")
    if Iprime >= 90:
        Iprime -= 180
    Iprime = np.deg2rad(Iprime)
    # 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
    southdown = process_type(
        shift=-180,
        inclination=Iprime,
        ud=-1,
        measurements=measurements,
        ordinates=ordinates_index,
        total_ordinate=total_ordinate,
        type="SouthDown",
        hs=hs,
    )
    southup = process_type(
        shift=180,
        inclination=Iprime,
        ud=1,
        measurements=measurements,
        ordinates=ordinates_index,
        total_ordinate=total_ordinate,
        type="SouthUp",
        hs=hs,
    )
    northup = process_type(
        shift=0,
        inclination=Iprime,
        ud=1,
        measurements=measurements,
        ordinates=ordinates_index,
        total_ordinate=total_ordinate,
        type="NorthUp",
        hs=hs,
    )
    northdown = process_type(
        shift=0,
        inclination=Iprime,
        ud=-1,
        measurements=measurements,
        ordinates=ordinates_index,
        total_ordinate=total_ordinate,
        type="NorthDown",
        hs=hs,
    )
    # gather measurements into array
    measurements = [southdown, southup, northdown, northup]
    # Get average inclination from measurments
    inclination = np.average(
        [calculate_measurement_inclination(i, total_ordinate.f) for i in measurements]
    )
    # iterate calculations until the difference in the resultant inclination angle is less than 0.0001
    # intialize inclination for current step to be outside threshold specified in condition
    Inclination = inclination + 1
    while abs(inclination - Inclination) >= 0.0001:
        # establish the inclination angle as the previously calculated average inclination angle
        Inclination = inclination
        # calculate average f component from each measurement
        f_avg = np.average(
            [calculate_f(i, total_ordinate, Inclination) for i in ordinates]
        )
        # update ordinate's f value as the average f component
        # used in next iterations f_avg calculation
        total_ordinate.f = f_avg
        # re-calculate inclination for measurement types
        inclination = np.average(
            [
                calculate_measurement_inclination(i, total_ordinate.f)
                for i in measurements
            ]
        )
        # transfer inclination angle from degrees to radians
        inclination = np.deg2rad(inclination)

    return Inclination, total_ordinate.f


def calculate_D(ordinates, measurements, measurements_index, AZ, Hb):
    """
    Calculate declination absolute and declination baseline from
    ordinates, measurements, measurement_index(dictionary), azimuth and H baseline
    Returns absolute and baseline for declination.
    """
    # compute average angle from marks
    average_mark = np.average(
        [m.angle for m in measurements if "mark" in m.measurement_type.capitalize()]
    )
    # gather calculation objects for each measurement type
    southdown = process_type(
        measurements=measurements_index,
        ordinates=ordinates,
        baseline=Hb,
        type="SouthDown",
    )
    southup = process_type(
        measurements=measurements_index,
        ordinates=ordinates,
        baseline=Hb,
        type="SouthUp",
    )
    northdown = process_type(
        measurements=measurements_index,
        ordinates=ordinates,
        baseline=Hb,
        type="NorthDown",
    )
    northup = process_type(
        measurements=measurements_index,
        ordinates=ordinates,
        baseline=Hb,
        type="NorthUp",
    )
    # gather measurements into array
    measurements = [southdown, southup, northup, northdown]
    # get average meridian angle from measurement types
    meridian = np.average([calculate_meridian_term(i) for i in measurements])
    # add average mark, meridian, and azimuth angle to get declination baseline
    Db = (average_mark + meridian + AZ) * 60
    # calculate declination absolute
    Dabs = Db + np.arctan(southdown.ordinate.e / (Hb + southdown.ordinate.h)) * (
        10800 / np.pi
    )

    return Db, Dabs


def calculate_absolutes(f, inclination, pier_correction):
    """
    Calculate absolutes for H, Z and F from computed
    average f value(from inclination computations),
    calculated inclination angle, and pier correction(metadata).
    Returns baselines for H, Z, and F
    """
    i = np.deg2rad(inclination)
    Fabs = f + pier_correction
    Habs = Fabs * np.cos(i)
    Zabs = Fabs * np.sin(i)

    return Habs, Zabs, Fabs


def calculate_baselines(Habs, Zabs, total_ordinate):
    """
    Calculate baselines with H and Z absolutes, and
    average ordinates across all measurements.
    Returns H and Z baselines
    """
    h_mean = total_ordinate.h
    e_mean = total_ordinate.e
    z_mean = total_ordinate.z

    Hb = np.sqrt(Habs ** 2 - e_mean ** 2) - h_mean
    Zb = Zabs - z_mean

    return Hb, Zb


def calculate_scale(f, measurements, inclination, pier_correction):
    """
    Calculate scale value from calulated f(from inclination computations),
    calculated inclination, and pier correction(metadata)
    """
    i = np.deg2rad(inclination)
    measurements = measurements[-2:]
    angle_diff = (np.diff([m.angle for m in measurements]) / f)[0]
    A = np.cos(i) * angle_diff
    B = np.sin(i) * angle_diff
    delta_f = np.rad2deg(A - B)
    detla_r = abs(np.diff([m.residual for m in measurements]))[0]
    time_delta = np.diff([m.time for m in measurements])[0]
    delta_b = delta_f + (time_delta / 60.0)
    scale_value = f * np.deg2rad(delta_b / detla_r)

    return scale_value


def average_angle(measurements, type):
    """
    Compute average angle from a dictionary of
    measurements and specified measurement type.
    """
    if type == "NorthDown":
        # exclude final measurement, which is only used for scaling
        measurements = measurements[type][:-1]
    else:
        measurements = measurements[type]
    return np.average([m.angle for m in measurements])


def average_residual(measurements, type):
    """
    Compute average residual from a dictionary
    of measurements and specified measurement type.
    """
    if type == "NorthDown":
        # exclude final measurement, which is only used for scaling
        measurements = measurements[type][:-1]
    else:
        measurements = measurements[type]
    return np.average([m.residual for m in measurements])


def average_ordinate(ordinates, type):
    """
    Compute average ordinate from a dictionary
    of ordinates and specified measurement type.
    """
    if type == "NorthDown":
        # exclude final measurement, which is only used for scaling
        ordinates = ordinates[type][:-1]
    elif type is not None:
        ordinates = ordinates[type]
    elif type is None:
        ordinates = ordinates[:-1]
    o = Ordinate(measurement_type=type)
    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, total_ordinate, 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
    h_mean = total_ordinate.h
    e_mean = total_ordinate.e
    z_mean = total_ordinate.z
    f_mean = total_ordinate.f
    # get individual ordinate means
    h = ordinate.h
    e = ordinate.e
    z = ordinate.z
    # calculate f using current step's inclination angle
    f = (
        f_mean
        + (h - h_mean) * np.cos(inclination)
        + (z - z_mean) * np.sin(inclination)
        + ((e) ** 2 - (e_mean) ** 2) / (2 * f_mean)
    )
    return f


def calculate_measurement_inclination(calculation, f):
    """
    Calculate a measurement's inclination value using
    Calculate items' elements.
    """
    shift = calculation.shift
    angle = calculation.angle
    ud = calculation.ud
    hs = calculation.hs
    r = calculation.residual
    return shift + angle + ud * np.rad2deg(hs * np.sin(r / f))


def process_type(
    measurements,
    ordinates,
    type,
    total_ordinate=None,
    shift=None,
    ud=None,
    inclination=None,
    baseline=None,
    hs=None,
):
    """
    Create a Calculation object for each
    measurement within a measurement type.
    """
    c = Calculate()
    c.angle = average_angle(measurements, type)
    c.residual = average_residual(measurements, type)
    c.ordinate = average_ordinate(ordinates, type)
    c.hs = hs
    c.ud = ud
    c.shift = shift
    c.baseline = baseline

    return c


def calculate_meridian_term(calculation):
    """
    Calculate meridian value from a measurement type
    using a Calculate object and H's baseline value.
    """
    A1 = np.arcsin(
        calculation.residual
        / np.sqrt(
            (calculation.ordinate.h + calculation.baseline) ** 2
            + (calculation.ordinate.e) ** 2
        )
    )
    A2 = np.arctan(
        calculation.ordinate.e / (calculation.ordinate.h + calculation.baseline)
    )
    A1 = np.rad2deg(A1)
    A2 = np.rad2deg(A2)
    meridian_term = calculation.angle - A1 - A2
    return meridian_term