def test_swatinit_less_than_1_below_contact(simulator, tmp_path):
    """SWATINIT below the contact is ignored, and SWAT is set based on the
    input SWOF table. In water-wet system (pc>0), this always yields SWAT=1
    """
    os.chdir(tmp_path)
    model = PillarModel(cells=1, apex=1000, owc=[900], swatinit=[0.7], swl=[0.1])
    qc_frame = run_reservoir_simulator(simulator, model)
    qc_vols = qc_volumes(qc_frame)
    assert qc_frame["QC_FLAG"][0] == __HC_BELOW_FWL__
    assert np.isclose(qc_frame["SWAT"][0], 1)

    assert np.isclose(qc_vols[__HC_BELOW_FWL__], (1 - 0.7) * qc_frame["PORV"][0])
    if "flow" in simulator:
        assert np.isclose(qc_frame["PPCW"][0], 3.0)
        assert np.isclose(qc_frame["PC_SCALING"][0], 1.0)
        assert np.isclose(qc_frame["PC"], 0)
    else:
        # E100 will not report a PPCW in this case, libecl gives -1e20,
        # which becomes a NaN through ecl2df and then NaN columns are dropped.
        if "PPCW" in qc_frame:
            assert pd.isnull(qc_frame["PPCW"][0])
        if "PC_SCALING" in qc_frame:
            assert pd.isnull(qc_frame["PC_SCALING"][0])
        if "PC" in qc_frame:
            assert pd.isnull(qc_frame["PC"][0])
예제 #2
0
def test_volplot_manynegative():
    qc_frame = pd.DataFrame([
        {
            "EQLNUM": 1,
            "Z": 1000,
            "SWATINIT": 0.9,
            "PORV": 100000,
            "SWAT": 0.2,
            "VOLUME": 80,
            "QC_FLAG": __SWL_TRUNC__,
            "SATNUM": 1,
            "PCOW_MAX": 2,
            "PPCW": 4,
            "PC_SCALING": 2,
            "OIP_INIT": 0,
        },
        {
            "EQLNUM": 1,
            "Z": 1000,
            "SWATINIT": 0.9,
            "PORV": 100000,
            "SWAT": 0.2,
            "VOLUME": 80,
            "QC_FLAG": __SWATINIT_1__,
            "SATNUM": 1,
            "PCOW_MAX": 2,
            "PPCW": 4,
            "PC_SCALING": 2,
            "OIP_INIT": 0,
        },
    ])
    wvol_waterfall(qc_volumes(qc_frame))

    print("Verify that y limits in particular are correct")
    pyplot.show()
def test_swat_higher_than_swatinit_via_swl_above_contact(simulator, tmp_path):
    """If SWL is set higher than SWATINIT, both Eclipse and flow
    truncates SWAT to SWL.

    QC category is "Water gained"
    """
    os.chdir(tmp_path)
    model = PillarModel(cells=1, apex=1000, owc=[2000], swatinit=[0.3], swl=[0.5])
    qc_frame = run_reservoir_simulator(simulator, model)
    assert qc_frame["QC_FLAG"][0] == __SWL_TRUNC__
    assert np.isclose(qc_frame["SWAT"][0], 0.5)
    assert np.isclose(qc_frame["SWATINIT"][0], 0.3)

    qc_vols = qc_volumes(qc_frame)
    assert np.isclose(qc_vols[__SWL_TRUNC__], (0.5 - 0.3) * qc_frame["PORV"][0])

    if "flow" in simulator:
        expected_ppcw = 12.650095
        assert np.isclose(qc_frame["PPCW"][0], expected_ppcw)  # oip_init=0
    else:
        assert np.isclose(qc_frame["PPCW"][0], 3.0)

    # When SWL is truncated, we cannot trust PC_SCALING to be used to
    # compute PC, so it is removed from the dataframe.
    assert pd.isnull(qc_frame["PC_SCALING"][0])
    assert pd.isnull(qc_frame["PC"][0])
def test_swatinit_almost1_slightly_above_contact(simulator, tmp_path):
    """The result is discontinuous close to swatinit=1 for Eclipse100, because
    at swatinit = 1 - epsilon, Eclipse will try to scale  the capillary
    pressure, and is only limited by PPCWMAX (but not in this test).

    flow is not discontinuous in SWAT as a function of SWATINIT, but in PPCW as a
    function of SWATINIT.
    """
    os.chdir(tmp_path)

    if "flow" in simulator:
        p_cap = 0.37392
    else:
        p_cap = 0.3738366

    model = PillarModel(cells=1, apex=1000, owc=[1030], swatinit=[0.999], swl=[0.1])
    qc_frame = run_reservoir_simulator(simulator, model)
    assert qc_frame["QC_FLAG"][0] == __PC_SCALED__
    assert np.isclose(qc_frame["SWAT"][0], 0.999)
    assert np.isclose(qc_frame["PC"], p_cap, atol=0.001)
    needed_scaling = p_cap / model.evaluate_pc(0.999)
    # Worryingly inaccurate?
    assert np.isclose(qc_frame["PPCW"][0], needed_scaling * 3.0, atol=1)

    qc_vols = qc_volumes(qc_frame)
    assert np.isclose(qc_vols[__PC_SCALED__], 0.0, atol=0.0003)
