def test_solution_auto_equilibrate(consolidated_data): """ assert that solutions are equilibrated with Na or Cl depending on what their concentrations are """ df = consolidated_data df = df.iloc[:3, :] # make sure that equilibrating with Na will fail because # it will needs a concentration below 0 to match the # charge in balance df.loc[0, 'Fe'] = 2*df.loc[0, 'Na'] sol = df.hgc.get_phreeqpython_solutions(equilibrate_with='auto', inplace=False) Na_in_sol = [s.total_element('Na') * mw('Na') for s in sol] Cl_in_sol = [s.total_element('Cl') * mw('Cl') for s in sol] # first one equilibrates with Cl np.testing.assert_allclose( Na_in_sol[:1], df.loc[:0, 'Na'].values, rtol=1.e-1) with pytest.raises(AssertionError): np.testing.assert_allclose(Na_in_sol[1:], df.loc[1:, 'Na'].values, rtol=1.e-1) # others one equilibrates with Na np.testing.assert_allclose( Cl_in_sol[1:], df.loc[1:, 'Cl'].values, rtol=1.e-1)
def test_add_oxygen(consolidated_data, phreeqpython_solutions_excel): ''' Test oxygen is added and returned correctly to and from phreeqpython with or without. Test it in pure phreeqpython (because there it is not working as expected) - test that no O(0) added yields no O in solution - test that adding `O(0)` `as O2` yields wrong result - test that adding `O(0)` without `as O2` yields correct result - test that adding in the SamplesFrame O2 concentration yields correct results ''' pp = PhreeqPython() test_solution_dict = {'units': 'mg/L', 'Cl': 8.0, 'Na': '2.0 charge', 'Alkalinity': '2.0 as HCO3', 'Ca': 1.0, 'pH': 7, 'temp': 11 } sol = pp.add_solution(test_solution_dict) assert sol.total_element('O') == 0.0 test_solution_dict['O(0)'] = '3 as O2' sol1 = pp.add_solution(test_solution_dict) assert sol1.species['O2'] * 2 * mw('O') * 1000. != pytest.approx(3., 1.e-4) test_solution_dict['O(0)'] = 3 sol1 = pp.add_solution(test_solution_dict) assert sol1.species['O2'] * 2 * mw('O') * 1000. == pytest.approx(3., 1.e-4) test_data = pd.DataFrame({'Cl': 8, 'Na': 2, 'alkalinity': 2, 'Ca': 1, 'O2': 3, 'ph': 7, 'temp': 11}, index=[0]) test_data.hgc.make_valid() test_data.hgc.consolidate( use_so4=None, use_ph=None, use_ec=None, use_temp=None) sols = test_data.hgc.get_phreeqpython_solutions(inplace=False) assert sols[0].species['O2'] * 2 * \ mw('O') * 1000. == pytest.approx(3., 1.e-4)
def test_solution_equilibrate_with(consolidated_data): ''' Assert phreeqpython solutions are returned as series''' df = consolidated_data solutions_default = df.hgc.get_phreeqpython_solutions(inplace=False) solutions_Na = df.hgc.get_phreeqpython_solutions(equilibrate_with='Na', inplace=False) solutions_Cl = df.hgc.get_phreeqpython_solutions(equilibrate_with='Cl', inplace=False) solutions_none = df.hgc.get_phreeqpython_solutions(equilibrate_with=None, inplace=False) solutions_auto = df.hgc.get_phreeqpython_solutions(equilibrate_with='auto', inplace=False) # get the list of Na-concentrations in the phreeqpython-solutions (from mmol/L to # mg/L) Na_in_sol_default = [s.total_element('Na') * mw('Na') for s in solutions_default] Na_in_sol_Na = [s.total_element('Na') * mw('Na') for s in solutions_Na] Na_in_sol_Cl = [s.total_element('Na') * mw('Na') for s in solutions_Cl] Na_in_sol_none = [s.total_element('Na') * mw('Na') for s in solutions_none] Na_in_sol_auto = [s.total_element('Na') * mw('Na') for s in solutions_auto] # get the list of Fe-concentrations in the phreeqpython-solutions (from mmol/L to # mg/L) Fe_in_sol_default = [s.total_element('Fe') * mw('Fe') for s in solutions_default] Fe_in_sol_none = [s.total_element('Fe') * mw('Fe') for s in solutions_none] Fe_in_sol_auto = [s.total_element('Fe') * mw('Fe') for s in solutions_auto] # test that default equilibrate with is None by returning the same array np.testing.assert_array_equal(Na_in_sol_default, Na_in_sol_none) # assert that indeed Na-concentration is altered by comparing it # to the Na concentration when Cl is used for equilibration assert all(np.not_equal(Na_in_sol_Cl, Na_in_sol_Na)) # assert that auto equals with Na for this case assert all(np.not_equal(Na_in_sol_none, Na_in_sol_Na)) # assert Na-concentration is not altered by using equilibrate with # Cl. Na concentration should be the same as in the original dataframe # to some extent of accuracy (this is generally not closer than 10% in my experience). np.testing.assert_allclose(Na_in_sol_Cl, df.Na.values, rtol=1.e-1) np.testing.assert_allclose(Na_in_sol_none, df.Na.values, rtol=1.e-1) # same assertion but now for Fe np.testing.assert_allclose(Fe_in_sol_default, df.Fe.values, rtol=1.e-1) np.testing.assert_allclose(Fe_in_sol_none, df.Fe.values, rtol=1.e-1) np.testing.assert_allclose(Fe_in_sol_auto, df.Fe.values, rtol=1.e-1)
def _get_dominant_anions_of_df(self, df_in): """ calculates the dominant anions of the dataframe df_in """ s_sum_cations = self.get_sum_cations(inplace=False) cols_req = ('ph', 'Na', 'K', 'Ca', 'Mg', 'Fe', 'Mn', 'NH4', 'Al', 'Ba', 'Co', 'Cu', 'Li', 'Ni', 'Pb', 'Sr', 'Zn') df_in = df_in.hgc._make_input_df(cols_req) na_mmol = df_in.Na / mw('Na') k_mmol = df_in.K / mw('K') nh4_mmol = df_in.NH4 / (mw('N') + 4 * mw('H')) ca_mmol = df_in.Ca / mw('Ca') mg_mmol = df_in.Mg / mw('Mg') fe_mmol = df_in.Fe / mw('Fe') mn_mmol = df_in.Mn / mw('Mn') h_mmol = (10**-df_in.ph) / 1000 # ph -> mol/L -> mmol/L al_mmol = 1000. * df_in.Al / mw('Al') # ug/L ->mg/L -> mmol/L # - Na, K, NH4 # select rows that do not have Na, K or NH4 as dominant cation is_no_domcat_na_nh4_k = (na_mmol + k_mmol + nh4_mmol) < (s_sum_cations / 2) is_domcat_nh4 = ~is_no_domcat_na_nh4_k & (nh4_mmol > (na_mmol + k_mmol)) is_domcat_na = ~is_no_domcat_na_nh4_k & ~is_domcat_nh4 & (na_mmol > k_mmol) is_domcat_k = ~is_no_domcat_na_nh4_k & ~is_domcat_nh4 & ~is_domcat_na # abbreviation is_domcat_na_nh4_k = is_domcat_na | is_domcat_nh4 | is_domcat_k # - Ca, Mg is_domcat_ca_mg = ( # not na or nh4 or k dominant ~is_domcat_na_nh4_k & ( # should be any of Ca or Mg available ((ca_mmol > 0) | (mg_mmol > 0)) | # should be more of Ca or Mg then sum of H, Fe, Al, Mn # (compensated for charge) (2 * ca_mmol + 2 * mg_mmol < h_mmol + 3 * al_mmol + 2 * fe_mmol + 2 * mn_mmol))) is_domcat_ca = is_domcat_ca_mg & (ca_mmol >= mg_mmol) is_domcat_mg = is_domcat_ca_mg & (ca_mmol < mg_mmol) # - H, Al, Fe, Mn # IF(IF(h_mmol+3*IF(al_mmol)>2*(fe_mol+mn_mol),IF(h_mmol>3*al_mmol,"H","Al"),IF(fe_mol>mn_mol,"Fe","Mn"))) is_domcat_fe_mn_al_h = ( # not na, nh4, k, ca or Mg dominant ~is_domcat_na_nh4_k & ~is_domcat_ca & ~is_domcat_mg & ( # should be any of Fe, Mn, Al or H available (fe_mmol > 0) | (mn_mmol > 0) | (h_mmol > 0) | (al_mmol > 0) # | # # should be more of Ca or Mg then sum of H, Fe, Al, Mn # # (compensated for charge) # (2*ca_mmol+2*mg_mmol < h_mmol+3*al_mmol+2*fe_mmol+2*mn_mmol) )) is_domcat_h_al = is_domcat_fe_mn_al_h & ((h_mmol + 3 * al_mmol) > (2 * fe_mmol + 2 * mn_mmol)) is_domcat_h = is_domcat_h_al & (h_mmol > al_mmol) is_domcat_al = is_domcat_h_al & (al_mmol > h_mmol) is_domcat_fe_mn = is_domcat_fe_mn_al_h & ~is_domcat_h_al is_domcat_fe = is_domcat_fe_mn & (fe_mmol > mn_mmol) is_domcat_mn = is_domcat_fe_mn & (mn_mmol > fe_mmol) sr_out = pd.Series(index=df_in.index, dtype='object') sr_out[:] = "" sr_out[is_domcat_nh4] = "NH4" sr_out[is_domcat_na] = "Na" sr_out[is_domcat_k] = "K" sr_out[is_domcat_ca] = 'Ca' sr_out[is_domcat_mg] = 'Mg' sr_out[is_domcat_fe] = 'Fe' sr_out[is_domcat_mn] = 'Mn' sr_out[is_domcat_al] = 'Al' sr_out[is_domcat_h] = 'H' return sr_out
def get_stuyfzand_water_type(self, inplace=True): """ Get Stuyfzand water type. This water type classification contains 5 components: Salinity, Alkalinity, Dominant Cation, Dominant Anion and Base Exchange Index. This results in a classification such as for example 'F3CaMix+'. It is assumed that only HCO<sub>3</sub><sup>-</sup> contributes to the alkalinity. Returns ------- pandas.Series Series with Stuyfzand water type of each row in original SamplesFrame. """ if not self.is_valid: raise ValueError( "Method can only be used on validated HGC frames, use 'make_valid' to validate" ) # Create input dataframe containing all required columns # Inherit column values from HGC frame, assume 0 if column # is not present cols_req = ('Al', 'Ba', 'Br', 'Ca', 'Cl', 'Co', 'Cu', 'doc', 'F', 'Fe', 'alkalinity', 'K', 'Li', 'Mg', 'Mn', 'Na', 'Ni', 'NH4', 'NO2', 'NO3', 'Pb', 'PO4', 'ph', 'SO4', 'Sr', 'Zn') df_in = self._make_input_df(cols_req) df_out = pd.DataFrame(index=df_in.index) # Salinity df_out['swt_s'] = 'G' df_out.loc[df_in['Cl'] > 5, 'swt_s'] = 'g' df_out.loc[df_in['Cl'] > 30, 'swt_s'] = 'F' df_out.loc[df_in['Cl'] > 150, 'swt_s'] = 'f' df_out.loc[df_in['Cl'] > 300, 'swt_s'] = 'B' df_out.loc[df_in['Cl'] > 1000, 'swt_s'] = 'b' df_out.loc[df_in['Cl'] > 10000, 'swt_s'] = 'S' df_out.loc[df_in['Cl'] > 20000, 'swt_s'] = 'H' #Alkalinity df_out['swt_a'] = '*' df_out.loc[df_in['alkalinity'] > 31, 'swt_a'] = '0' df_out.loc[df_in['alkalinity'] > 61, 'swt_a'] = '1' df_out.loc[df_in['alkalinity'] > 122, 'swt_a'] = '2' df_out.loc[df_in['alkalinity'] > 244, 'swt_a'] = '3' df_out.loc[df_in['alkalinity'] > 488, 'swt_a'] = '4' df_out.loc[df_in['alkalinity'] > 976, 'swt_a'] = '5' df_out.loc[df_in['alkalinity'] > 1953, 'swt_a'] = '6' df_out.loc[df_in['alkalinity'] > 3905, 'swt_a'] = '7' #Dominant cation s_sum_cations = self.get_sum_cations(inplace=False) df_out['swt_domcat'] = self._get_dominant_anions_of_df(df_in) # Dominant anion s_sum_anions = self.get_sum_anions(inplace=False) cl_mmol = df_in.Cl / mw('Cl') hco3_mmol = df_in.alkalinity / (mw('H') + mw('C') + 3 * mw('O')) no3_mmol = df_in.NO3 / (mw('N') + 3 * mw('O')) so4_mmol = df_in.SO4 / (mw('S') + 4 * mw('O')) # TODO: consider renaming doman to dom_an or dom_anion is_doman_cl = (cl_mmol > s_sum_anions / 2) df_out.loc[is_doman_cl, 'swt_doman'] = "Cl" is_doman_hco3 = ~is_doman_cl & (hco3_mmol > s_sum_anions / 2) df_out.loc[is_doman_hco3, 'swt_doman'] = "HCO3" is_doman_so4_or_no3 = ~is_doman_cl & ~is_doman_hco3 & ( 2 * so4_mmol + no3_mmol > s_sum_anions / 2) is_doman_so4 = (2 * so4_mmol > no3_mmol) df_out.loc[is_doman_so4_or_no3 & is_doman_so4, 'swt_doman'] = "SO4" df_out.loc[is_doman_so4_or_no3 & ~is_doman_so4, 'swt_doman'] = "NO3" is_mix = ~is_doman_cl & ~is_doman_hco3 & ~is_doman_so4_or_no3 df_out.loc[is_mix, 'swt_doman'] = "Mix" # Base Exchange Index s_bex = self.get_bex(inplace=False) threshold1 = 0.5 + 0.02 * cl_mmol threshold2 = -0.5 - 0.02 * cl_mmol is_plus = (s_bex > threshold1) & (s_bex > 1.5 * (s_sum_cations - s_sum_anions)) is_minus = ~is_plus & (s_bex < threshold2) & ( s_bex < 1.5 * (s_sum_cations - s_sum_anions)) is_neutral = (~is_plus & ~is_minus & (s_bex > threshold2) & (s_bex < threshold1) & ((s_sum_cations == s_sum_anions) | ((abs(s_bex + threshold1 * (s_sum_cations - s_sum_anions)) / abs(s_sum_cations - s_sum_anions)) > abs( 1.5 * (s_sum_cations - s_sum_anions))))) is_none = ~is_plus & ~is_minus & ~is_neutral df_out.loc[is_plus, 'swt_bex'] = '+' df_out.loc[is_minus, 'swt_bex'] = '-' df_out.loc[is_neutral, 'swt_bex'] = 'o' df_out.loc[is_none, 'swt_bex'] = '' #Putting it all together df_out['swt'] = df_out['swt_s'].str.cat( df_out[['swt_a', 'swt_domcat', 'swt_doman', 'swt_bex']]) if inplace: self._obj['water_type'] = df_out['swt'] else: return df_out['swt']