Example #1
0
def pdn(r1, r2, r3, r4, angle, mode, rid, delta=0.001):
    """
    Calculate partial derivatives numerically by perturbation.

    Parameters
    ----------
    r1 : float
        Vp2 / Vp1
    r2 : float
        Vs1 / Vp1
    r3 : float
        Vs2 / Vp1
    r4 : float
        Ro2 / Ro1
    angle : float
        incident angle in degrees
    mode : str
        'PP' or 'PS'
    rid : int
        1-4 corresponds to r1, r2, r3, r4
    delta : float
        perturbation amount

    Returns
    -------
    grad : float
        gradient or partial derivative w.r.t. r1
    """
    a1, a2, gra = 0, 0, 0
    if mode is 'PP':
        a1, _ = rpp_cer1977(r1, r2, r3, r4, angle)
        if rid == 1:
            r1 += delta
        elif rid == 2:
            r2 += delta
        elif rid == 3:
            r3 += delta
        elif rid == 4:
            r4 += delta
        else:
            raise ValueError("Illegal ratio index")
        a2, _ = rpp_cer1977(r1, r2, r3, r4, angle)
        gra = (a2 - a1) / delta
    elif mode is 'PS':
        a1, _ = rps_cer1977(r1, r2, r3, r4, angle, amp_type='abs')
        if rid == 1:
            r1 += delta
        elif rid == 2:
            r2 += delta
        elif rid == 3:
            r3 += delta
        elif rid == 4:
            r4 += delta
        else:
            raise ValueError("Illegal ratio index")
        a2, _ = rps_cer1977(r1, r2, r3, r4, angle, amp_type='abs')
        gra = (a2 - a1) / delta
    else:
        raise ValueError("Unsupported wave mode")
    return gra
Example #2
0
    def test_pp_clean(self):
        # Two half spaces elastic model
        # vp1, vp2 = 3.0, 2.0
        # vs1, vs2 = 1.5, 1.0
        # ro1, ro2 = 2.3, 2.0
        vp1, vp2 = 4.0, 2.0
        vs1, vs2 = 2.0, 1.0
        ro1, ro2 = 2.4, 2.0

        # Change parameterization
        r1, r2, r3, r4 = elapar_hs2ratio(vp1, vs1, ro1, vp2, vs2, ro2)

        # Define angles
        angles = np.arange(1, 60, 6)

        # Calculate the reflection amplitude or b in Ax=b
        m = len(angles)
        rpp = np.zeros(m)
        rps = np.zeros(m)
        for i in range(m):
            angle = angles[i]
            amp, pha = rpp_cer1977(r1, r2, r3, r4, angle)
            rpp[i] = amp
            amp, pha = rps_cer1977(r1, r2, r3, r4, angle, amp_type='abs')
            rps[i] = amp

        # print("Target model:", r1, r2, r3, r4)
        r1_ini = 2.4 / 4.0
        r2_ini = 2.2 / 4.0
        r3_ini = 1.3 / 4.0
        r4_ini = 1.6 / 2.4
        x_ini = (r1_ini, r2_ini, r3_ini, r4_ini)
        # print("Initial model:", x_ini)

        for i in range(5):
            x_new = cer1itr(angles, rpp, x_ini, rps=rps)
            # print("Updated", x_new)
            x_ini = x_new

        self.assertLessEqual(r1 - x_new[0], 0.001)
        self.assertLessEqual(r2 - x_new[1], 0.001)
        self.assertLessEqual(r3 - x_new[2], 0.001)
        self.assertLessEqual(r4 - x_new[3], 0.001)