예제 #5
0
def test_volplot_negative_bars():
    """Test the volumetrics waterfall chart with negative values, giving negative bars,
    interaticve plot test"""
    qc_frame = pd.DataFrame([
        # This dataframe is a minimum dataset for check_swatinit
        # to run.
        {
            "EQLNUM": 1,
            "Z": 1000,
            "SWATINIT": 0.9,
            "PORV": 100,
            "SWAT": 0.8,
            "VOLUME": 80,
            "QC_FLAG": __SWL_TRUNC__,
            "SATNUM": 1,
            "PCOW_MAX": 2,
            "PPCW": 4,
            "PC_SCALING": 2,
            "OIP_INIT": 0,
        }
    ])

    wvol_waterfall(qc_volumes(qc_frame))

    print("Verify that all annotations are visible")
    pyplot.show()
def test_swatinit_1_far_above_contact(simulator, tmp_path):
    """If SWATINIT is 1 far above the contact, we are in an unstable
    situation (water should not be mobile e.g)

    Eclipse doc says this:
    "If a cell is given saturation corresponding to a zero capillary pressure
    (typically 1.0) above the contact, then the Pc curve cannot be scaled to
    honor the saturation, hence the Pc curve is left unscaled."

    The SWATINIT is effectively ignored by Eclipse100: SWAT is taken from
    the SWOF table and the relevant Pc pressure, and since that Pc curve
    is not touched by SWATINIT, SWAT becomes SWL far above contact.
    """
    os.chdir(tmp_path)
    model = PillarModel(
        cells=1, apex=1000, owc=[2000], swatinit=[1], swl=[0.1], maxpc=[3.0]
    )

    qc_frame = run_reservoir_simulator(simulator, model)

    qc_vols = qc_volumes(qc_frame)

    assert qc_frame["QC_FLAG"][0] == __SWATINIT_1__
    if "flow" in simulator:
        # Flow accepts this swatinit, but this water will flow out
        assert np.isclose(qc_frame["SWAT"][0], 1)
        # PPCW is the input Pc:
        assert np.isclose(qc_frame["PPCW"][0], 3.0)

        assert np.isclose(qc_vols[__SWATINIT_1__], (1 - 1) * qc_frame["PORV"])
    else:
        # E100 ignores SWATINIT and sets the saturation to SWL:
        assert np.isclose(qc_frame["SWAT"][0], 0.1)
        assert np.isclose(qc_frame["PPCW"][0], 3.0)
        # Negative number means water is lost:
        assert np.isclose(qc_vols[__SWATINIT_1__], -(1 - 0.1) * qc_frame["PORV"])
    # Not possible to compute PC, it should be Nan:
    assert np.isnan(qc_frame["PC"][0])

    # Bigger reservoir model, so that OWC is within the grid, should
    # not make a difference:
    biggermodel = PillarModel(
        cells=200, apex=1000, owc=[2000], swatinit=[1] * 200, swl=[0.1]
    )
    qc_frame = run_reservoir_simulator(simulator, biggermodel)
    assert set(qc_frame["QC_FLAG"]) == set([__SWATINIT_1__, __WATER__])
    assert qc_frame[qc_frame["Z"] < 2000]["QC_FLAG"].unique()[0] == __SWATINIT_1__
    assert qc_frame[qc_frame["Z"] > 2000]["QC_FLAG"].unique()[0] == __WATER__
    if "flow" in simulator:
        assert np.isclose(qc_frame["SWAT"][0], 1)
        # PPCW is the input Pc:
        assert np.isclose(qc_frame["PPCW"][0], 3.0)
    else:
        # E100 ignores SWATINIT and sets the saturation to SWL:
        assert np.isclose(qc_frame["SWAT"][0], 0.1)
        assert np.isclose(qc_frame["PPCW"][0], 3.0)
    assert np.isnan(qc_frame["PC"][0])
