def test_fit_acph2__non_strict__raise_error_for_bad_moments(moments, err_str):
    """
    No matter of strict = True, if any moment is non-positive or CV <= 0,
    raise ValueError indicating failed moment (or CV).
    """
    with pytest.raises(ValueError) as err:
        fit_acph2(moments, strict=False)  # even non-strict mode!
    assert str(err.value) == err_str
def test_fit_acph2__non_strict__finds_ph_for_m1():
    """If only M1 is provided, get exponential distribution in ACPH(2) form.
    """
    ph, _ = fit_acph2([1], strict=False)
    assert_allclose(ph.mean, 1)
    assert_allclose(ph.cv, 1)
    assert_allclose(ph.moment(3), 6)
def test_fit_acph2__non_strict__finds_ph_for_m1_and_m2():
    """If only M1 and M2 are provided, take some M3 and find ACPH(2)
    when strict = False.
    """
    # If 0.5 <= pow(CV, 2) <= 1.0, take M3 value between boundaries:
    ph1, _ = fit_acph2([1, 1.64], strict=False)
    assert_allclose(ph1.moment(1), 1)
    assert_allclose(ph1.moment(2), 1.64)
    assert_allclose(ph1.moment(3), 3.7582051942088834)

    # If 1 < CV, take M3 from boundary:
    ph2, _ = fit_acph2([1, 2.21], strict=False)
    assert_allclose(ph2.moment(1), 1)
    assert_allclose(ph2.moment(2), 2.21)
    assert_allclose(ph2.moment(3), 7.32615 * 10 / 9)

    # If 1 = CV, M3 equals 6 * pow(m1, 3):
    ph3, _ = fit_acph2([1, 2], strict=False)
    assert_allclose(ph3.moment(1), 1)
    assert_allclose(ph3.moment(2), 2)
    assert_allclose(ph3.moment(3), 6)
def test_fit_acph2__strict__compute_errors_for_all_moments_passed():
    """
    Validate that when more then three moments passed to fit_acph2() with
    strict = True, errors will be computed for all of them.
    """
    moments = (2, 7.2, 37.5, 50, 800)
    ph, errors = fit_acph2(moments, strict=True)

    # Check that first three moments were fitted properly:
    assert_allclose(errors[:3], (0, 0, 0), atol=1e-5)
    for i in range(3):
        assert_allclose(ph.moment(i + 1), moments[i], rtol=1e-5)

    # Check that other errors for other moments were estimated in some way:
    assert len(errors) == len(moments)
    assert all(errors[3:] > 0)
def test_fit_acph2__non_strict__finds_approx_solution(moments, errors,
                                                      comment):
    """
    Validate that fit_acph2() with strict = False tries to fit moments even
    if they are out of the bounds, and put relative errors into the result.
    """
    ph, actual_errors = fit_acph2(moments, strict=False)

    assert_allclose(actual_errors,
                    errors,
                    rtol=1e-3,
                    atol=1e-3,
                    err_msg=comment)
    for i, m in enumerate(moments):
        rtol = max((errors[i] * 1.01), 1e-5)  # to avoid zero rtol for error=0
        assert_allclose(ph.moment(i + 1), m, rtol=rtol, err_msg=comment)
def test_fit_acph2__with_good_data(m1, cv2, m3):
    """
    Test fit_acph2 with good data obtained from Telek and Heindl paper.

    Data was retrieved from Figure 2 of paper [1]. First point is from
    region with CV > 1, second point - from tight stripe when CV < 1, and
    the last point relates to singular point representing exponential
    distribution.

    [1] Telek, Miklós & Heindl, Armin. (2003). Matching Moments For Acyclic 
    Discrete And Continuous Phase-Type Distributions Of Second Order. 
    International Journal of Simulation Systems, Science & Technology. 3.
    """
    cv = cv2**0.5
    m2 = (cv2 + 1) * m1**2

    ph, errors = fit_acph2([m1, m2, m3])

    assert_allclose(errors, (0, 0, 0), rtol=1e-8, atol=1e-8)
    assert_allclose(ph.mean, m1)
    assert_allclose(ph.var, m2 - m1**2)
    assert_allclose(ph.cv, cv)
    assert_allclose(ph.moment(3), m3)
def test_fit_acph2__srtict__raise_error_for_bad_values(moments, err_str):
    with pytest.raises(BoundsError) as err:
        fit_acph2(moments, strict=True)
    assert str(err.value).startswith(err_str)
def test_fit_acph2__strict__raise_error_if_less_then_three_moments_given():
    with pytest.raises(ValueError) as err:
        fit_acph2([1, 2], strict=True)
    assert str(err.value) == "Expected three moments, but 2 found"