def test_create_lists(): """Test the factory methods for making pyscal lists""" testdir = Path(__file__).absolute().parent scalfile_xls = testdir / "data/scal-pc-input-example.xlsx" scaldata = PyscalFactory.load_relperm_df(scalfile_xls) scalrec_list = PyscalFactory.create_scal_recommendation_list(scaldata) assert len(scalrec_list) == 3 assert scalrec_list.pyscaltype == SCALrecommendation basecasedata = scaldata[scaldata["CASE"] == "base"].reset_index() relpermlist = PyscalFactory.create_pyscal_list(basecasedata) assert len(relpermlist) == 3 assert relpermlist.pyscaltype == WaterOilGas wo_list = PyscalFactory.create_pyscal_list( basecasedata.drop(["Lg", "Eg", "Tg", "Log", "Eog", "Tog"], axis="columns") ) assert len(wo_list) == 3 assert wo_list.pyscaltype == WaterOil go_list = PyscalFactory.create_pyscal_list( basecasedata.drop(["Lw", "Ew", "Tw", "Low", "Eow", "Tow"], axis="columns") ) assert len(go_list) == 3 assert go_list.pyscaltype == GasOil
def test_swirr_partially_missing(tmpdir): """Test that swirr can be present for only a subset of the rows, and interpreted as zero when not there.""" dframe = pd.DataFrame( columns=[ "SATNUM", "Nw", "Now", "swl", "swirr", "a", "b", "poro_ref", "perm_ref", "drho", ], data=[ [1, 2, 2, 0.2, 0.1, 2, -2, 0.2, 100, 300], [2, 3, 3, 0.1, np.nan, np.nan, np.nan, np.nan, np.nan], ], ) relperm_data = PyscalFactory.load_relperm_df(dframe) p_list = PyscalFactory.create_pyscal_list(relperm_data, h=0.2) assert "a=2, b=-2" in p_list[1].pccomment assert p_list[2].pccomment == "" tmpdir.chdir() dframe.to_excel("partial_pc.xlsx") relperm_data_via_xlsx = PyscalFactory.load_relperm_df("partial_pc.xlsx") p_list = PyscalFactory.create_pyscal_list(relperm_data_via_xlsx, h=0.2) assert "a=2, b=-2" in p_list[1].pccomment assert p_list[2].pccomment == ""
def test_create_lists(): """Test the factory methods for making pyscal lists""" if "__file__" in globals(): # Easen up copying test code into interactive sessions testdir = os.path.dirname(os.path.abspath(__file__)) else: testdir = os.path.abspath(".") scalfile_xls = testdir + "/data/scal-pc-input-example.xlsx" scaldata = PyscalFactory.load_relperm_df(scalfile_xls) scalrec_list = PyscalFactory.create_scal_recommendation_list(scaldata) assert len(scalrec_list) == 3 assert scalrec_list.pyscaltype == SCALrecommendation basecasedata = scaldata[scaldata["CASE"] == "base"].reset_index() relpermlist = PyscalFactory.create_pyscal_list(basecasedata) assert len(relpermlist) == 3 assert relpermlist.pyscaltype == WaterOilGas wo_list = PyscalFactory.create_pyscal_list( basecasedata.drop(["Lg", "Eg", "Tg", "Log", "Eog", "Tog"], axis="columns") ) assert len(wo_list) == 3 assert wo_list.pyscaltype == WaterOil go_list = PyscalFactory.create_pyscal_list( basecasedata.drop(["Lw", "Ew", "Tw", "Low", "Eow", "Tow"], axis="columns") ) assert len(go_list) == 3 assert go_list.pyscaltype == GasOil
def test_comments_df(): """Test that we support a tag column in the dataframe, and that we are able to handle UTF-8 stuff nicely in py2-3 """ dframe = pd.DataFrame( columns=["SATNUM", "tag", "Nw", "Now", "ng", "nOG"], data=[[1, "thisisacomment", 2, 2, 2, 2]], ) relperm_data = PyscalFactory.load_relperm_df(dframe) p_list = PyscalFactory.create_pyscal_list(relperm_data, h=0.2) relperm_str = p_list.dump_family_1() assert "thisisacomment" in relperm_str # tag vs comment in dataframe should not matter. dframe = pd.DataFrame( columns=["SATNUM", "comment", "Nw", "Now", "ng", "nOG"], data=[[1, "thisisacomment", 2, 2, 2, 2]], ) relperm_data = PyscalFactory.load_relperm_df(dframe) p_list = PyscalFactory.create_pyscal_list(relperm_data, h=0.2) relperm_str = p_list.dump_family_1() assert relperm_str.count("thisisacomment") == 2 # Check case insensitiveness: dframe = pd.DataFrame( columns=["SAtnUM", "coMMent", "Nw", "Now", "ng", "nOG"], data=[[1, "thisisacomment", 2, 2, 2, 2]], ) relperm_data = PyscalFactory.load_relperm_df(dframe) p_list = PyscalFactory.create_pyscal_list(relperm_data, h=0.2) assert p_list.dump_family_1().count("thisisacomment") == 2 # UTF-8 stuff: dframe = pd.DataFrame( columns=["SATNUM", "TAG", "Nw", "Now", "Ng", "Nog"], data=[[1, "æøå", 2, 2, 2, 2]], ) relperm_data = PyscalFactory.load_relperm_df(dframe) p_list = PyscalFactory.create_pyscal_list(relperm_data, h=0.2) assert p_list.dump_family_1().count("æøå") == 2
def test_mock_two_satnums_via_fam2_files(tmpdir, int_param, expected_file): tmpdir.chdir() PyscalFactory.create_pyscal_list(TWO_SATNUM_PYSCAL_MOCK.loc["low"], h=0.1).dump_family_2("pess.inc") PyscalFactory.create_pyscal_list(TWO_SATNUM_PYSCAL_MOCK.loc["base"], h=0.1).dump_family_2("base.inc") PyscalFactory.create_pyscal_list(TWO_SATNUM_PYSCAL_MOCK.loc["high"], h=0.1).dump_family_2("opt.inc") config = { "base": ["base.inc"], "low": ["pess.inc"], "high": ["opt.inc"], "result_file": "outfile.inc", "interpolations": [{ "param_w": int_param, "param_g": int_param }], "family": 2, "delta_s": 0.1, } interp_relperm.process_config(config) outfile_str = Path("outfile.inc").read_text() outfile_df = satfunc.df(outfile_str) if expected_file is not None: expected_df = satfunc.df(Path(expected_file).read_text()) pd.testing.assert_frame_equal(outfile_df, expected_df) else: # Use test function from pyscal to assert that the produced file is # valid for Eclipse (not testing numerically that the interpolation # is correct) sat_table_str_ok(outfile_str)
def mock_family_1(): columns = [ "SATNUM", "Nw", "Now", "Ng", "Nog", "swl", "a", "b", "poro_ref", "perm_ref", "drho", ] dframe_pess = pd.DataFrame( columns=columns, data=[[1, 1, 1, 1, 1, 0.1, 2, -2, 0.25, 100, 150]], ) dframe_base = pd.DataFrame( columns=columns, data=[[1, 2, 2, 2, 2, 0.1, 2, -2, 0.25, 200, 150]], ) dframe_opt = pd.DataFrame( columns=columns, data=[[1, 3, 3, 3, 3, 0.1, 2, -2, 0.25, 300, 150]], ) PyscalFactory.create_pyscal_list(dframe_pess).dump_family_1("pess.inc") PyscalFactory.create_pyscal_list(dframe_base).dump_family_1("base.inc") PyscalFactory.create_pyscal_list(dframe_opt).dump_family_1("opt.inc")
def test_dump(): """Test dumping Eclipse include data to file""" testdir = Path(__file__).absolute().parent relperm_data = PyscalFactory.load_relperm_df( testdir / "data/relperm-input-example.xlsx") pyscal_list = PyscalFactory.create_pyscal_list(relperm_data) fam1 = pyscal_list.dump_family_1() sat_table_str_ok(fam1) fam2 = pyscal_list.dump_family_2() sat_table_str_ok(fam2)
def test_load_scalrec(tmpdir): """Load a SATNUM range from xlsx""" testdir = Path(__file__).absolute().parent scalrec_data = PyscalFactory.load_relperm_df( testdir / "data/scal-pc-input-example.xlsx") # Also check that we can read the old excel format scalrec_data_legacy_xls = PyscalFactory.load_relperm_df( testdir / "data/scal-pc-input-example.xls") pd.testing.assert_frame_equal(scalrec_data, scalrec_data_legacy_xls) scalrec_list = PyscalFactory.create_scal_recommendation_list(scalrec_data) wog_list = scalrec_list.interpolate(-0.3) with pytest.raises((AssertionError, ValueError)): scalrec_list.interpolate(-1.1) assert wog_list.pyscaltype == WaterOilGas with pytest.raises(TypeError): scalrec_list.dump_family_1() with pytest.raises(TypeError): scalrec_list.dump_family_2() with pytest.raises(TypeError): scalrec_list.SWOF() scalrec_list.interpolate([-1, 1, 0]) with pytest.raises((AssertionError, ValueError)): scalrec_list.interpolate([-1, 0, 1.1]) # Not enough interpolation parameters with pytest.raises(ValueError): scalrec_list.interpolate([-1, 1]) with pytest.raises(ValueError): scalrec_list.interpolate([-1, 1, 0, 0]) scalrec_list.interpolate(-1, 1) scalrec_list.interpolate(-1, [0, -1, 1]) with pytest.raises(ValueError): scalrec_list.interpolate(1, [-1, 1, 0, 0]) # Test slicing the scalrec to base, this is relevant for # API usage. base_data = scalrec_data[scalrec_data["CASE"] == "base"].drop("CASE", axis=1) PyscalFactory.load_relperm_df(base_data) # Ensure no errors. pyscal_list = PyscalFactory.create_pyscal_list(base_data) assert "LET" in pyscal_list.dump_family_1()
def test_corey_let_mix(): """Test that we can supply a dataframe where some SATNUMs have Corey and others have LET""" dframe = pd.DataFrame( columns=["SATNUM", "Nw", "Now", "Lw", "Ew", "Tw", "Ng", "Nog"], data=[[1, 2, 2, np.nan, np.nan, np.nan, 1, 1], [2, np.nan, 3, 1, 1, 1, 2, 2]], ) relperm_data = PyscalFactory.load_relperm_df(dframe) p_list = PyscalFactory.create_pyscal_list(relperm_data, h=0.2) swof1 = p_list.pyscal_list[0].SWOF() swof2 = p_list.pyscal_list[1].SWOF() assert "Corey krw" in swof1 assert "Corey krow" in swof1 assert "LET krw" in swof2 assert "Corey krow" in swof2
def test_dump(): """Test dumping Eclipse include data to file""" if "__file__" in globals(): # Easen up copying test code into interactive sessions testdir = os.path.dirname(os.path.abspath(__file__)) else: testdir = os.path.abspath(".") relperm_data = PyscalFactory.load_relperm_df( testdir + "/data/relperm-input-example.xlsx") pyscal_list = PyscalFactory.create_pyscal_list(relperm_data) fam1 = pyscal_list.dump_family_1() sat_table_str_ok(fam1) fam2 = pyscal_list.dump_family_2() sat_table_str_ok(fam2)
def test_gaswater(): """Test list of gas-water objects""" dframe = pd.DataFrame( columns=["SATNUM", "NW", "NG", "TAG"], data=[[1, 2, 2, "thetag"], [2, 3, 3, "othertag"]], ) pyscal_list = PyscalFactory.create_pyscal_list( PyscalFactory.load_relperm_df(dframe), h=0.1) assert pyscal_list.pyscaltype == GasWater dump = pyscal_list.dump_family_2() assert "SATNUM 2" in dump assert "SATNUM 1" in dump assert "SATNUM 3" not in dump assert "SWFN" in dump assert "SGFN" in dump assert "othertag" in dump assert "thetag" in dump with pytest.raises(ValueError): # Does not make sense for GasWater: pyscal_list.dump_family_1()
def test_many_nans(): """Excel or oocalc sometimes saves a xlsx file that gives all NaN rows and all-NaN columns, maybe some column setting that triggers Pandas to load them as actual columns/rows. Ensure we handle extra Nans in both directions""" nanframe = pd.DataFrame([ { "SATNUM": 1, "nw": 2, "now": 2, "Unnamed: 15": np.nan }, { "SATNUM": np.nan, "nw": np.nan, "now": np.nan, "Unnamed: 15": np.nan }, ]) wateroil_list = PyscalFactory.create_pyscal_list( PyscalFactory.load_relperm_df(nanframe)) assert len(wateroil_list) == 1 sat_table_str_ok(wateroil_list.SWOF())
def test_capillary_pressure(): """Test that we recognize capillary pressure parametrizations""" dframe = pd.DataFrame( columns=[ "SATNUM", "nw", "now", "swl", "a", "b", "PORO_reF", "PERM_ref", "drho", ], data=[[1, 1, 1, 0.05, 3.6, -3.5, 0.25, 15, 150]], ) pyscal_list = PyscalFactory.create_pyscal_list( PyscalFactory.load_relperm_df(dframe)) swof = pyscal_list.dump_family_1() assert "Simplified J-function" in swof assert "petrophysical" not in swof dframe = pd.DataFrame( columns=[ "SATNUM", "nw", "now", "swl", "a_petro", "b_petro", "PORO_reF", "PERM_ref", "drho", ], data=[[1, 1, 1, 0.05, 3.6, -3.5, 0.25, 15, 150]], ) pyscal_list = PyscalFactory.create_pyscal_list( PyscalFactory.load_relperm_df(dframe)) swof = pyscal_list.dump_family_1() assert "Simplified J-function" in swof assert "petrophysical" in swof dframe = pd.DataFrame( columns=[ "SATNUM", "nw", "now", "swl", "a", "b", "PORO", "PERM", "sigma_COStau", ], data=[[1, 1, 1, 0.05, 3.6, -3.5, 0.25, 15, 30]], ) pyscal_list = PyscalFactory.create_pyscal_list( PyscalFactory.load_relperm_df(dframe)) swof = pyscal_list.dump_family_1() assert "normalized J-function" in swof assert "sigma_costau" in swof assert "petrophysical" not in swof
def test_mock(tmpdir): """Mocked pyscal-generated input files. Note that this is using pyscal both for dumping to disk and parsing from disk, and is thus not representative for how flexible the code is for reading from include files not originating in pyscal. """ tmpdir.chdir() columns = [ "SATNUM", "Nw", "Now", "Ng", "Nog", "swl", "a", "b", "poro_ref", "perm_ref", "drho", ] dframe_pess = pd.DataFrame( columns=columns, data=[[1, 1, 1, 1, 1, 0.1, 2, -2, 0.25, 100, 150]], ) dframe_base = pd.DataFrame( columns=columns, data=[[1, 2, 2, 2, 2, 0.1, 2, -2, 0.25, 200, 150]], ) dframe_opt = pd.DataFrame( columns=columns, data=[[1, 3, 3, 3, 3, 0.1, 2, -2, 0.25, 300, 150]], ) PyscalFactory.create_pyscal_list(dframe_pess).dump_family_1("pess.inc") PyscalFactory.create_pyscal_list(dframe_base).dump_family_1("base.inc") PyscalFactory.create_pyscal_list(dframe_opt).dump_family_1("opt.inc") config = { "base": ["base.inc"], "low": ["pess.inc"], "high": ["opt.inc"], "result_file": "outfile.inc", "interpolations": [{ "param_w": -0.5, "param_g": 0.5 }], "delta_s": 0.1, } interp_relperm.process_config(config) interp_relperm.process_config(config) outfile_df = satfunc.df(open("outfile.inc").read(), ntsfun=1) assert set(outfile_df["KEYWORD"].unique()) == {"SWOF", "SGOF"} assert outfile_df["SW"].sum() > 0 assert outfile_df["SG"].sum() > 0 assert outfile_df["KRW"].sum() > 0 assert outfile_df["KROW"].sum() > 0 assert outfile_df["KRG"].sum() > 0 assert outfile_df["KROG"].sum() > 0 assert outfile_df["PCOW"].sum() > 0
def test_explicit_df(): """Test some dataframes, check the error messages given""" dframe = pd.DataFrame(columns=["satnum"], data=[[1], [2]]) with pytest.raises(ValueError): # SATNUM column must be upper case, or should we allow lowercase?? relperm_data = PyscalFactory.load_relperm_df(dframe) dframe = pd.DataFrame(columns=["SATNUM"], data=[[0], [1]]) with pytest.raises(ValueError): # SATNUM must start at 1. relperm_data = PyscalFactory.load_relperm_df(dframe) dframe = pd.DataFrame(columns=["SATNUM"], data=[[1], ["foo"]]) with pytest.raises(ValueError): # SATNUM must contain only integers relperm_data = PyscalFactory.load_relperm_df(dframe) dframe = pd.DataFrame(columns=["SATNUM", "nw", "now"], data=[[1.01, 1, 1], [2.01, 1, 1]]) # This one will pass, as these can be converted to ints relperm_data = PyscalFactory.load_relperm_df(dframe) dframe = pd.DataFrame(columns=["SATNUM", "nw", "now"], data=[[1.01, 1, 1], [1.3, 1, 1]]) with pytest.raises(ValueError): # complains about non-uniqueness in SATNUM relperm_data = PyscalFactory.load_relperm_df(dframe) dframe = pd.DataFrame(columns=["SATNUM"], data=[[1], [2]]) with pytest.raises(ValueError): # Not enough data relperm_data = PyscalFactory.load_relperm_df(dframe) # Minimal amount of data: dframe = pd.DataFrame(columns=["SATNUM", "Nw", "Now"], data=[[1, 2, 2]]) relperm_data = PyscalFactory.load_relperm_df(dframe) p_list = PyscalFactory.create_pyscal_list(relperm_data, h=0.2) p_list.dump_family_1() with pytest.raises(ValueError): p_list.dump_family_2() # Case insensitive for parameters dframe = pd.DataFrame(columns=["SATNUM", "nw", "now"], data=[[1, 2, 2]]) relperm_data = PyscalFactory.load_relperm_df(dframe) p_list = PyscalFactory.create_pyscal_list(relperm_data, h=0.2) p_list.dump_family_1() # Minimal wateroilgas dframe = pd.DataFrame(columns=["SATNUM", "Nw", "Now", "ng", "nOG"], data=[[1, 2, 2, 2, 2]]) relperm_data = PyscalFactory.load_relperm_df(dframe) p_list = PyscalFactory.create_pyscal_list(relperm_data, h=0.2) relperm_str = p_list.dump_family_1() assert "SWOF" in relperm_str assert "SGOF" in relperm_str assert "SLGOF" not in relperm_str assert "SOF3" not in relperm_str # Minimal wateroilgas with pc dframe = pd.DataFrame( columns=[ "SATNUM", "swl", "Nw", "Now", "ng", "nOG", "a", "b", "poro_ref", "perm_ref", "drho", ], data=[[1, 0.1, 2, 2, 2, 2, 1.5, -0.5, 0.1, 100, 300]], ) relperm_data = PyscalFactory.load_relperm_df(dframe) p_list = PyscalFactory.create_pyscal_list(relperm_data, h=0.2) relperm_str = p_list.dump_family_1() assert "SWOF" in relperm_str assert "Simplified" in relperm_str # Bad practice, testing for stuff in comments assert "a=1.5" in relperm_str
def test_load_relperm_df(tmpdir, caplog): """Test loading of dataframes with validation from excel or from csv""" testdir = Path(__file__).absolute().parent scalfile_xls = testdir / "data/scal-pc-input-example.xlsx" scaldata = PyscalFactory.load_relperm_df(scalfile_xls) with pytest.raises(IOError): PyscalFactory.load_relperm_df("not-existing-file") with pytest.raises(ValueError, match="Non-existing sheet-name"): PyscalFactory.load_relperm_df(scalfile_xls, sheet_name="foo") assert "SATNUM" in scaldata assert "CASE" in scaldata assert not scaldata.empty tmpdir.chdir() scaldata.to_csv("scal-input.csv") scaldata_fromcsv = PyscalFactory.load_relperm_df("scal-input.csv") assert "CASE" in scaldata_fromcsv assert not scaldata_fromcsv.empty scaldata_fromdf = PyscalFactory.load_relperm_df(scaldata_fromcsv) assert "CASE" in scaldata_fromdf assert "SATNUM" in scaldata_fromdf assert len(scaldata_fromdf) == len(scaldata_fromcsv) == len(scaldata) scaldata_fromcsv = PyscalFactory.load_relperm_df("scal-input.csv", sheet_name="foo") assert "Sheet name only relevant for XLSX files, ignoring foo" in caplog.text with pytest.raises(ValueError, match="Unsupported argument"): PyscalFactory.load_relperm_df(dict(foo=1)) # Perturb the dataframe, this should trigger errors with pytest.raises(ValueError): PyscalFactory.load_relperm_df(scaldata.drop("SATNUM", axis="columns")) wrongsatnums = scaldata.copy() wrongsatnums["SATNUM"] = wrongsatnums["SATNUM"] * 2 with pytest.raises(ValueError): PyscalFactory.load_relperm_df(wrongsatnums) wrongsatnums = scaldata.copy() wrongsatnums["SATNUM"] = wrongsatnums["SATNUM"].astype(int) wrongsatnums = wrongsatnums[wrongsatnums["SATNUM"] > 2] with pytest.raises(ValueError): PyscalFactory.load_relperm_df(wrongsatnums) wrongcases = scaldata.copy() wrongcases["CASE"] = wrongcases["CASE"] + "ffooo" with pytest.raises(ValueError): PyscalFactory.load_relperm_df(wrongcases) with pytest.raises(ValueError): PyscalFactory.load_relperm_df( scaldata.drop(["Lw", "Lg"], axis="columns")) # Insert a NaN, this replicates what happens if cells are merged mergedcase = scaldata.copy() mergedcase.loc[3, "SATNUM"] = np.nan with pytest.raises(ValueError): PyscalFactory.load_relperm_df(mergedcase) relpermfile_xls = testdir / "data/relperm-input-example.xlsx" relpermdata = PyscalFactory.load_relperm_df(relpermfile_xls) assert "TAG" in relpermdata assert "SATNUM" in relpermdata assert "satnum" not in relpermdata # always converted to upper-case assert len(relpermdata) == 3 swof_str = PyscalFactory.create_pyscal_list(relpermdata, h=0.2).SWOF() assert "Åre 1.8" in swof_str assert "SATNUM 2" in swof_str # Autogenerated in SWOF, generated by factory assert "SATNUM 3" in swof_str assert "foobar" in swof_str # Random string injected in xlsx. # Make a dummy text file Path("dummy.txt").write_text("foo\nbar, com") with pytest.raises(ValueError): PyscalFactory.load_relperm_df("dummy.txt") # Make an empty csv file Path("empty.csv").write_text("") # A ValueError would also make sense here assert PyscalFactory.load_relperm_df("empty.csv").empty # Merge tags and comments if both are supplied Path("tagandcomment.csv").write_text( "SATNUM,nw,now,tag,comment\n1,1,1,a-tag,a-comment") tagandcomment_df = PyscalFactory.load_relperm_df("tagandcomment.csv") assert (tagandcomment_df["TAG"].values[0] == "SATNUM 1 tag: a-tag; comment: a-comment") # Missing SATNUMs: Path("wrongsatnum.csv").write_text("SATNUM,nw,now\n1,1,1\n3,1,1") with pytest.raises(ValueError, match="Missing SATNUMs?"): PyscalFactory.load_relperm_df("wrongsatnum.csv") # Missing SATNUMs, like merged cells: Path("mergedcells.csv").write_text( "CASE,SATNUM,nw,now\nlow,,1,1\nlow,1,2,2\nlow,,3,32") with pytest.raises(ValueError, match="Found not-a-number"): PyscalFactory.load_relperm_df("mergedcells.csv") # Missing SATNUMs, like merged cells: Path("mergedcellscase.csv").write_text( "CASE,SATNUM,nw,now\n,1,1,1\nlow,1,2,2\n,1,3,32") with pytest.raises(ValueError, match="Found not-a-number"): PyscalFactory.load_relperm_df("mergedcellscase.csv")
def test_fast(): """Test fast mode for SCALrecommendation""" testdir = Path(__file__).absolute().parent scalrec_data = PyscalFactory.load_relperm_df( testdir / "data/scal-pc-input-example.xlsx") scalrec_list_fast = PyscalFactory.create_scal_recommendation_list( scalrec_data, fast=True) for item in scalrec_list_fast: assert item.fast assert item.low.fast assert item.base.fast assert item.high.fast wog_list_fast = scalrec_list_fast.interpolate(-0.5) for item in wog_list_fast: assert item.fast # WaterOilGas list dframe = pd.DataFrame( columns=["SATNUM", "nw", "now", "ng", "nog"], data=[ [1, 2, 2, 2, 2], [2, 2, 2, 2, 2], [3, 2, 2, 2, 2], ], ) relperm_data = PyscalFactory.load_relperm_df(dframe) p_list_fast = PyscalFactory.create_pyscal_list(relperm_data, h=0.2, fast=True) for item in p_list_fast: assert item.fast # GasOil list input_dframe = dframe[["SATNUM", "ng", "nog"]].copy() relperm_data = PyscalFactory.load_relperm_df(input_dframe) p_list_fast = PyscalFactory.create_pyscal_list(relperm_data, h=0.2, fast=True) for item in p_list_fast: assert item.fast # WaterOil list input_dframe = dframe[["SATNUM", "nw", "now"]].copy() relperm_data = PyscalFactory.load_relperm_df(input_dframe) p_list_fast = PyscalFactory.create_pyscal_list(relperm_data, h=0.2, fast=True) for item in p_list_fast: assert item.fast # GasWater list input_dframe = dframe[["SATNUM", "nw", "ng"]].copy() relperm_data = PyscalFactory.load_relperm_df(input_dframe) p_list_fast = PyscalFactory.create_pyscal_list(relperm_data, h=0.2, fast=True) for item in p_list_fast: assert item.fast # Testing with "fast" column in dataframe # Currently the choice is to only implement fast mode # as a global option. This column should do nothing now. # One could imagine it implemented # for individual SATNUM regions at a later stage dframe = pd.DataFrame( columns=["SATNUM", "nw", "now", "ng", "nog", "fast"], data=[ [1, 2, 2, 2, 2, True], [2, 2, 2, 2, 2, False], [3, 2, 2, 2, 2, True], ], ) relperm_data = PyscalFactory.load_relperm_df(dframe) assert "fast" in relperm_data p_list = PyscalFactory.create_pyscal_list(relperm_data, h=0.2) for item in p_list: assert not item.fast
def test_error_messages_pr_satnum(caplog): """When errors are somewhere in a big dataframe, we should provide hints to the user for where to look""" # A string instead of a number in SATNUM 2 dframe = pd.DataFrame(columns=["SATNUM", "nw", "now"], data=[[1, 1, 1], [2, "foo", 1]]) with pytest.raises(ValueError, match="SATNUM 2"): PyscalFactory.create_pyscal_list(dframe, h=0.2) dframe = pd.DataFrame(columns=["SATNUM", "nw", "now"], data=[[1, 1, 1], [2, np.nan, 1]]) with pytest.raises(ValueError, match="SATNUM 2"): PyscalFactory.create_pyscal_list(dframe, h=0.2) # Mixed up order: dframe = pd.DataFrame(columns=["SATNUM", "nw", "now"], data=[[2, np.nan, 1], [1, 1, 1]]) with pytest.raises(ValueError, match="SATNUM 2"): PyscalFactory.create_pyscal_list(dframe, h=0.2) # Gasoil list dframe = pd.DataFrame(columns=["SATNUM", "ng", "nog"], data=[[1, 1, 1], [2, np.nan, 1]]) with pytest.raises(ValueError, match="SATNUM 2"): PyscalFactory.create_pyscal_list(dframe, h=0.2) # Gaswater list: dframe = pd.DataFrame(columns=["SATNUM", "ng", "nw"], data=[[1, 1, 1], [2, np.nan, 1]]) with pytest.raises(ValueError, match="SATNUM 2"): PyscalFactory.create_pyscal_list(dframe, h=0.2) # Wateroilgas list: dframe = pd.DataFrame( columns=["SATNUM", "nw", "now", "ng", "nog"], data=[[1, 1, 1, 1, 1], [2, np.nan, 1, 2, 3]], ) with pytest.raises(ValueError, match="SATNUM 2"): PyscalFactory.create_pyscal_list(dframe, h=0.2) # SCAL rec list: dframe = pd.DataFrame( columns=["SATNUM", "CASE", "nw", "now", "ng", "nog"], data=[ [1, "low", 1, 1, 1, 1], [1, "base", 1, 1, 1, 1], [1, "high", 1, 1, 1, 1], [2, "low", np.nan, 1, 2, 3], [2, "base", 2, 1, 2, 3], [2, "high", 3, 1, 2, 3], ], ) # The error should hint both to SATNUM and to low/base/high with pytest.raises(ValueError, match="SATNUM 2"): PyscalFactory.create_scal_recommendation_list(dframe, h=0.2) with pytest.raises(ValueError, match="Problem with low"): PyscalFactory.create_scal_recommendation_list(dframe, h=0.2)
def test_mock_two_satnums_via_files(tmpdir): """Mocked pyscal-generated input files. Note that this is using pyscal both for dumping to disk and parsing from disk, and is thus not representative for how flexible the code is for reading from include files not originating in pyscal. """ # pylint: disable=no-value-for-parameter tmpdir.chdir() PyscalFactory.create_pyscal_list( TWO_SATNUM_PYSCAL_MOCK.loc["low"]).dump_family_1("pess.inc") PyscalFactory.create_pyscal_list( TWO_SATNUM_PYSCAL_MOCK.loc["base"]).dump_family_1("base.inc") PyscalFactory.create_pyscal_list( TWO_SATNUM_PYSCAL_MOCK.loc["high"]).dump_family_1("opt.inc") config = { "base": ["base.inc"], "low": ["pess.inc"], "high": ["opt.inc"], "result_file": "outfile.inc", "interpolations": [{ "param_w": -0.5, "param_g": 0.5 }], "delta_s": 0.1, } interp_relperm.process_config(config) outfile_str = Path("outfile.inc").read_text() # Assert things about the comments emitted by pyscal when interpolating: # This is used as a proxy for asserting that interpolation parameters # are used for the correct satnums assert outfile_str.find("SCAL recommendation interpolation to 0.5") assert outfile_str.find("SCAL recommendation interpolation to -0.5") # SWOF comes before SGOF: assert outfile_str.find("to -0.5") < outfile_str.find("to 0.5") outfile_df = satfunc.df(outfile_str, ntsfun=2) assert set(outfile_df["KEYWORD"].unique()) == {"SWOF", "SGOF"} assert set(outfile_df["SATNUM"].unique()) == {1, 2} config = { "base": ["base.inc"], "low": ["pess.inc"], "high": ["opt.inc"], "result_file": "outfile.inc", "interpolations": [ { "tables": [1], "param_w": -0.9, "param_g": -0.5 }, { "tables": [2], "param_w": 0.5, "param_g": 0.8 }, ], "delta_s": 0.1, } interp_relperm.process_config(config) outfile_str = open("outfile.inc").read() assert outfile_str.find("to -0.9") < outfile_str.find("to 0.5") assert outfile_str.find("to 0.5") < outfile_str.find("to -0.5") assert outfile_str.find("to 0.5") < outfile_str.find("to 0.8") config = { "base": ["base.inc"], "low": ["pess.inc"], "high": ["opt.inc"], "result_file": "outfile.inc", "interpolations": [ # This is a user error, the latter will override the first { "param_w": -0.9, "param_g": -0.5 }, { "param_w": 0.5, "param_g": 0.8 }, ], "delta_s": 0.1, } interp_relperm.process_config(config) outfile_str = open("outfile.inc").read() assert "interpolation to -0.9" not in outfile_str assert "interpolation to 0.8" in outfile_str config = { "base": ["base.inc"], "low": ["pess.inc"], "high": ["opt.inc"], "result_file": "outfile.inc", "interpolations": [ # Here the user intentionally overwrites the first: { "param_w": -0.9, "param_g": -0.5 }, { "tables": [], "param_w": 0.5, "param_g": 0.8 }, ], "delta_s": 0.1, } interp_relperm.process_config(config) outfile_str = open("outfile.inc").read() assert "interpolation to -0.9" not in outfile_str assert "interpolation to 0.8" in outfile_str
def test_swl_from_height(): """Test that can initialize swl from capillary pressure height""" df_columns = [ "SATNUM", "nw", "now", "swl", "swlheight", "swirr", "a", "b", "PORO_reF", "PERM_ref", "drho", ] dframe = pd.DataFrame( columns=df_columns, data=[[1, 1, 1, np.nan, 300, 0.00, 3.6, -3.5, 0.25, 15, 150]], ) pyscal_list = PyscalFactory.create_pyscal_list( PyscalFactory.load_relperm_df(dframe)) # Mix swlheight init and direct swl-init: assert np.isclose(pyscal_list[1].swl, 0.157461) dframe = pd.DataFrame( columns=df_columns, data=[ [1, 1, 1, np.nan, 300, 0.00, 3.6, -3.5, 0.25, 15, 150], [2, 1, 1, 0.3, np.nan, 0.00, 3.6, -3.5, 0.25, 15, 150], ], ) pyscal_list = PyscalFactory.create_pyscal_list( PyscalFactory.load_relperm_df(dframe)) assert np.isclose(pyscal_list[1].swl, 0.157461) assert np.isclose(pyscal_list[2].swl, 0.3) # Ambiguous, swl and swlheight both supplied: dframe = pd.DataFrame( columns=df_columns, data=[[1, 1, 1, 0.3, 300, 0.00, 3.6, -3.5, 0.25, 15, 150]], ) with pytest.raises(ValueError): PyscalFactory.create_pyscal_list(PyscalFactory.load_relperm_df(dframe)) # WaterOilGas (gasoil is also dependant on the computed swl) df_wog_columns = [ "SATNUM", "nw", "now", "ng", "nog", "swlheight", "swirr", "a", "b", "PORO_reF", "PERM_ref", "drho", ] dframe = pd.DataFrame( columns=df_wog_columns, data=[[1, 1, 1, 2, 2, 300, 0.00, 3.6, -3.5, 0.25, 15, 150]], ) pyscal_list = PyscalFactory.create_pyscal_list( PyscalFactory.load_relperm_df(dframe)) assert np.isclose(pyscal_list[1].wateroil.swl, 0.157461) assert np.isclose(pyscal_list[1].gasoil.swl, 0.157461) # Test for GasWater: df_gw_columns = [ "SATNUM", "nw", "ng", "swlheight", "swirr", "a", "b", "PORO_REF", "PERM_REF", "drho", ] dframe = pd.DataFrame( columns=df_gw_columns, data=[[1, 2, 3, 300, 0.00, 3.6, -3.5, 0.25, 15, 150]], ) pyscal_list = PyscalFactory.create_pyscal_list( PyscalFactory.load_relperm_df(dframe)) assert np.isclose(pyscal_list[1].wateroil.swl, 0.157461) assert np.isclose(pyscal_list[1].swl, 0.157461)
def pyscal_main( parametertable, verbose=False, output="relperm.inc", delta_s=None, int_param_wo=None, int_param_go=None, sheet_name=None, slgof=False, family2=False, ): """A "main()" method not relying on argparse. This can be used for testing, and also by an ERT forward model, e.g. in semeio (github.com/equinor/semeio) Args: parametertable (string): Filename (CSV or XLSX) to load verbose (bool): verbose or not output (string): Output filename delta_s (float): Saturation step-length int_param_wo (list): Interpolation params for wateroil int_param_go (list): Interpolation params for gasoil sheet_name (string): Which sheet in XLSX file slgof (bool): Use SLGOF family2 (bool): Dump family 2 keywords """ if verbose: # Fixme: Logging level is not inherited in called modules. logger.setLevel(logging.INFO) if sheet_name: logger.info("Loading data from %s and sheetname %s", parametertable, sheet_name) else: logger.info("Loading data from %s", parametertable) scalinput_df = PyscalFactory.load_relperm_df(parametertable, sheet_name=sheet_name) logger.info("Input data:\n%s", scalinput_df.to_string(index=False)) if int_param_go is not None and int_param_wo is None: logger.error("Don't use int_param_go alone, only int_param_wo") raise ValueError if "SATNUM" not in scalinput_df: logger.error("There is no column called SATNUM in the input data") raise ValueError if "CASE" in scalinput_df: # Then we should do interpolation if int_param_wo is None: logger.error("No interpolation parameters provided") raise ValueError scalrec_list = PyscalFactory.create_scal_recommendation_list( scalinput_df, h=delta_s) logger.info( "Interpolating, wateroil=%s, gasoil=%s", str(int_param_wo), str(int_param_go), ) wog_list = scalrec_list.interpolate(int_param_wo, int_param_go, h=delta_s) else: wog_list = PyscalFactory.create_pyscal_list( scalinput_df, h=delta_s) # can be both water-oil, water-oil-gas, or gas-water if (int_param_wo is not None or int_param_go is not None) and "CASE" not in scalinput_df: logger.error( "Interpolation parameter provided but no CASE column in input data" ) raise ValueError if not family2: logger.info("Generating family 1 keywords.") if output == "-": print(wog_list.dump_family_1(slgof=slgof)) else: wog_list.dump_family_1(filename=output, slgof=slgof) print("Written to " + output) else: logger.info("Generating family 2 keywords") if output == "-": print(wog_list.dump_family_2()) else: wog_list.dump_family_2(filename=output) print("Written to " + output)
def test_load_relperm_df(tmpdir): """Test loading of dataframes with validation from excel or from csv""" testdir = Path(__file__).absolute().parent scalfile_xls = testdir / "data/scal-pc-input-example.xlsx" scaldata = PyscalFactory.load_relperm_df(scalfile_xls) with pytest.raises(IOError): PyscalFactory.load_relperm_df("not-existing-file") assert "SATNUM" in scaldata assert "CASE" in scaldata assert not scaldata.empty tmpdir.chdir() scaldata.to_csv("scal-input.csv") scaldata_fromcsv = PyscalFactory.load_relperm_df("scal-input.csv") assert "CASE" in scaldata_fromcsv assert not scaldata_fromcsv.empty scaldata_fromdf = PyscalFactory.load_relperm_df(scaldata_fromcsv) assert "CASE" in scaldata_fromdf assert "SATNUM" in scaldata_fromdf assert len(scaldata_fromdf) == len(scaldata_fromcsv) == len(scaldata) # Perturb the dataframe, this should trigger errors with pytest.raises(ValueError): PyscalFactory.load_relperm_df(scaldata.drop("SATNUM", axis="columns")) wrongsatnums = scaldata.copy() wrongsatnums["SATNUM"] = wrongsatnums["SATNUM"] * 2 with pytest.raises(ValueError): PyscalFactory.load_relperm_df(wrongsatnums) wrongsatnums = scaldata.copy() wrongsatnums["SATNUM"] = wrongsatnums["SATNUM"].astype(int) wrongsatnums = wrongsatnums[wrongsatnums["SATNUM"] > 2] with pytest.raises(ValueError): PyscalFactory.load_relperm_df(wrongsatnums) wrongcases = scaldata.copy() wrongcases["CASE"] = wrongcases["CASE"] + "ffooo" with pytest.raises(ValueError): PyscalFactory.load_relperm_df(wrongcases) with pytest.raises(ValueError): PyscalFactory.load_relperm_df(scaldata.drop(["Lw", "Lg"], axis="columns")) # Insert a NaN, this replicates what happens if cells are merged mergedcase = scaldata.copy() mergedcase.loc[3, "SATNUM"] = np.nan with pytest.raises(ValueError): PyscalFactory.load_relperm_df(mergedcase) relpermfile_xls = testdir / "data/relperm-input-example.xlsx" relpermdata = PyscalFactory.load_relperm_df(relpermfile_xls) assert "TAG" in relpermdata assert "SATNUM" in relpermdata assert "satnum" not in relpermdata # always converted to upper-case assert len(relpermdata) == 3 swof_str = PyscalFactory.create_pyscal_list(relpermdata, h=0.2).SWOF() assert "Åre 1.8" in swof_str assert "SATNUM 2" in swof_str # Autogenerated in SWOF, generated by factory assert "SATNUM 3" in swof_str assert "foobar" in swof_str # Random string injected in xlsx. # Make a dummy text file with open("dummy.txt", "w") as fhandle: fhandle.write("foo\nbar, com") with pytest.raises(ValueError): PyscalFactory.load_relperm_df("dummy.txt")
def test_df(): """Test dataframe dumps""" testdir = Path(__file__).absolute().parent scalrec_data = PyscalFactory.load_relperm_df( testdir / "data/scal-pc-input-example.xlsx") scalrec_list = PyscalFactory.create_scal_recommendation_list(scalrec_data) wog_list = scalrec_list.interpolate(-0.3) # Test dataframe dumps: dframe = scalrec_list.df() assert "SG" in dframe assert "KRG" in dframe assert "KROG" in dframe assert "PCOW" in dframe if "PCOG" in dframe: # Allow PCOG to be included later. assert dframe["PCOG"].sum() == 0 assert "KRW" in dframe assert "KROW" in dframe assert "SATNUM" in dframe assert dframe["SATNUM"].min() == 1 assert len(dframe["SATNUM"].unique()) == len(scalrec_list) assert set(dframe["CASE"]) == set(["pess", "base", "opt"]) assert dframe["SATNUM"].max() == len(scalrec_list) if HAVE_ECL2DF: # Test using ecl2df to do the include file printing. First we need to # massage the dataframe into what ecl2df can handle: base_df_swof = (dframe.set_index("CASE").loc["base"][[ "SW", "KRW", "KROW", "PCOW", "SATNUM" ]].assign(KEYWORD="SWOF").dropna().reset_index(drop=True)) ecl_inc = ecl2df.satfunc.df2ecl(base_df_swof) dframe_from_inc = ecl2df.satfunc.df(ecl_inc) pd.testing.assert_frame_equal(base_df_swof, dframe_from_inc) # Test also SGOF base_df_sgof = (dframe.set_index("CASE").loc["base"][[ "SG", "KRG", "KROG", "SATNUM" ]].assign(KEYWORD="SGOF", PCOG=0.0).dropna().reset_index(drop=True)) ecl_inc = ecl2df.satfunc.df2ecl(base_df_sgof) dframe_from_inc = ecl2df.satfunc.df(ecl_inc) pd.testing.assert_frame_equal(base_df_sgof, dframe_from_inc, check_like=True) # WaterOilGasList: dframe = wog_list.df() assert "SG" in dframe assert "KRG" in dframe assert "KROG" in dframe assert "PCOW" in dframe assert "PCOG" in dframe # This gets included through interpolation assert "KRW" in dframe assert "KROW" in dframe assert "SATNUM" in dframe assert dframe["SATNUM"].min() == 1 assert len(dframe["SATNUM"].unique()) == len(wog_list) assert "CASE" not in dframe assert dframe["SATNUM"].max() == len(wog_list) # WaterOil list input_dframe = pd.DataFrame(columns=["SATNUM", "Nw", "Now"], data=[[1, 2, 2]]) relperm_data = PyscalFactory.load_relperm_df(input_dframe) p_list = PyscalFactory.create_pyscal_list(relperm_data, h=0.2) dframe = p_list.df() assert "SW" in dframe assert "KRW" in dframe assert "KROW" in dframe assert "PCOW" not in dframe # to be interpreted as zero assert "SATNUM" in dframe assert len(dframe.columns) == 4 assert not dframe.empty # GasOil list input_dframe = pd.DataFrame(columns=["SATNUM", "Ng", "Nog"], data=[[1, 2, 2]]) relperm_data = PyscalFactory.load_relperm_df(input_dframe) p_list = PyscalFactory.create_pyscal_list(relperm_data, h=0.2) dframe = p_list.df() assert "SG" in dframe assert "KRG" in dframe assert "KROG" in dframe assert "PCOG" not in dframe # to be interpreted as zero assert "SATNUM" in dframe assert len(dframe.columns) == 4 assert not dframe.empty
def test_mock_two_satnums(tmpdir): """Mocked pyscal-generated input files. Note that this is using pyscal both for dumping to disk and parsing from disk, and is thus not representative for how flexible the code is for reading from include files not originating in pyscal. """ # pylint: disable=no-value-for-parameter tmpdir.chdir() columns = [ "SATNUM", "Nw", "Now", "Ng", "Nog", "swl", "a", "b", "poro_ref", "perm_ref", "drho", ] dframe_pess = pd.DataFrame( columns=columns, data=[ [1, 1, 1, 1, 1, 0.1, 2, -2, 0.25, 100, 150], [1, 1, 1, 1, 1, 0.1, 2, -2, 0.25, 100, 150], ], ) dframe_base = pd.DataFrame( columns=columns, data=[ [1, 2, 2, 2, 2, 0.1, 2, -2, 0.25, 200, 150], [1, 2, 2, 2, 2, 0.1, 2, -2, 0.25, 200, 150], ], ) dframe_opt = pd.DataFrame( columns=columns, data=[ [1, 3, 3, 3, 3, 0.1, 2, -2, 0.25, 300, 150], [1, 3, 3, 3, 3, 0.1, 2, -2, 0.25, 300, 150], ], ) PyscalFactory.create_pyscal_list(dframe_pess).dump_family_1("pess.inc") PyscalFactory.create_pyscal_list(dframe_base).dump_family_1("base.inc") PyscalFactory.create_pyscal_list(dframe_opt).dump_family_1("opt.inc") config = { "base": ["base.inc"], "low": ["pess.inc"], "high": ["opt.inc"], "result_file": "outfile.inc", "interpolations": [{ "param_w": -0.5, "param_g": 0.5 }], "delta_s": 0.1, } interp_relperm.process_config(config) outfile_str = open("outfile.inc").read() # Assert things about the comments emitted by pyscal when interpolating: # This is used as a proxy for asserting that interpolation parameters # are used for the correct satnums assert outfile_str.find("SCAL recommendation interpolation to 0.5") assert outfile_str.find("SCAL recommendation interpolation to -0.5") # SWOF comes before SGOF: assert outfile_str.find("to -0.5") < outfile_str.find("to 0.5") outfile_df = satfunc.df(outfile_str, ntsfun=2) assert set(outfile_df["KEYWORD"].unique()) == {"SWOF", "SGOF"} assert set(outfile_df["SATNUM"].unique()) == {1, 2} config = { "base": ["base.inc"], "low": ["pess.inc"], "high": ["opt.inc"], "result_file": "outfile.inc", "interpolations": [ { "tables": [1], "param_w": -0.9, "param_g": -0.5 }, { "tables": [2], "param_w": 0.5, "param_g": 0.8 }, ], "delta_s": 0.1, } interp_relperm.process_config(config) outfile_str = open("outfile.inc").read() assert outfile_str.find("to -0.9") < outfile_str.find("to 0.5") assert outfile_str.find("to 0.5") < outfile_str.find("to -0.5") assert outfile_str.find("to 0.5") < outfile_str.find("to 0.8") config = { "base": ["base.inc"], "low": ["pess.inc"], "high": ["opt.inc"], "result_file": "outfile.inc", "interpolations": [ # This is a user error, the latter will override the first { "param_w": -0.9, "param_g": -0.5 }, { "param_w": 0.5, "param_g": 0.8 }, ], "delta_s": 0.1, } interp_relperm.process_config(config) outfile_str = open("outfile.inc").read() assert "interpolation to -0.9" not in outfile_str assert "interpolation to 0.8" in outfile_str config = { "base": ["base.inc"], "low": ["pess.inc"], "high": ["opt.inc"], "result_file": "outfile.inc", "interpolations": [ # Here the user intentionally overwrites the first: { "param_w": -0.9, "param_g": -0.5 }, { "tables": [], "param_w": 0.5, "param_g": 0.8 }, ], "delta_s": 0.1, } interp_relperm.process_config(config) outfile_str = open("outfile.inc").read() assert "interpolation to -0.9" not in outfile_str assert "interpolation to 0.8" in outfile_str