def test_swat_limited_by_ppcwmax_above_contact(simulator, tmp_path):
    """Test PPCWMAX far above contact. This keyword is only supported by Eclipse100
    and will be ignored by flow.

    This leads to water being lost from SWATINIT to SWAT.
    """
    os.chdir(tmp_path)
    swatinit = 0.8
    model = PillarModel(
        cells=1, apex=1000, owc=[1100], swatinit=[swatinit], ppcwmax=[3.01]
    )
    qc_frame = run_reservoir_simulator(simulator, model)
    assert np.isclose(qc_frame["PPCWMAX"][0], 3.01)
    qc_vols = qc_volumes(qc_frame)
    swat_if_ppcwmax = 0.5746147
    if "eclipse" in simulator:
        # for PPCWMAX set to 3.01 Eclipse100 will scale the swatinit value to this:
        assert qc_frame["QC_FLAG"][0] == __PPCWMAX__
        assert np.isclose(qc_frame["SWAT"][0], swat_if_ppcwmax)
        assert np.isclose(qc_frame["PC_SCALING"][0], 3.01 / 3.00)
        actual_pc = 1.4226775
        assert np.isclose(
            model.evaluate_pc(swat_if_ppcwmax, scaling=3.01 / 3.00), actual_pc
        )
        assert np.isclose(qc_frame["PC"][0], actual_pc)

        assert np.isclose(
            qc_vols[__PPCWMAX__], (swat_if_ppcwmax - swatinit) * qc_frame["PORV"][0]
        )
    else:
        # "flow" does not support PPCWMAX and will scale the Pc curve as much is needed
        # and will thus reproduce swatinit:
        assert qc_frame["QC_FLAG"][0] == __PC_SCALED__
        assert np.isclose(qc_frame["SWAT"][0], swatinit)
        expected_scaling = 2.107745
        assert np.isclose(qc_frame["PC_SCALING"][0], expected_scaling)

        actual_pc = 1.405163
        # The capillary pressure in this cell:
        assert np.isclose(
            model.evaluate_pc(swatinit, scaling=expected_scaling), actual_pc
        )
        assert np.isclose(qc_frame["PC"][0], actual_pc)
        # If we had done scaling according to PPCWMAX, we would have gotten
        # the same result as in Eclipse:
        assert np.isclose(
            # Worryingly inaccurate?
            model.evaluate_sw(actual_pc, scaling=3.01 / 3.00),
            swat_if_ppcwmax,
            atol=0.02,
            # (opm probably uses monotone cubic interpolation in SWOF, but that
            # should not affect SWOF tables with only two points)
        )

        # Not very accurate:
        assert np.isclose(qc_vols[__PC_SCALED__], 0, atol=0.001)
def test_swatinit_less_than_1_below_contact_neg_pc(simulator, tmpdir):
    """For an oil-wet system, there can be oil below free water level.

    Flow will set water saturation to 1 no questions asked. Bug?

    Eclipse ignores SWATINIT but calculates SWAT based on the input
    Pc-curve, and can thus give SWAT<1 if pc_min < 0.
    """
    tmpdir.chdir()
    model = PillarModel(
        cells=1,
        apex=1000,
        owc=[900],
        swatinit=[0.7],
        swl=[0.1],
        maxpc=[3.0],
        minpc=[-3.0],
    )
    # Eclipse will pick this SWAT:
    expected_swat = 0.7915066

    # This must then be the Pc in the cell:
    actual_pc = -1.610044
    p_cap = model.evaluate_pc(expected_swat)
    assert np.isclose(p_cap, actual_pc)
    qc_frame = run_reservoir_simulator(simulator, model)
    assert qc_frame["QC_FLAG"][0] == __HC_BELOW_FWL__

    qc_vols = qc_volumes(qc_frame)
    if "flow" in simulator:
        assert np.isclose(qc_frame["SWAT"][0], 1.0)
        assert np.isclose(qc_frame["PPCW"][0], 3.0)
        assert np.isclose(qc_frame["PC_SCALING"][0], 1.0)

        # Computed Pc is wrong here, but is what corresponds
        # to the saturation picked by OPM-flow:
        assert np.isclose(qc_frame["PC"][0], -3.0)

        assert np.isclose(qc_vols[__HC_BELOW_FWL__],
                          (1 - 0.7) * qc_frame["PORV"][0])
    else:
        assert np.isclose(qc_frame["SWAT"][0], expected_swat)
        # PPCW is set to NaN, so we don't have that column
        if "PPCW" in qc_frame:
            assert pd.isnull(qc_frame["PPCW"][0])
        if "PC_SCALING" in qc_frame:
            assert pd.isnull(qc_frame["PC_SCALING"][0])
        if "PC" in qc_frame:
            assert pd.isnull(qc_frame["PC"][0])
        assert np.isclose(qc_frame["PCW"][0], 3.0)  # Untouched input

        assert np.isclose(
            qc_vols[__HC_BELOW_FWL__],
            (expected_swat - 0.7) * qc_frame["PORV"][0],
            atol=0.1,
        )