Example #3
0
    def test_2(self):
        # Two half spaces elastic model
        vp1, vp2 = 5.72, 2.87
        vs1, vs2 = 2.93, 1.61
        ro1, ro2 = 2.86, 2.14

        # Change parameterization
        r1, r2, r3, r4 = elapar_hs2ratio(vp1, vs1, ro1, vp2, vs2, ro2)

        # Define angles
        # angles = np.arange(0, 90, 1)
        # for angle in angles:
        #     amp, pha = rps_cer1977(r1, r2, r3, r4, angle, amp_type='abs')
        #     amp, pha = rps_cer1977(r1, r2, r3, r4, angle, amp_type='real')
        #     print("ang,amp,pha =", angle, amp, pha)

        angle = 10
        amp, pha = rps_cer1977(r1, r2, r3, r4, angle, amp_type='real')
        amp_truth = -0.14823324
        err = amp_truth - amp
        self.assertLessEqual(err, 0.001)
Example #4
0
    def test_idm_functionality(self):
        # Functionality-based IDM

        # r1 physical non negative, blocks: (0,1), >=1
        r1s = [0.9, 1.1]
        angles = [-1, 0, 30, 100]  # blocks: <0, =0, (0,90), >=90

        # base choice
        r1, r2, r3, r4, angle, amp_type = 0.9, 0.5, 0.5, 0.9, 30, 'real'

        r1, angle = 0.9, -1
        with self.assertRaises(ValueError):
            a, p = rpp_cer1977(r1, r2, r3, r4, angle, amp_type=amp_type)
        with self.assertRaises(ValueError):
            a, p = rps_cer1977(r1, r2, r3, r4, angle, amp_type=amp_type)

        r1, angle = 0.9, 0
        a, p = rpp_cer1977(r1, r2, r3, r4, angle, amp_type=amp_type)
        self.assertLessEqual(-0.104972376 - a, 0.001)
        self.assertEqual(p, 180)
        a, p = rps_cer1977(r1, r2, r3, r4, angle, amp_type=amp_type)
        self.assertEqual(a, 0)
        self.assertEqual(p, 0)

        r1, angle = 0.9, 30
        a, p = rpp_cer1977(r1, r2, r3, r4, angle, amp_type=amp_type)
        self.assertLessEqual(-0.10705785 - a, 0.001)
        self.assertEqual(p, 180)
        a, p = rps_cer1977(r1, r2, r3, r4, angle, amp_type=amp_type)
        self.assertLessEqual(-0.04646549 - a, 0.001)
        self.assertEqual(p, 180)

        r1, angle = 0.9, 100
        with self.assertRaises(ValueError):
            a, p = rpp_cer1977(r1, r2, r3, r4, angle, amp_type=amp_type)
        with self.assertRaises(ValueError):
            a, p = rps_cer1977(r1, r2, r3, r4, angle, amp_type=amp_type)

        r1, angle = 1.1, -1
        with self.assertRaises(ValueError):
            a, p = rpp_cer1977(r1, r2, r3, r4, angle, amp_type=amp_type)
        with self.assertRaises(ValueError):
            a, p = rps_cer1977(r1, r2, r3, r4, angle, amp_type=amp_type)

        r1, angle = 1.1, 0
        a, p = rpp_cer1977(r1, r2, r3, r4, angle, amp_type=amp_type)
        self.assertLessEqual(-0.0050251256 - a, 0.001)
        self.assertEqual(p, 180)
        a, p = rps_cer1977(r1, r2, r3, r4, angle, amp_type=amp_type)
        self.assertEqual(a, 0)
        self.assertEqual(p, 0)

        r1, angle = 1.1, 30
        a, p = rpp_cer1977(r1, r2, r3, r4, angle, amp_type=amp_type)
        self.assertLessEqual(0.026191689 - a, 0.001)
        self.assertEqual(p, 0)
        a, p = rps_cer1977(r1, r2, r3, r4, angle, amp_type=amp_type)
        self.assertLessEqual(-0.04659759 - a, 0.001)
        self.assertEqual(p, 180)

        r1, angle = 1.1, 100
        with self.assertRaises(ValueError):
            a, p = rpp_cer1977(r1, r2, r3, r4, angle, amp_type=amp_type)
        with self.assertRaises(ValueError):
            a, p = rps_cer1977(r1, r2, r3, r4, angle, amp_type=amp_type)

        # Add tests to increase coverage

        r1, r2, r3, r4, angle, amp_type = -1, 0.5, 0.5, 0.9, 30, 'real'
        with self.assertRaises(ValueError):
            a, p = rpp_cer1977(r1, r2, r3, r4, angle, amp_type=amp_type)

        r1, r2, r3, r4, angle, amp_type = 0.9, 0.8, 0.5, 0.9, 30, 'real'
        with self.assertRaises(ValueError):
            a, p = rpp_cer1977(r1, r2, r3, r4, angle, amp_type=amp_type)

        r1, r2, r3, r4, angle, amp_type = 0.9, 0.5, 0.8, 0.9, 30, 'real'
        with self.assertRaises(ValueError):
            a, p = rpp_cer1977(r1, r2, r3, r4, angle, amp_type=amp_type)

        r1, r2, r3, r4, angle, amp_type = 0.9, 0.5, 0.5, -0.9, 30, 'real'
        with self.assertRaises(ValueError):
            a, p = rpp_cer1977(r1, r2, r3, r4, angle, amp_type=amp_type)

        r1, r2, r3, r4, angle, amp_type = 0.9, 0.5, 0.5, 0.9, 0, 'abs'
        a, p = rpp_cer1977(r1, r2, r3, r4, angle, amp_type=amp_type)

        r1, r2, r3, r4, angle, amp_type = 0.9, 0.5, 0.5, 0.9, 0, 'else'
        with self.assertRaises(ValueError):
            a, p = rpp_cer1977(r1, r2, r3, r4, angle, amp_type=amp_type)

        r1, r2, r3, r4, angle, amp_type = 0.9, 0.5, 0.5, 0.9, 30, 'abs'
        a, p = rpp_cer1977(r1, r2, r3, r4, angle, amp_type=amp_type)
        a, p = rps_cer1977(r1, r2, r3, r4, angle, amp_type=amp_type)

        r1, r2, r3, r4, angle, amp_type = 0.9, 0.5, 0.5, 0.9, 30, 'else'
        with self.assertRaises(ValueError):
            a, p = rpp_cer1977(r1, r2, r3, r4, angle, amp_type=amp_type)
        with self.assertRaises(ValueError):
            a, p = rps_cer1977(r1, r2, r3, r4, angle, amp_type=amp_type)

        # post-critical angle
        r1, r2, r3, r4, angle, amp_type = 2, 0.5, 0.9, 1.2, 45, 'real'
        a, p = rpp_cer1977(r1, r2, r3, r4, angle, amp_type=amp_type)

        # Add tests of geophysical interest

        # No contrast: r1=1, r2=r3, r4=1
        r1, r2, r3, r4, angle, amp_type = 1, 0.5, 0.5, 1, 0, 'real'
        a, p = rpp_cer1977(r1, r2, r3, r4, angle, amp_type=amp_type)
        self.assertEqual(a, 0)
        self.assertEqual(p, 0)
        a, p = rps_cer1977(r1, r2, r3, r4, angle, amp_type=amp_type)
        self.assertEqual(a, 0)
        self.assertEqual(p, 0)
        r1, r2, r3, r4, angle, amp_type = 1, 0.5, 0.5, 1, 30, 'real'
        a, p = rpp_cer1977(r1, r2, r3, r4, angle, amp_type=amp_type)
        self.assertEqual(a, 0)
        self.assertEqual(p, 0)
        a, p = rps_cer1977(r1, r2, r3, r4, angle, amp_type=amp_type)
        self.assertEqual(a, 0)
        self.assertEqual(p, 0)

        # Only density contrast: r1=1, r2=r3, r4!=1

        # NOT handle acoustic media
        # r1, r2, r3, r4, angle, amp_type = 1, 0, 0, 1.1, 30, 'real'
        # a, p = rpp_cer1977(r1, r2, r3, r4, angle, amp_type=amp_type)

        # It is known that acoustic RC has NO AVA on density-only contrast.
        # Is there AVA with elastic density contrast? Seems there is.
        r1, r2, r3, r4, angle, amp_type = 1, 0.5, 0.5, 1.1, 0, 'real'
        a, p = rpp_cer1977(r1, r2, r3, r4, angle, amp_type=amp_type)
        # print('test1', a, p)
        r1, r2, r3, r4, angle, amp_type = 1, 0.5, 0.5, 1.1, 30, 'real'
        a, p = rpp_cer1977(r1, r2, r3, r4, angle, amp_type=amp_type)
        # print('test2', a, p)
        r1, r2, r3, r4, angle, amp_type = 1, 0.5, 0.5, 1.1, 50, 'real'
        a, p = rpp_cer1977(r1, r2, r3, r4, angle, amp_type=amp_type)