def test_accepted_swatinit_far_above_contact(simulator, tmpdir):
    """Test a "normal" scenario, SWATINIT is accepted and some PC scaling will be applied
    far above the contact
    """
    tmpdir.chdir()
    model = PillarModel(cells=1,
                        apex=1000,
                        owc=[1100],
                        swatinit=[0.1],
                        swl=[0.0],
                        maxpc=[3.0])
    qc_frame = run_reservoir_simulator(simulator, model)
    assert np.isclose(qc_frame["SWAT"][0], 0.1)
    assert qc_frame["QC_FLAG"][0] == __PC_SCALED__

    qc_vols = qc_volumes(qc_frame)
    assert np.isclose(qc_vols[__PC_SCALED__], 0, atol=0.0001)

    if "flow" in simulator:
        # Flow returns the unscaled SWOF input here
        assert np.isclose(qc_frame["PCW"][0], 3.0)
        expected_ppcw = 1.5612928
        assert np.isclose(
            qc_frame["PPCW"][0], expected_ppcw
        )  # This is what it had to be scaled to to reach swatinit.
        assert np.isclose(qc_frame["PC_SCALING"][0], expected_ppcw / 3.0)

        # The actual Pc value can be back-calculated from SWOF:
        actual_pc = model.evaluate_pc(0.1, scaling=expected_ppcw / 3.0)
        assert np.isclose(actual_pc, 1.4051635)  # in bars.
        assert np.isclose(qc_frame["PC"], actual_pc)
        # At surface conditions, density difference is 200 kg/m3, this number
        # is sort of "close" to 200 kg/m3 * 9.81 m/s^2 * 100 meters / 1e5 = 1.96
        # (mismatch due to Bo and compressibility)
    else:
        # Eclipse100, numbers are only slightly different:
        expected_ppcw = 1.5807527
        assert np.isclose(qc_frame["PCW"][0], expected_ppcw)
        assert np.isclose(qc_frame["PPCW"][0], expected_ppcw)
        assert np.isclose(qc_frame["PC_SCALING"][0], expected_ppcw / 3.0)
        # (cell centre is 5 meters above contact)

        # The actual Pc value can be back-calculated from SWOF:
        actual_pc = model.evaluate_pc(0.1, scaling=expected_ppcw / 3.0)
        assert np.isclose(actual_pc, 1.42267743)
        assert np.isclose(qc_frame["PC"], actual_pc)
def test_swatinit_1_slightly_above_contact(simulator, tmpdir):
    """If we are slightly above the contact, item 9 in EQUIL plays
    a small role.

    SWATINIT=1 is still ignored above contact, Pc curve is left untouched.
    """
    tmpdir.chdir()
    model = PillarModel(cells=1,
                        apex=1000,
                        owc=[1030],
                        swatinit=[1],
                        swl=[0.1],
                        oip_init=0)
    qc_frame = run_reservoir_simulator(simulator, model)
    assert qc_frame["QC_FLAG"][0] == __SWATINIT_1__
    qc_vols = qc_volumes(qc_frame)
    if "flow" in simulator:
        expected_swat = 0.887824
        actual_pc = 0.37392
    else:
        expected_swat = 0.887849
        actual_pc = 0.3738366

    if "flow" in simulator:
        # Flow accepts this swatinit, but this water will flow out.
        assert np.isclose(qc_frame["SWAT"][0], 1)
        assert np.isnan(qc_frame["PC"][0])
        assert np.isclose(qc_vols[__SWATINIT_1__], (1 - 1) * qc_frame["PORV"])
    else:
        # E100:
        assert np.isclose(qc_frame["SWAT"][0], expected_swat)
        assert np.isclose(qc_frame["PC"][0], actual_pc)
        assert np.isclose(qc_vols[__SWATINIT_1__],
                          (expected_swat - 1) * qc_frame["PORV"])
    assert model.evaluate_pc(0.1) == 3.0
    assert model.evaluate_pc(1) == 0

    # The actual capillary pressure in this cell:
    assert np.isclose(model.evaluate_pc(expected_swat), actual_pc)

    # Check that if we run without SWATINIT, even flow will give this
    # saturation:
    model.swatinit = [None]  # hacking the model object
    qc_frame = run_reservoir_simulator(simulator, model)
    assert np.isclose(qc_frame["SWAT"][0], expected_swat, atol=0.001)