Example #5
0
    def test_idm_interface(self):
        # Interface-based IDM
        # r1 float, blocks: <0, =0, (0,1), =1, >1
        r1s = [-1, 0, 0.5, 1, 2]
        r2s = [-1, 0, 0.5, 1, 2]
        r3s = [-1, 0, 0.5, 1, 2]
        r4s = [-1, 0, 0.5, 1, 2]
        angles = [-1, 0, 100]  # blocks: <0, =0, >0
        # angles = [-1, 0, 10]  # blocks: <0, =0, >0
        amp_types = ['real', 'abs', 'else']

        # Total number of test cases is 5**4 * 4 * 3 = 7500
        # To illustrate, we fix these parameters, iterate r1 and angle
        r2, r3, r4, amp_type = 0.5, 0.5, 1.0, 'real'

        r1 = -1  # illegal value
        for angle in angles:
            with self.assertRaises(ValueError):
                a, p = rpp_cer1977(r1, r2, r3, r4, angle, amp_type=amp_type)
            with self.assertRaises(ValueError):
                a, p = rps_cer1977(r1, r2, r3, r4, angle, amp_type=amp_type)

        r1 = 0  # illegal value
        for angle in angles:
            with self.assertRaises(ValueError):
                a, p = rpp_cer1977(r1, r2, r3, r4, angle, amp_type=amp_type)
            with self.assertRaises(ValueError):
                a, p = rps_cer1977(r1, r2, r3, r4, angle, amp_type=amp_type)

        r1 = 0.5  # illegal r3 = 0.5 > 0.707 * r1
        for angle in angles:
            with self.assertRaises(ValueError):
                a, p = rpp_cer1977(r1, r2, r3, r4, angle, amp_type=amp_type)
            with self.assertRaises(ValueError):
                a, p = rps_cer1977(r1, r2, r3, r4, angle, amp_type=amp_type)

        r1 = 1
        angle = -1  # illegal value
        with self.assertRaises(ValueError):
            a, p = rpp_cer1977(r1, r2, r3, r4, angle, amp_type=amp_type)
        with self.assertRaises(ValueError):
            a, p = rps_cer1977(r1, r2, r3, r4, angle, amp_type=amp_type)

        # No elastic contrast
        r1 = 1
        angle = 0
        a, p = rpp_cer1977(r1, r2, r3, r4, angle, amp_type=amp_type)
        self.assertEqual(a, 0)
        self.assertEqual(p, 0)
        a, p = rps_cer1977(r1, r2, r3, r4, angle, amp_type=amp_type)
        self.assertEqual(a, 0)
        self.assertEqual(p, 0)

        r1 = 1
        angle = 100  # illegal value
        with self.assertRaises(ValueError):
            a, p = rpp_cer1977(r1, r2, r3, r4, angle, amp_type=amp_type)
        with self.assertRaises(ValueError):
            a, p = rps_cer1977(r1, r2, r3, r4, angle, amp_type=amp_type)

        r1 = 2
        angle = -1  # illegal value
        with self.assertRaises(ValueError):
            a, p = rpp_cer1977(r1, r2, r3, r4, angle, amp_type=amp_type)
        with self.assertRaises(ValueError):
            a, p = rps_cer1977(r1, r2, r3, r4, angle, amp_type=amp_type)

        r1 = 2
        angle = 0
        a, p = rpp_cer1977(r1, r2, r3, r4, angle, amp_type=amp_type)
        self.assertLessEqual(0.33333333 - a, 0.001)
        self.assertEqual(p, 0)
        a, p = rps_cer1977(r1, r2, r3, r4, angle, amp_type=amp_type)
        self.assertEqual(a, 0)
        self.assertEqual(p, 0)

        r1 = 2
        angle = 100  # illegal value
        with self.assertRaises(ValueError):
            a, p = rpp_cer1977(r1, r2, r3, r4, angle, amp_type=amp_type)
        with self.assertRaises(ValueError):
            a, p = rps_cer1977(r1, r2, r3, r4, angle, amp_type=amp_type)