예제 #11
0
def test_volplot_largenegative():
    qc_frame = pd.DataFrame([{
        "EQLNUM": 1,
        "Z": 1000,
        "SWATINIT": 0.9,
        "PORV": 100000,
        "SWAT": 0.2,
        "VOLUME": 80,
        "QC_FLAG": __SWL_TRUNC__,
        "SATNUM": 1,
        "PCOW_MAX": 2,
        "PPCW": 4,
        "PC_SCALING": 2,
        "OIP_INIT": 0,
    }])
    wvol_waterfall(qc_volumes(qc_frame))

    print("Verify that all annotations are visible and placed wisely")
    pyplot.show()
예제 #12
0
def test_volplot_zerospan():
    # Test when there is no difference from SWATINIT to SWAT:
    qc_frame = pd.DataFrame([{
        "EQLNUM": 1,
        "Z": 1000,
        "SWATINIT": 0.9,
        "PORV": 100000,
        "SWAT": 0.9,
        "VOLUME": 80,
        "QC_FLAG": __SWL_TRUNC__,
        "SATNUM": 1,
        "PCOW_MAX": 2,
        "PPCW": 4,
        "PC_SCALING": 2,
        "OIP_INIT": 0,
    }])
    wvol_waterfall(qc_volumes(qc_frame))

    print("Verify that all annotations are visible and placed wisely")
    pyplot.show()
def test_accepted_swatinit_slightly_above_contact(simulator, tmpdir):
    """Test a "normal" scenario, SWATINIT is accepted and some PC scaling will be applied
    some meters above the contact

    QC-wise, these cells will not be flagged, but contribute to average PC_SCALING
    """
    tmpdir.chdir()
    model = PillarModel(cells=1,
                        apex=1000,
                        owc=[1020],
                        swatinit=[0.5],
                        swl=[0.0],
                        maxpc=[3.0])
    qc_frame = run_reservoir_simulator(simulator, model)
    # E100 is not very accurate here, flow gives exactly 0.5
    assert np.isclose(qc_frame["SWAT"][0], 0.5, atol=0.001)
    assert qc_frame["QC_FLAG"][0] == __PC_SCALED__
    # Here, the Pc-curve is scaled (it goes from 3 to 0 in SWOF). At
    # swatinit=0.5, pc_swof is 1.5.

    qc_vols = qc_volumes(qc_frame)
    assert np.isclose(qc_vols[__PC_SCALED__], 0, atol=0.00001)

    if "flow" in simulator:
        # Flow returns the unscaled SWOF input here
        assert np.isclose(qc_frame["PCW"][0], 3.0)

        expected_ppcw = 0.4495535

        assert np.isclose(qc_frame["PPCW"][0], expected_ppcw)

        # This is what it had to be scaled to to reach swatinit.
        assert np.isclose(qc_frame["PC_SCALING"][0], expected_ppcw / 3.0)

        actual_pc = model.evaluate_pc(0.5, scaling=expected_ppcw / 3.0)
        assert np.isclose(actual_pc, 0.22477675)
        assert np.isclose(qc_frame["PC"], actual_pc)
    else:
        # Eclipse100, numbers are a tad different:
        assert np.isclose(qc_frame["PCW"][0], 0.4485523)
        assert np.isclose(qc_frame["PPCW"][0], 0.4485523)
def test_swatinit_1_below_contact(simulator, tmp_path):
    """An all-good scenario, below contact, water-wet, ask for water, we get water."""
    os.chdir(tmp_path)
    model = PillarModel(
        cells=1,
        apex=1000,
        owc=[100],
        swatinit=[1],
        swl=[0.1],
        maxpc=[3.0],
    )
    qc_frame = run_reservoir_simulator(simulator, model)
    assert qc_frame["QC_FLAG"][0] == __WATER__
    assert np.isclose(qc_frame["SWAT"][0], 1)
    if "flow" in simulator:
        assert np.isclose(qc_frame["PPCW"][0], 3.0)
        assert np.isclose(qc_frame["PC"][0], 0)
    else:
        if "PPCW" in qc_frame:
            assert pd.isnull(qc_frame["PPCW"][0])

    qc_vols = qc_volumes(qc_frame)
    assert np.isclose(qc_vols[__WATER__], 0.0)
예제 #15
0
def test_qc_volumes(propslist, expected_dict):
    """Test that we calculate qc volumes correctly from a cell-based qc dataframe"""
    qc_frame = pd.DataFrame(propslist)
    qc_vols = qc_volumes(qc_frame)
    for key in expected_dict.keys():
        assert np.isclose(qc_vols[key], expected_dict[key])