Example #6
0
def cer1itr(angles,
            rpp,
            x_ini,
            rps=None,
            fm='numeric',
            scale=1,
            constraints={}):
    """
    One iteration of linearized inversion.

    Parameters
    ----------
    angles : array
        incident angles in degrees.
    rpp : array
        Rpp amplitude at the angles, also the b in Ax=b.
    x_ini : tuple
        Initial or starting model of this iteration.
    rps : array
        Rps amplitude at the angles, append b in Ax=b.
    fm : str
        the method to calculate Frechet derivatives, numeric or analytic
    scale : float
        Scale to the model update
    constraints : dict
        constraints e.g. {'r2': 0.5}

    Returns
    -------
    x_new : tuple
        Updated model
    """
    x_ini_copy = [x for x in x_ini]
    if 'r1' in constraints:
        x_ini_copy[0] = constraints['r1']
    if 'r2' in constraints:
        x_ini_copy[1] = constraints['r2']
    if 'r3' in constraints:
        x_ini_copy[2] = constraints['r3']
    if 'r4' in constraints:
        x_ini_copy[3] = constraints['r4']
    r1_ini, r2_ini, r3_ini, r4_ini = x_ini_copy

    m = len(angles)
    n = 4
    rpp_ini = np.zeros(m)
    A1 = np.zeros((m, n))
    for i in range(m):
        angle = angles[i]
        amp, pha = rpp_cer1977(r1_ini, r2_ini, r3_ini, r4_ini, angle)
        rpp_ini[i] = amp
        # Calculate the Jacobian matrix A in Ax=b
        for j in range(n):
            fre = gradient(r1_ini,
                           r2_ini,
                           r3_ini,
                           r4_ini,
                           angle,
                           'PP',
                           j + 1,
                           method=fm)
            A1[i, j] = fre
    # A *= -1  # needed when we take abs of negative rpp
    b1 = rpp - rpp_ini

    if rps is None:
        A = A1
        b_dif = b1
    else:
        rps_ini = np.zeros(m)
        A2 = np.zeros((m, n))
        for i in range(m):
            angle = angles[i]
            amp, pha = rps_cer1977(r1_ini,
                                   r2_ini,
                                   r3_ini,
                                   r4_ini,
                                   angle,
                                   amp_type='abs')
            rps_ini[i] = amp
            # Calculate the Jacobian matrix A in Ax=b
            for j in range(n):
                fre = gradient(r1_ini,
                               r2_ini,
                               r3_ini,
                               r4_ini,
                               angle,
                               'PS',
                               j + 1,
                               method=fm)
                A2[i, j] = fre
        b2 = rps - rps_ini
        A = np.concatenate((A1, A2), axis=0)
        b_dif = np.concatenate((b1, b2), axis=0)

    lstsq = np.linalg.lstsq(A, b_dif, rcond=None)
    x_dif = lstsq[0]
    if 'r1' in constraints:
        x_dif[0] = 0
    if 'r2' in constraints:
        x_dif[1] = 0
    if 'r3' in constraints:
        x_dif[2] = 0
    if 'r4' in constraints:
        x_dif[3] = 0
    x_new = x_ini_copy + x_dif * scale
    return x_new
Example #7
0
def modeling(model, inc_angles, equation, reflection):
    """
    Unified API for GUI call.

    Parameters
    ----------
    model : tuple
        Two half-space elastic model (vp1, vs1, ro1, vp2, vs2, ro2)
    inc_angles : str
        Incident angles in degrees, either comma separated values, or
        1-60(2) means from 1 to 60 with step 2.
    equation : str
        modeling equation, 'linear', 'quadratic', 'zoeppritz'
    reflection : str
        reflection type, 'PP', 'PS'

    Returns
    -------
    rc : array
        amplitude and phase of the reflection coefficients at the angles.
        The array shape is mx3 of columns: incident angle, amplitude, phase.
    """
    # Change parameterization
    vp1, vs1, ro1, vp2, vs2, ro2 = model
    ro_rd, vp_rd, vs_rd, vs_vp_ratio = \
        elapar_hs2delta(vp1, vs1, ro1, vp2, vs2, ro2)
    r1, r2, r3, r4 = elapar_hs2ratio(vp1, vs1, ro1, vp2, vs2, ro2)

    if '-' in inc_angles:
        a, b = inc_angles.split('-')
        c, d = b.split('(')
        e, f = d.split(')')
        a1 = float(a)
        a2 = float(c)
        ad = float(e)
        angles = np.arange(a1, a2, ad)
    else:
        angles = np.array([float(a) for a in inc_angles.split(',')])
    ave_angles = inc2ave_angle(angles, vp_rd)

    m = len(angles)
    a, p = np.zeros(m), np.zeros(m)
    if reflection == 'PP':
        if equation == 'linear':
            a = aki1980(vs_vp_ratio, ro_rd, vp_rd, vs_rd, ave_angles)
            return np.vstack((angles, a, p)).T  # mx3 array
        elif equation == 'quadratic':
            a = wang1999(vs_vp_ratio, ro_rd, vp_rd, vs_rd, ave_angles)
            return np.vstack((angles, a, p)).T  # mx3 array
        elif equation == 'zoeppritz':
            for i in range(m):
                angle = angles[i]
                amp, pha = rpp_cer1977(r1, r2, r3, r4, angle)
                a[i], p[i] = amp, pha
            return np.vstack((angles, a, p)).T  # mx3 array
        else:
            raise NotImplementedError
    elif reflection == 'PS':
        if equation == 'linear':
            raise NotImplementedError
        elif equation == 'quadratic':
            raise NotImplementedError
        elif equation == 'zoeppritz':
            for i in range(m):
                angle = angles[i]
                amp, pha = rps_cer1977(r1, r2, r3, r4, angle)
                a[i], p[i] = amp, pha
            return np.vstack((angles, a, p)).T  # mx3 array
        else:
            raise NotImplementedError
    else:
        raise NotImplementedError
Example #8
0
    def test_pp_noise(self):
        # Two half spaces elastic model
        vp1, vp2 = 4.0, 2.0
        vs1, vs2 = 2.0, 1.0
        ro1, ro2 = 2.4, 2.0

        # Change parameterization
        r1, r2, r3, r4 = elapar_hs2ratio(vp1, vs1, ro1, vp2, vs2, ro2)

        # Define angles
        angles = np.arange(1, 60, 1)

        # Calculate the reflection amplitude or b in Ax=b
        m = len(angles)
        rpp = np.zeros(m)
        rps = np.zeros(m)
        for i in range(m):
            angle = angles[i]
            amp, pha = rpp_cer1977(r1, r2, r3, r4, angle)
            rpp[i] = amp
            amp, pha = rps_cer1977(r1, r2, r3, r4, angle, amp_type='abs')
            rps[i] = amp

        # Add noise to data
        # ramp = np.max(rpp) - np.min(rpp)
        # mu, sigma = 0, 0.05 * ramp  # mean and standard deviation
        # noise = np.random.normal(mu, sigma, m)

        # hard code noise to make test result consistent
        noise = np.array([
            -3.40745756e-03,
            4.41326891e-03,
            -1.84969329e-02,
            -8.57665267e-03,
            -4.64722728e-03,
            1.81164323e-02,
            2.35764041e-03,
            8.66820650e-03,
            2.61862025e-03,
            5.60835320e-03,
            -1.32386200e-02,
            -1.13868325e-02,
            -3.85411636e-03,
            8.30156732e-04,
            6.08364262e-03,
            -1.13107829e-02,
            7.51568819e-03,
            8.32391400e-03,
            7.18915187e-03,
            2.48970883e-03,
            1.42114394e-02,
            2.45652884e-04,
            -4.69414374e-03,
            4.60964000e-03,
            1.43935631e-02,
            -5.88788401e-03,
            3.13041871e-03,
            -6.68177919e-04,
            -6.20489672e-03,
            -1.68069368e-04,
            -1.78392131e-02,
            8.38724551e-04,
            1.30622636e-03,
            -9.83497743e-03,
            -1.17627106e-02,
            -1.62056738e-02,
            4.62611536e-03,
            1.48628494e-02,
            -1.24973356e-02,
            -1.01725440e-02,
            7.38562227e-03,
            9.21933387e-03,
            -6.69923701e-03,
            6.42089408e-03,
            -4.77129595e-03,
            2.33900064e-03,
            3.29402557e-05,
            9.54770479e-04,
            -1.49280387e-02,
            -6.65381602e-03,
            -1.58004300e-02,
            -7.08064272e-03,
            5.65539007e-04,
            -2.76684435e-03,
            -5.60120257e-03,
            8.84405490e-03,
            -3.24883460e-03,
            5.64724034e-03,
            -9.45532624e-03,
        ])
        rpp_noisy = rpp + noise

        # ramp = np.max(rps) - np.min(rps)
        # mu, sigma = 0, 0.05 * ramp  # mean and standard deviation
        # noise = np.random.normal(mu, sigma, m)

        # hard code noise to make test result consistent
        noise = np.array([
            -0.0309984,
            0.00092359,
            -0.00770345,
            -0.03662312,
            0.00336188,
            0.00583431,
            -0.02101242,
            -0.0248055,
            -0.00333648,
            0.02492424,
            -0.00099495,
            0.00944948,
            -0.00325943,
            0.01934984,
            -0.00704765,
            0.01490579,
            0.00779604,
            0.02183828,
            -0.00405295,
            -0.01820525,
            -0.00446887,
            0.01793082,
            0.03251096,
            0.0026122,
            0.01377384,
            -0.01452418,
            0.02901279,
            -0.00881719,
            0.02308159,
            0.01260138,
            -0.00522267,
            0.00769085,
            0.02171298,
            -0.01478435,
            0.01349567,
            -0.00778548,
            -0.01922285,
            -0.01798599,
            -0.02126122,
            -0.00327526,
            0.01550364,
            0.00130878,
            0.00680895,
            0.02670106,
            -0.05456112,
            0.02081972,
            0.02333233,
            0.03656901,
            0.01069452,
            -0.01197574,
            0.02639394,
            0.01850353,
            0.0232636,
            -0.00037154,
            -0.01148699,
            0.03056004,
            0.006255,
            0.01079065,
            0.02806546,
        ])
        rps_noisy = rps + noise

        # print("Target model:", r1, r2, r3, r4)
        r1_ini = 2.4 / 4.0
        r2_ini = 2.2 / 4.0
        r3_ini = 1.3 / 4.0
        r4_ini = 1.6 / 2.4
        x_ini = (r1_ini, r2_ini, r3_ini, r4_ini)
        # print("Initial model:", x_ini)

        for i in range(10):
            # x_new = cer1itr(angles, rpp_noisy, x_ini, rps=None)  # fails
            x_new = cer1itr(angles, rpp_noisy, x_ini, rps=rps_noisy)
            # print("Updated", i, x_new)
            x_ini = x_new

        self.assertLessEqual(0.54935233 - x_new[0], 0.001)
        self.assertLessEqual(0.48925867 - x_new[1], 0.001)
        self.assertLessEqual(0.25932287 - x_new[2], 0.001)
        self.assertLessEqual(0.76031868 - x_new[3], 0.001)