From d80910025b3b246a02b2e18547db9e33e46d4ab4 Mon Sep 17 00:00:00 2001 From: Harrison Nicholls Date: Wed, 24 Jul 2024 16:07:43 +0100 Subject: [PATCH 1/8] Initial stub stuff --- examples/dummy_atmosphere/init_coupler.cfg | 1 - examples/earth_demo/init_coupler.cfg | 1 - input/default.cfg | 1 - input/dummy.cfg | 25 ++-- input/janus_mixed.cfg | 1 - input/jgr_grid.cfg | 1 - input/rce_mixed.cfg | 1 - input/rce_steam.cfg | 1 - plot/cpl_elements.py | 74 ++++++++++++ proteus.py | 130 +++++++++++++++------ utils/constants.py | 3 + utils/coupler.py | 21 ++-- utils/escape.py | 37 ++++++ utils/spider.py | 27 ----- 14 files changed, 235 insertions(+), 89 deletions(-) create mode 100755 plot/cpl_elements.py create mode 100644 utils/escape.py diff --git a/examples/dummy_atmosphere/init_coupler.cfg b/examples/dummy_atmosphere/init_coupler.cfg index a1179e4b..c30b98ea 100644 --- a/examples/dummy_atmosphere/init_coupler.cfg +++ b/examples/dummy_atmosphere/init_coupler.cfg @@ -57,7 +57,6 @@ dt_initial = 1e2 # Inital step size # Flux convergence scheme and tolerances for surface equilibration shallow_ocean_layer = 0 # 0: off | 1: on F_atm_bc = 0 # Boundary condition choice for F_atm, 0: TOA | 1: Surface -RF_crit = 0.01 # depth fraction skin_d = 0.01 # m skin_k = 2.0 # W m-1 K-1 prevent_warming = 1 # Require that the planet only cool down over time, 0: disabled | 1: enabled diff --git a/examples/earth_demo/init_coupler.cfg b/examples/earth_demo/init_coupler.cfg index 9fb2049c..133896c5 100644 --- a/examples/earth_demo/init_coupler.cfg +++ b/examples/earth_demo/init_coupler.cfg @@ -57,7 +57,6 @@ dt_initial = 1e4 # Inital step size # Flux convergence scheme and tolerances for surface equilibration shallow_ocean_layer = 0 # 0: off | 1: on F_atm_bc = 0 # Boundary condition choice for F_atm, 0: TOA | 1: Surface -RF_crit = 0.01 # depth fraction skin_d = 0.01 # m skin_k = 2.0 # W m-1 K-1 prevent_warming = 1 # Require that the planet only cool down over time, 0: disabled | 1: enabled diff --git a/input/default.cfg b/input/default.cfg index 9fb2049c..133896c5 100644 --- a/input/default.cfg +++ b/input/default.cfg @@ -57,7 +57,6 @@ dt_initial = 1e4 # Inital step size # Flux convergence scheme and tolerances for surface equilibration shallow_ocean_layer = 0 # 0: off | 1: on F_atm_bc = 0 # Boundary condition choice for F_atm, 0: TOA | 1: Surface -RF_crit = 0.01 # depth fraction skin_d = 0.01 # m skin_k = 2.0 # W m-1 K-1 prevent_warming = 1 # Require that the planet only cool down over time, 0: disabled | 1: enabled diff --git a/input/dummy.cfg b/input/dummy.cfg index a1179e4b..e3b481f4 100644 --- a/input/dummy.cfg +++ b/input/dummy.cfg @@ -50,14 +50,13 @@ dt_maximum = 3e7 # Maximum time-step dt_minimum = 1e2 # Minimum time-step dt_method = 1 # Time-stepping method, 0: proportional | 1: adaptive | 2: maximum dt_propconst = 52.0 # Proportionality constant for dt_method=0 -dt_atol = 0.02 # Step size atol -dt_rtol = 0.07 # Step size rtol +dt_atol = 0.02 # Step size atol +dt_rtol = 0.08 # Step size rtol dt_initial = 1e2 # Inital step size # Flux convergence scheme and tolerances for surface equilibration shallow_ocean_layer = 0 # 0: off | 1: on F_atm_bc = 0 # Boundary condition choice for F_atm, 0: TOA | 1: Surface -RF_crit = 0.01 # depth fraction skin_d = 0.01 # m skin_k = 2.0 # W m-1 K-1 prevent_warming = 1 # Require that the planet only cool down over time, 0: disabled | 1: enabled @@ -75,8 +74,12 @@ steady_dprel = 1.0e-9 # Percentage change in melt fraction ov emit_stop = 1 # Enable this break condition F_crit = 0.2 # Model will terminate when |F_atm| < F_crit +# Atmospheric escape +escape_model = 2 # Escape model to be used, 0: None | 1: ZEPHYRUS | 2: Dummy +M_atm_crit + # Method for solving for T(p) profile -atmosphere_model = 2 # Atmosphere model to be used, 0: JANUS | 1: AGNI +atmosphere_model = 2 # Atmosphere model to be used, 0: JANUS | 1: AGNI | 2: Dummy atmosphere_solve_energy = 0 # Enable time-stepped atmosphere solution, 0: disabled | 1: enabled atmosphere_surf_state = 1 # Atmosphere bottom edge boundary condition, 0: free | 1: fixed at T_surf | 2: conductive skin @@ -134,10 +137,10 @@ F_atm = 1e6 fO2_shift_IW = 4 # Enable solving for initial partial pressures (0: off | 1: on) -solvevol_use_params = 0 +solvevol_use_params = 1 # Parameters used to solve for initial partial pressures (when solvepp_use_params = 1) -T_magma = 1900 # Surface temperature initial guess [K] +T_magma = 3000 # Surface temperature initial guess [K] Phi_global = 1.0 # Mantle melt fraction initial guess CH_ratio = 1.0 # C/H ratio hydrogen_earth_oceans = 6.0 # Hydrogen inventory in units of equivalent Earth oceans @@ -147,7 +150,7 @@ sulfur_ppmw = 200.0 # Sulfur inventory in ppmw relative to m # Prescribed injected partial pressures [bar] # Summed with solvepp results when solvepp_enabled = 1 H2O_included = 1 -H2O_initial_bar = 30.0 +H2O_initial_bar = 0.0 CO2_included = 1 CO2_initial_bar = 0.0 @@ -158,15 +161,15 @@ N2_initial_bar = 0.0 S2_included = 1 S2_initial_bar = 0.0 -SO2_included = 0 +SO2_included = 1 SO2_initial_bar = 0.0 -H2_included = 0 +H2_included = 1 H2_initial_bar = 0.0 -CH4_included = 0 +CH4_included = 1 CH4_initial_bar = 0.0 -CO_included = 0 +CO_included = 1 CO_initial_bar = 0.0 diff --git a/input/janus_mixed.cfg b/input/janus_mixed.cfg index 29789f5a..e1e2e274 100644 --- a/input/janus_mixed.cfg +++ b/input/janus_mixed.cfg @@ -57,7 +57,6 @@ dt_initial = 1e4 # Inital step size # Flux convergence scheme and tolerances for surface equilibration shallow_ocean_layer = 0 # 0: off | 1: on F_atm_bc = 0 # Boundary condition choice for F_atm, 0: TOA | 1: Surface -RF_crit = 0.01 # depth fraction skin_d = 0.01 # m skin_k = 2.0 # W m-1 K-1 prevent_warming = 1 # Require that the planet only cool down over time, 0: disabled | 1: enabled diff --git a/input/jgr_grid.cfg b/input/jgr_grid.cfg index e93500f6..ef23496b 100644 --- a/input/jgr_grid.cfg +++ b/input/jgr_grid.cfg @@ -57,7 +57,6 @@ dt_initial = 8e2 # Inital step size # Flux convergence scheme and tolerances for surface equilibration shallow_ocean_layer = 0 # 0: off | 1: on F_atm_bc = 0 # Boundary condition choice for F_atm, 0: TOA | 1: Surface -RF_crit = 0.01 # depth fraction skin_d = 0.01 # m skin_k = 2.0 # W m-1 K-1 prevent_warming = 1 # Require that the planet only cool down over time, 0: disabled | 1: enabled diff --git a/input/rce_mixed.cfg b/input/rce_mixed.cfg index 118f1dfd..a38b0dee 100644 --- a/input/rce_mixed.cfg +++ b/input/rce_mixed.cfg @@ -57,7 +57,6 @@ dt_initial = 1e4 # Inital step size # Flux convergence scheme and tolerances for surface equilibration shallow_ocean_layer = 0 # 0: off | 1: on F_atm_bc = 0 # Boundary condition choice for F_atm, 0: TOA | 1: Surface -RF_crit = 0.01 # depth fraction skin_d = 0.01 # m skin_k = 2.0 # W m-1 K-1 prevent_warming = 1 # Require that the planet only cool down over time, 0: disabled | 1: enabled diff --git a/input/rce_steam.cfg b/input/rce_steam.cfg index d7eb7d0c..21ac46fe 100644 --- a/input/rce_steam.cfg +++ b/input/rce_steam.cfg @@ -57,7 +57,6 @@ dt_initial = 1e4 # Inital step size # Flux convergence scheme and tolerances for surface equilibration shallow_ocean_layer = 0 # 0: off | 1: on F_atm_bc = 0 # Boundary condition choice for F_atm, 0: TOA | 1: Surface -RF_crit = 0.01 # depth fraction skin_d = 0.01 # m skin_k = 2.0 # W m-1 K-1 prevent_warming = 1 # Require that the planet only cool down over time, 0: disabled | 1: enabled diff --git a/plot/cpl_elements.py b/plot/cpl_elements.py new file mode 100755 index 00000000..7fda4954 --- /dev/null +++ b/plot/cpl_elements.py @@ -0,0 +1,74 @@ +#!/usr/bin/env python3 + +from utils.modules_ext import * +from utils.plot import * + +log = logging.getLogger("PROTEUS") + +def plot_elements( output_dir, plot_format="pdf", t0=100.0): + + log.info("Plot elements") + + hf_all = pd.read_csv(output_dir+"/runtime_helpfile.csv", sep=r"\s+") + + time = np.array(hf_all["Time"] ) + if len(time) < 3: + log.warning("Cannot make plot with less than 3 samples") + return + + # make plot + lw = 1.2 + al = 0.5 + scale = 1.1 + fig,ax = plt.subplots(1,1, figsize=(7*scale,4*scale)) + + + all = np.zeros(len(time)) + for e in element_list: + y = hf_all[e+"_kg_total"] + all += y + + l = ax.plot(time, y, lw=lw, ls='solid', label=e)[0] + + # c = l.get_color() + # ax.plot(time, hf_all[e+"_kg_liquid"], lw=lw, color=c, alpha=al, ls='dashed') # in magma ocean + # ax.plot(time, hf_all[e+"_kg_atm"], lw=lw, color=c, alpha=al, ls='dotted') # in atmosphere + + # decorate + ax.set_ylabel("Inventory [kg]") + ax.set_yscale("log") + ax.set_xlabel("Time [yr]") + ax.set_xscale("log") + ax.set_xlim(left=t0, right=np.amax(time)) + ax.grid(alpha=0.2) + ax.legend() + + plt.close() + plt.ioff() + + fpath = os.path.join(output_dir, "plot_elements.%s"%plot_format) + fig.savefig(fpath, dpi=200, bbox_inches='tight') + +#==================================================================== +def main(): + + if len(sys.argv) == 2: + cfg = sys.argv[1] + else: + cfg = 'init_coupler.cfg' + + # Read in COUPLER input file + log.info("Read cfg file") + from utils.coupler import ReadInitFile, SetDirectories + COUPLER_options, time_dict = ReadInitFile( cfg ) + + # Set directories dictionary + dirs = SetDirectories(COUPLER_options) + + plot_elements( output_dir=dirs["output"], plot_format=COUPLER_options["plot_format"] ) + +#==================================================================== + +if __name__ == "__main__": + + main() diff --git a/proteus.py b/proteus.py index 251a594a..160c0fe5 100755 --- a/proteus.py +++ b/proteus.py @@ -29,14 +29,14 @@ def main(): cfg_file = os.path.abspath(str(args["cfg"])) COUPLER_options, time_dict = ReadInitFile( cfg_file , verbose=False ) - # Validate options - ValidateInitFile(COUPLER_options) - # Set directories dictionary utils.constants.dirs = SetDirectories(COUPLER_options) from utils.constants import dirs UpdateStatusfile(dirs, 0) + # Validate options + ValidateInitFile(dirs, COUPLER_options) + # Clean output directory if not resume: CleanDir(dirs["output"]) @@ -160,7 +160,8 @@ def main(): # Set volatile mass targets solvevol_target = {} - for e in ["H","C","N","S"]: + for e in element_list: + if e == 'O': continue solvevol_target[e] = hf_row[e+"_kg_total"] # Import the appropriate atmosphere module @@ -173,6 +174,18 @@ def main(): else: UpdateStatusfile(dirs, 20) raise Exception("Invalid atmosphere model") + + # Import the appropriate escape module + if COUPLER_options["escape_model"] == 0: + pass + elif COUPLER_options["escape_model"] == 1: + from utils.escape import RunZEPHYRUS + elif COUPLER_options["escape_model"] == 2: + from utils.escape import RunDummyEsc + else: + UpdateStatusfile(dirs, 20) + raise Exception("Invalid escape model") + # Download all basic data. # (to be improved such that we only download the one we need) @@ -361,13 +374,52 @@ def main(): loop_counter, hf_all, hf_row ) sim_time, spider_result = ReadSPIDER(dirs, COUPLER_options, hf_row["Time"], prev_T_magma) - hf_row["Time"] = float(sim_time) for k in spider_result.keys(): if k in hf_row.keys(): hf_row[k] = spider_result[k] + + # Advance current time in main loop according to interior step + dt = float(sim_time) - hf_row["Time"] + hf_row["Time"] += dt # in years + hf_row["age_star"] += dt # in years + ############### / INTERIOR + ############### ESCAPE + + if loop_counter["total"] >= loop_counter["init_loops"]: + PrintHalfSeparator() + + if COUPLER_options["escape_model"] == 0: + pass + + elif COUPLER_options["escape_model"] == 1: + escape_result = RunZEPHYRUS() + + elif COUPLER_options["escape_model"] == 2: + escape_result = RunDummyEsc() + + # store total escape rate + hf_row["esc_rate_total"] = np.sum(list(escape_result.values())) # bulk kg/s + + # for each element + for e in element_list: + if e == 'O': continue + + hf_row["esc_rate_"+e] = escape_result[e] # elemental kg/s + + # subtract from total inventory + solvevol_target[e] -= escape_result[e] * dt * secs_per_year + + # do not allow zero or negative masses + solvevol_target[e] = max(0.0, solvevol_target[e]) + + + + ############### / ESCAPE + + ############### OUTGASSING PrintHalfSeparator() solvevol_inp = copy.deepcopy(COUPLER_options) @@ -380,7 +432,7 @@ def main(): # depending on the true melt fraction and T_magma found by SPIDER at runtime. if (loop_counter["init"] < loop_counter["init_loops"]): - # calculate target mass of atoms + # calculate target mass of atoms (except O, which is derived from fO2) if COUPLER_options["solvevol_use_params"] > 0: solvevol_target = solvevol_get_target_from_params(solvevol_inp) else: @@ -399,35 +451,18 @@ def main(): warnings.filterwarnings("ignore", category=RuntimeWarning) solvevol_result = solvevol_equilibrium_atmosphere(solvevol_target, solvevol_inp) + # store results for k in solvevol_result.keys(): if k in hf_row.keys(): hf_row[k] = solvevol_result[k] + + # calculate total atmosphere mass + hf_row["M_atm"] = 0.0 + for s in volatile_species: + hf_row["M_atm"] += hf_row[s+"_kg_atm"] ############### / OUTGASSING - ############### UPDATE TIME - - # Advance current time in main loop according to interior step - dt = sim_time - hf_row["Time"] - hf_row["Time"] += dt - hf_row["age_star"] += dt - - # Update init loop counter - # Next init iter - if loop_counter["init"] < loop_counter["init_loops"]: - loop_counter["init"] += 1 - hf_row["Time"] = 0. - # Reset restart flag once SPIDER has correct heat flux - if loop_counter["total"] >= loop_counter["init_loops"]: - IC_INTERIOR = 2 - - # Adjust total iteration counters - loop_counter["total"] += 1 - - ############### / UPDATE TIME - - - ############### ATMOSPHERE SUB-LOOP PrintHalfSeparator() if COUPLER_options["shallow_ocean_layer"] == 1: @@ -461,6 +496,23 @@ def main(): hf_row["contrast_ratio"] = ((hf_row["F_olr"]+hf_row["F_sct"])/hf_row["F_ins"]) * \ (hf_row["z_obs"] / (COUPLER_options["mean_distance"]*AU))**2.0 + + ############### HOUSEKEEPING AND CONVERGENCE CHECK + + PrintHalfSeparator() + + # Update init loop counter + # Next init iter + if loop_counter["init"] < loop_counter["init_loops"]: + loop_counter["init"] += 1 + hf_row["Time"] = 0. + # Reset restart flag once SPIDER has correct heat flux + if loop_counter["total"] >= loop_counter["init_loops"]: + IC_INTERIOR = 2 + + # Adjust total iteration counters + loop_counter["total"] += 1 + # Update full helpfile if loop_counter["total"]>1: # append row @@ -472,11 +524,7 @@ def main(): # Write helpfile to disk WriteHelpfileToCSV(dirs["output"], hf_all) - - ############### CONVERGENCE CHECK - # Print info to terminal and log file - PrintHalfSeparator() PrintCurrentState(hf_row) # Stop simulation when planet is completely solidified @@ -533,8 +581,16 @@ def main(): log.info("===> Planet entered a steady state! <===") log.info("") finished = True - - # Stop simulation if maximum time reached + + # Atmosphere has escaped + if (hf_row["Time"] >= time_dict["target"]): + UpdateStatusfile(dirs, 13) + log.info("") + log.info("===> Target time reached! <===") + log.info("") + finished = True + + # Maximum time reached if (hf_row["Time"] >= time_dict["target"]): UpdateStatusfile(dirs, 13) log.info("") @@ -542,7 +598,7 @@ def main(): log.info("") finished = True - # Stop simulation if maximum loops reached + # Maximum loops reached if (loop_counter["total"] > loop_counter["total_loops"]): UpdateStatusfile(dirs, 12) log.info("") @@ -569,7 +625,7 @@ def main(): PrintHalfSeparator() UpdatePlots( dirs["output"], COUPLER_options ) - ############### / CONVERGENCE CHECK + ############### / HOUSEKEEPING AND CONVERGENCE CHECK # ---------------------- # FINAL THINGS BEFORE EXIT diff --git a/utils/constants.py b/utils/constants.py index 229d02d4..f225eb7c 100644 --- a/utils/constants.py +++ b/utils/constants.py @@ -14,6 +14,9 @@ mol = 6.02214076e+23 # mol definition ocean_moles = 7.68894973907177e+22 # moles of H2 (or H2O) in one present-day Earth ocean +# Time constants +secs_per_year = 365.25 * 24 * 60 * 60 # seconds per year + # Values from phys.py const_h = 6.626075540e-34 #Planck's constant const_c = 2.99792458e8 #Speed of light diff --git a/utils/coupler.py b/utils/coupler.py index 2aeec95a..f2ac38df 100644 --- a/utils/coupler.py +++ b/utils/coupler.py @@ -115,11 +115,18 @@ def GetHelpfileKeys(): # Observational "z_obs", "transit_depth", "contrast_ratio", # observed from infinity - - # Surface composition - "P_surf", "atm_kg_per_mol", # more keys are added below - ] + + # Escape rates + keys.append("esc_rate_total") + for e in element_list: + if e == 'O': continue + keys.append("esc_rate_"+e) + + # Atmosphere composition + keys.append("M_atm") + keys.append("P_surf") + keys.append("atm_kg_per_mol") # gases for s in volatile_species: @@ -270,7 +277,7 @@ def ReadInitFile(init_file_passed:str, verbose=False): if not line.startswith("time_"): # Some parameters are int - if key in [ "solid_stop", "steady_stop", "iter_max", "emit_stop", + if key in [ "solid_stop", "steady_stop", "iter_max", "emit_stop", "escape_model", "plot_iterfreq", "stellar_heating", "mixing_length", "shallow_ocean_layer", "atmosphere_chemistry", "solvevol_use_params", "insert_rscatter", "water_cloud", "tropopause", "F_atm_bc", "atmosphere_solve_energy", "atmosphere_surf_state", @@ -301,7 +308,7 @@ def ReadInitFile(init_file_passed:str, verbose=False): return COUPLER_options, time_dict -def ValidateInitFile(COUPLER_options:dict): +def ValidateInitFile(dirs:dict, COUPLER_options:dict): ''' Validate configuration file, checking for invalid options ''' @@ -339,7 +346,7 @@ def ValidateInitFile(COUPLER_options:dict): raise Exception("Volatile %s has non-zero pressure but is disabled in cfg"%s) if (COUPLER_options[key_pp] > 0.0) and (COUPLER_options["solvevol_use_params"] > 0): UpdateStatusfile(dirs, 20) - raise Exception("Volatile %s has non-zero pressure but outgassing parameters are enabled") + raise Exception("Volatile %s has non-zero pressure but outgassing parameters are enabled"%s) # Required vols for s in ["H2O","CO2","N2","S2"]: diff --git a/utils/escape.py b/utils/escape.py new file mode 100644 index 00000000..b9808e18 --- /dev/null +++ b/utils/escape.py @@ -0,0 +1,37 @@ +# Functions used to handle escape + +from utils.modules_ext import * +from utils.constants import * +from utils.helper import * + +log = logging.getLogger("PROTEUS") + + +def RunDummyEsc(): + """Run dummy escape model. + + Returns + ---------- + out : dict + Dictionary of bulk escape rates for each element [kg s-1] + + """ + log.info("Running dummy escape...") + + # Hardcoded dummy value of bulk volatile escape rate [kg/s] + phi = 1e7 + + # Escape rates for each element + out = {} + for e in element_list: + out[e] = phi + + return out + + + +# Zephyrus wrapper +def RunZEPHYRUS(): + log.info("Running ZEPHYRUS...") + raise Exception("Not yet implemented") + diff --git a/utils/spider.py b/utils/spider.py index f90ccb3f..d5633951 100644 --- a/utils/spider.py +++ b/utils/spider.py @@ -108,33 +108,6 @@ def get_solid_phase_boolean_array( self, nodes='basic' ): #==================================================================== -def get_column_data_from_SPIDER_lookup_file( infile ): - '''Load column data from a text file and scale by the specified - value (by position)''' - - # this approach prevents reading the whole file into memory - # just to extract header information - fp = open( infile, 'r' ) - for ii, line in enumerate( fp ): - if ii == 0: - splitline = list(map( float, line.lstrip('#').split() )) - sline = int(splitline[0]) - size_a = splitline[1:] - elif ii == sline-1: - scalings = map( float, line.lstrip('#').split() ) - elif ii > sline: - break - fp.close() - - # read files, ignore headers (#), and make a 2-D array - data_a = np.loadtxt( infile, ndmin=2 ) - - # scale each column in the data array by the respective scaling - for nn, scale in enumerate( scalings ): - data_a[:,nn] *= scale - - return (data_a, size_a) - def get_all_output_times( odir='output' ): ''' Get all times (in yr) from the json files located in the output directory From b03186caef2b4fd8075ab90052f1e83d7a0c9e60 Mon Sep 17 00:00:00 2001 From: Harrison Nicholls Date: Wed, 24 Jul 2024 16:21:30 +0100 Subject: [PATCH 2/8] Plot updates. Tracks total atm mass. Checks for when whole atmosphere is escaped --- input/dummy.cfg | 2 +- plot/cpl_elements.py | 10 ++++------ proteus.py | 6 +++--- utils/escape.py | 2 +- utils/helper.py | 1 + 5 files changed, 10 insertions(+), 11 deletions(-) diff --git a/input/dummy.cfg b/input/dummy.cfg index e3b481f4..056b985c 100644 --- a/input/dummy.cfg +++ b/input/dummy.cfg @@ -76,7 +76,7 @@ F_crit = 0.2 # Model will terminate when |F_atm| < F_ # Atmospheric escape escape_model = 2 # Escape model to be used, 0: None | 1: ZEPHYRUS | 2: Dummy -M_atm_crit +M_atm_stop = 1e9 # Atmosphere mass below which the model will terminate [kg] # Method for solving for T(p) profile atmosphere_model = 2 # Atmosphere model to be used, 0: JANUS | 1: AGNI | 2: Dummy diff --git a/plot/cpl_elements.py b/plot/cpl_elements.py index 7fda4954..09489920 100755 --- a/plot/cpl_elements.py +++ b/plot/cpl_elements.py @@ -23,17 +23,15 @@ def plot_elements( output_dir, plot_format="pdf", t0=100.0): fig,ax = plt.subplots(1,1, figsize=(7*scale,4*scale)) - all = np.zeros(len(time)) for e in element_list: - y = hf_all[e+"_kg_total"] - all += y - - l = ax.plot(time, y, lw=lw, ls='solid', label=e)[0] + l = ax.plot(time, hf_all[e+"_kg_total"], lw=lw, ls='solid', label="Total "+e)[0] # c = l.get_color() # ax.plot(time, hf_all[e+"_kg_liquid"], lw=lw, color=c, alpha=al, ls='dashed') # in magma ocean # ax.plot(time, hf_all[e+"_kg_atm"], lw=lw, color=c, alpha=al, ls='dotted') # in atmosphere + ax.plot(time, hf_all["M_atm"], lw=lw, ls='solid', label='Atmos.', c='k') + # decorate ax.set_ylabel("Inventory [kg]") ax.set_yscale("log") @@ -41,7 +39,7 @@ def plot_elements( output_dir, plot_format="pdf", t0=100.0): ax.set_xscale("log") ax.set_xlim(left=t0, right=np.amax(time)) ax.grid(alpha=0.2) - ax.legend() + ax.legend(loc='center left', bbox_to_anchor=(1, 0.5)) plt.close() plt.ioff() diff --git a/proteus.py b/proteus.py index 160c0fe5..73916b15 100755 --- a/proteus.py +++ b/proteus.py @@ -583,10 +583,10 @@ def main(): finished = True # Atmosphere has escaped - if (hf_row["Time"] >= time_dict["target"]): - UpdateStatusfile(dirs, 13) + if hf_row["M_atm"] <= COUPLER_options["M_atm_stop"]: + UpdateStatusfile(dirs, 15) log.info("") - log.info("===> Target time reached! <===") + log.info("===> Atmosphere has escaped! <===") log.info("") finished = True diff --git a/utils/escape.py b/utils/escape.py index b9808e18..92c18c6c 100644 --- a/utils/escape.py +++ b/utils/escape.py @@ -19,7 +19,7 @@ def RunDummyEsc(): log.info("Running dummy escape...") # Hardcoded dummy value of bulk volatile escape rate [kg/s] - phi = 1e7 + phi = 1e8 # Escape rates for each element out = {} diff --git a/utils/helper.py b/utils/helper.py index 021ee313..4be2f7fa 100644 --- a/utils/helper.py +++ b/utils/helper.py @@ -44,6 +44,7 @@ def CommentFromStatus(status:int): case 12: desc = "Completed (maximum iterations)" case 13: desc = "Completed (target time)" case 14: desc = "Completed (net flux is small)" + case 15: desc = "Completed (atmosphere escaped)" # Error cases case 20: desc = "Error (generic case, or configuration issue)" case 21: desc = "Error (Interior model)" From ef43b331fe80c9d82fe13d5199bbb407ad3c8787 Mon Sep 17 00:00:00 2001 From: Harrison Nicholls Date: Wed, 24 Jul 2024 16:56:40 +0100 Subject: [PATCH 3/8] Maintains atmosphere elemental mmrs --- input/dummy.cfg | 12 ++++++------ plot/cpl_elements.py | 11 ++++++++--- proteus.py | 32 +++++++++++++++++++------------- utils/coupler.py | 17 ++++++----------- utils/dummy_atmosphere.py | 2 +- utils/escape.py | 13 ++++--------- 6 files changed, 44 insertions(+), 43 deletions(-) diff --git a/input/dummy.cfg b/input/dummy.cfg index 056b985c..d230ad8b 100644 --- a/input/dummy.cfg +++ b/input/dummy.cfg @@ -45,13 +45,13 @@ stellar_heating = 1 plot_iterfreq = 1 # Plotting frequency, 0: wait until completion | n: every n iterations plot_format = png # Plotting image file format sspec_dt_update = 1e9 # Time intervals at which to re-calculate the stellar spectrum -sinst_dt_update = 1e2 # Time intervals at which to re-calculate the instellation +sinst_dt_update = 1e1 # Time intervals at which to re-calculate the instellation dt_maximum = 3e7 # Maximum time-step dt_minimum = 1e2 # Minimum time-step dt_method = 1 # Time-stepping method, 0: proportional | 1: adaptive | 2: maximum dt_propconst = 52.0 # Proportionality constant for dt_method=0 dt_atol = 0.02 # Step size atol -dt_rtol = 0.08 # Step size rtol +dt_rtol = 0.10 # Step size rtol dt_initial = 1e2 # Inital step size # Flux convergence scheme and tolerances for surface equilibration @@ -76,7 +76,7 @@ F_crit = 0.2 # Model will terminate when |F_atm| < F_ # Atmospheric escape escape_model = 2 # Escape model to be used, 0: None | 1: ZEPHYRUS | 2: Dummy -M_atm_stop = 1e9 # Atmosphere mass below which the model will terminate [kg] +escape_stop_frac = 1e-3 # Terminate when atm mass drops below this fraction of its initial mass # Method for solving for T(p) profile atmosphere_model = 2 # Atmosphere model to be used, 0: JANUS | 1: AGNI | 2: Dummy @@ -118,10 +118,10 @@ mixing_length = 2 solver_tolerance = 1.0e-10 # Maximum absolute surface temperature change [K] -tsurf_poststep_change = 20.0 +tsurf_poststep_change = 30.0 # Maximum fractional surface temperature change [fraction] -tsurf_poststep_change_frac = 0.01 +tsurf_poststep_change_frac = 0.02 # Fractional core radius planet_coresize = 0.55 @@ -134,7 +134,7 @@ ic_dsdr = -4.698e-06 F_atm = 1e6 # Oxygen fugacity offset relative to the IW buffer (log10 units) -fO2_shift_IW = 4 +fO2_shift_IW = 2 # Enable solving for initial partial pressures (0: off | 1: on) solvevol_use_params = 1 diff --git a/plot/cpl_elements.py b/plot/cpl_elements.py index 09489920..5e70448a 100755 --- a/plot/cpl_elements.py +++ b/plot/cpl_elements.py @@ -23,14 +23,19 @@ def plot_elements( output_dir, plot_format="pdf", t0=100.0): fig,ax = plt.subplots(1,1, figsize=(7*scale,4*scale)) + total = np.zeros(len(time)) for e in element_list: - l = ax.plot(time, hf_all[e+"_kg_total"], lw=lw, ls='solid', label="Total "+e)[0] + y = hf_all[e+"_kg_total"] + l = ax.plot(time, y, lw=lw, ls='solid', label="Total "+e)[0] + + total += y # c = l.get_color() # ax.plot(time, hf_all[e+"_kg_liquid"], lw=lw, color=c, alpha=al, ls='dashed') # in magma ocean # ax.plot(time, hf_all[e+"_kg_atm"], lw=lw, color=c, alpha=al, ls='dotted') # in atmosphere - - ax.plot(time, hf_all["M_atm"], lw=lw, ls='solid', label='Atmos.', c='k') + + ax.plot(time, total, lw=lw, ls='solid', label='Total', c='k') + ax.plot(time, hf_all["M_atm"], lw=lw, ls='dotted', label='Atmos.', c='k') # decorate ax.set_ylabel("Inventory [kg]") diff --git a/proteus.py b/proteus.py index 73916b15..ad513f4e 100755 --- a/proteus.py +++ b/proteus.py @@ -388,29 +388,35 @@ def main(): ############### ESCAPE - if loop_counter["total"] >= loop_counter["init_loops"]: + if (loop_counter["total"] >= loop_counter["init_loops"]) \ + and (COUPLER_options["escape_model"] > 0): + PrintHalfSeparator() - if COUPLER_options["escape_model"] == 0: - pass - - elif COUPLER_options["escape_model"] == 1: - escape_result = RunZEPHYRUS() + if COUPLER_options["escape_model"] == 1: + phi_esc = RunZEPHYRUS() elif COUPLER_options["escape_model"] == 2: - escape_result = RunDummyEsc() + phi_esc = RunDummyEsc() # store total escape rate - hf_row["esc_rate_total"] = np.sum(list(escape_result.values())) # bulk kg/s + hf_row["esc_rate_total"] = phi_esc # bulk kg/s + log.info("Bulk escape rate: %.2e kg yr-1"%(phi_esc * secs_per_year)) - # for each element + # for each elem, calculate new total inventory while + # maintaining a constant mass mixing ratio in the atmosphere for e in element_list: if e == 'O': continue - hf_row["esc_rate_"+e] = escape_result[e] # elemental kg/s + # current elemental mass ratio in atmosphere + emr = hf_row[e+"_kg_atm"]/hf_row["M_atm"] + + # mass of e in atmosphere, after escape + e_atm = emr * (hf_row["M_atm"] - phi_esc * dt * secs_per_year) - # subtract from total inventory - solvevol_target[e] -= escape_result[e] * dt * secs_per_year + # calculate new elemental inventory + solvevol_target[e] -= hf_row[e+"_kg_atm"] + solvevol_target[e] += e_atm # do not allow zero or negative masses solvevol_target[e] = max(0.0, solvevol_target[e]) @@ -583,7 +589,7 @@ def main(): finished = True # Atmosphere has escaped - if hf_row["M_atm"] <= COUPLER_options["M_atm_stop"]: + if hf_row["M_atm"] <= COUPLER_options["escape_stop_frac"]*hf_all.iloc[0]["M_atm"]: UpdateStatusfile(dirs, 15) log.info("") log.info("===> Atmosphere has escaped! <===") diff --git a/utils/coupler.py b/utils/coupler.py index f2ac38df..7fcedd2c 100644 --- a/utils/coupler.py +++ b/utils/coupler.py @@ -115,19 +115,14 @@ def GetHelpfileKeys(): # Observational "z_obs", "transit_depth", "contrast_ratio", # observed from infinity - ] - - # Escape rates - keys.append("esc_rate_total") - for e in element_list: - if e == 'O': continue - keys.append("esc_rate_"+e) - # Atmosphere composition - keys.append("M_atm") - keys.append("P_surf") - keys.append("atm_kg_per_mol") + # Escape + "esc_rate_total", + # Atmospheric composition + "M_atm", "P_surf", "atm_kg_per_mol", # more keys added below + ] + # gases for s in volatile_species: keys.append(s+"_mol_atm") diff --git a/utils/dummy_atmosphere.py b/utils/dummy_atmosphere.py index 2451d03d..6b5c3c11 100644 --- a/utils/dummy_atmosphere.py +++ b/utils/dummy_atmosphere.py @@ -15,7 +15,7 @@ def RunDummyAtm( dirs:dict, COUPLER_options:dict, T_magma:float, F_ins:float): # surface, relative to the surface temperature itself # Setting this to 0 will result in an entirely transparent atmosphere # Setting this to 1 will result in an OLR of zero - gamma = 0.3 + gamma = 0.9 # Parameters zenith_angle = COUPLER_options["zenith_angle"] diff --git a/utils/escape.py b/utils/escape.py index 92c18c6c..28b53c2c 100644 --- a/utils/escape.py +++ b/utils/escape.py @@ -12,21 +12,16 @@ def RunDummyEsc(): Returns ---------- - out : dict - Dictionary of bulk escape rates for each element [kg s-1] + phi : float + Bulk escape rate [kg/s] """ log.info("Running dummy escape...") # Hardcoded dummy value of bulk volatile escape rate [kg/s] - phi = 1e8 + phi = 1e10 - # Escape rates for each element - out = {} - for e in element_list: - out[e] = phi - - return out + return phi From 21bdb0c34e0c1a12ba1ddea1d2059056f09ef7fa Mon Sep 17 00:00:00 2001 From: Harrison Nicholls Date: Wed, 24 Jul 2024 17:30:25 +0100 Subject: [PATCH 4/8] Escape termination works --- input/dummy.cfg | 4 ++-- proteus.py | 7 ++++--- utils/surface_gases.py | 14 ++++++++------ 3 files changed, 14 insertions(+), 11 deletions(-) diff --git a/input/dummy.cfg b/input/dummy.cfg index d230ad8b..a27dee7b 100644 --- a/input/dummy.cfg +++ b/input/dummy.cfg @@ -137,7 +137,7 @@ F_atm = 1e6 fO2_shift_IW = 2 # Enable solving for initial partial pressures (0: off | 1: on) -solvevol_use_params = 1 +solvevol_use_params = 0 # Parameters used to solve for initial partial pressures (when solvepp_use_params = 1) T_magma = 3000 # Surface temperature initial guess [K] @@ -150,7 +150,7 @@ sulfur_ppmw = 200.0 # Sulfur inventory in ppmw relative to m # Prescribed injected partial pressures [bar] # Summed with solvepp results when solvepp_enabled = 1 H2O_included = 1 -H2O_initial_bar = 0.0 +H2O_initial_bar = 400.0 CO2_included = 1 CO2_initial_bar = 0.0 diff --git a/proteus.py b/proteus.py index ad513f4e..d1ea3bb0 100755 --- a/proteus.py +++ b/proteus.py @@ -411,13 +411,14 @@ def main(): # current elemental mass ratio in atmosphere emr = hf_row[e+"_kg_atm"]/hf_row["M_atm"] - # mass of e in atmosphere, after escape - e_atm = emr * (hf_row["M_atm"] - phi_esc * dt * secs_per_year) + # mass loss of element e, keeping a constant mixing ratio + e_atm = emr * (hf_row["M_atm"] - phi_esc * dt * secs_per_year) - hf_row[e+"_kg_atm"] # calculate new elemental inventory - solvevol_target[e] -= hf_row[e+"_kg_atm"] solvevol_target[e] += e_atm + log.debug("New mass of %s: %.2e kg"%(e, solvevol_target[e])) + # do not allow zero or negative masses solvevol_target[e] = max(0.0, solvevol_target[e]) diff --git a/utils/surface_gases.py b/utils/surface_gases.py index ff681a8b..2ec1a293 100644 --- a/utils/surface_gases.py +++ b/utils/surface_gases.py @@ -485,8 +485,8 @@ def solvevol_get_initial_pressures(target_d): """Get initial guesses of partial pressures""" # all in bar - cH2O = [-7 , +5] # range in log10 units - cCO2 = [-8 , +5] + cH2O = [-10, +5] # range in log10 units + cCO2 = [-10, +5] cN2 = [-10, +5] cS2 = [-10, +5] @@ -495,13 +495,15 @@ def solvevol_get_initial_pressures(target_d): pN2 = get_log_rand(cN2 ) pS2 = get_log_rand(cS2) - if target_d['H'] == 0: + mass_min = 1.0 # kg + + if target_d['H'] < mass_min: pH2O = 0.0 - if target_d['C'] == 0: + if target_d['C'] < mass_min: pCO2 = 0.0 - if target_d['N'] == 0: + if target_d['N'] < mass_min: pN2 = 0.0 - if target_d['S'] == 0: + if target_d['S'] < mass_min: pS2 = 0.0 return pH2O, pCO2, pN2, pS2 From eb229c4c0eb1684d0d63e8559dfddefda80dda0c Mon Sep 17 00:00:00 2001 From: Harrison Nicholls Date: Wed, 24 Jul 2024 20:47:36 +0100 Subject: [PATCH 5/8] Renamed solvevol_ functions to be more concise. Escape no longer fractionates --- input/dummy.cfg | 10 ++++---- proteus.py | 38 ++++++++++++---------------- tools/SolveVol.ipynb | 32 +++++++++++------------- utils/dummy_atmosphere.py | 2 +- utils/escape.py | 47 +++++++++++++++++++++++++++++++---- utils/surface_gases.py | 52 +++++++++++++++++++-------------------- 6 files changed, 105 insertions(+), 76 deletions(-) diff --git a/input/dummy.cfg b/input/dummy.cfg index a27dee7b..bc89f728 100644 --- a/input/dummy.cfg +++ b/input/dummy.cfg @@ -66,17 +66,17 @@ solid_stop = 1 phi_crit = 0.005 # melt fraction # Break at steady state? -steady_stop = 1 +steady_stop = 0 steady_flux = 0.8 # Maximum absolute value of F_atm allowed for convergence steady_dprel = 1.0e-9 # Percentage change in melt fraction over time (dp/p)/dt*100 # Break at small flux? -emit_stop = 1 # Enable this break condition +emit_stop = 0 # Enable this break condition F_crit = 0.2 # Model will terminate when |F_atm| < F_crit # Atmospheric escape escape_model = 2 # Escape model to be used, 0: None | 1: ZEPHYRUS | 2: Dummy -escape_stop_frac = 1e-3 # Terminate when atm mass drops below this fraction of its initial mass +escape_stop_frac = 3e-4 # Terminate when atm mass drops below this fraction of its initial mass # Method for solving for T(p) profile atmosphere_model = 2 # Atmosphere model to be used, 0: JANUS | 1: AGNI | 2: Dummy @@ -137,7 +137,7 @@ F_atm = 1e6 fO2_shift_IW = 2 # Enable solving for initial partial pressures (0: off | 1: on) -solvevol_use_params = 0 +solvevol_use_params = 1 # Parameters used to solve for initial partial pressures (when solvepp_use_params = 1) T_magma = 3000 # Surface temperature initial guess [K] @@ -150,7 +150,7 @@ sulfur_ppmw = 200.0 # Sulfur inventory in ppmw relative to m # Prescribed injected partial pressures [bar] # Summed with solvepp results when solvepp_enabled = 1 H2O_included = 1 -H2O_initial_bar = 400.0 +H2O_initial_bar = 0.0 CO2_included = 1 CO2_initial_bar = 0.0 diff --git a/proteus.py b/proteus.py index d1ea3bb0..2635d850 100755 --- a/proteus.py +++ b/proteus.py @@ -394,33 +394,27 @@ def main(): PrintHalfSeparator() if COUPLER_options["escape_model"] == 1: - phi_esc = RunZEPHYRUS() + esc_result = RunZEPHYRUS() elif COUPLER_options["escape_model"] == 2: - phi_esc = RunDummyEsc() + esc_result = RunDummyEsc(hf_row, dt) # store total escape rate - hf_row["esc_rate_total"] = phi_esc # bulk kg/s - log.info("Bulk escape rate: %.2e kg yr-1"%(phi_esc * secs_per_year)) + hf_row["esc_rate_total"] = esc_result["rate_bulk"] + log.info("Bulk escape rate: %.2e kg yr-1"%(hf_row["esc_rate_total"] * secs_per_year)) - # for each elem, calculate new total inventory while - # maintaining a constant mass mixing ratio in the atmosphere + # update elemental mass targets for e in element_list: - if e == 'O': continue + if e=='O': continue + solvevol_target[e] += esc_result[e+"_dm"] - # current elemental mass ratio in atmosphere - emr = hf_row[e+"_kg_atm"]/hf_row["M_atm"] + esc_m = solvevol_target[e] + esc_dm = esc_result[e+"_dm"] + log.debug(" escape %s: m=%.2e kg, dm=%+.2e (%.3f%%)"% + (e, esc_m, esc_dm, 100*esc_dm/esc_m)) - # mass loss of element e, keeping a constant mixing ratio - e_atm = emr * (hf_row["M_atm"] - phi_esc * dt * secs_per_year) - hf_row[e+"_kg_atm"] - - # calculate new elemental inventory - solvevol_target[e] += e_atm - - log.debug("New mass of %s: %.2e kg"%(e, solvevol_target[e])) - - # do not allow zero or negative masses - solvevol_target[e] = max(0.0, solvevol_target[e]) + # do not allow negative masses + solvevol_target[e] = max(0.0, solvevol_target[e]) @@ -441,9 +435,9 @@ def main(): # calculate target mass of atoms (except O, which is derived from fO2) if COUPLER_options["solvevol_use_params"] > 0: - solvevol_target = solvevol_get_target_from_params(solvevol_inp) + solvevol_target = get_target_from_params(solvevol_inp) else: - solvevol_target = solvevol_get_target_from_pressures(solvevol_inp) + solvevol_target = get_target_from_pressures(solvevol_inp) # prevent numerical issues for key in solvevol_target.keys(): @@ -456,7 +450,7 @@ def main(): # the model makes a poor guess for the composition. These are then discarded, # so the warning should not propagate anywhere. Errors are still printed. warnings.filterwarnings("ignore", category=RuntimeWarning) - solvevol_result = solvevol_equilibrium_atmosphere(solvevol_target, solvevol_inp) + solvevol_result = equilibrium_atmosphere(solvevol_target, solvevol_inp) # store results for k in solvevol_result.keys(): diff --git a/tools/SolveVol.ipynb b/tools/SolveVol.ipynb index 274c5e35..85c1f1b4 100644 --- a/tools/SolveVol.ipynb +++ b/tools/SolveVol.ipynb @@ -113,8 +113,8 @@ } ], "source": [ - "solvevol_target = solvevol_get_target_from_params(COUPLER_options)\n", - "# solvevol_target = solvevol_get_target_from_pressures(COUPLER_options)\n", + "solvevol_target = get_target_from_params(COUPLER_options)\n", + "# solvevol_target = get_target_from_pressures(COUPLER_options)\n", "\n", "print(\"Targets:\")\n", "print(solvevol_target)" @@ -136,7 +136,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "{'M_atm': 2.1140842980536222e+21, 'P_surf': 262.94767901508135, 'H2O_bar': 0.3077575188583487, 'H2O_atm_kg': 1.5336596970914025e+18, 'H2O_liquid_kg': 1.1076196739322948e+22, 'H2O_solid_kg': 0.0, 'H2O_total_kg': 1.107773039902004e+22, 'CO2_bar': 18.53427818446327, 'CO2_atm_kg': 2.2563494534798184e+20, 'CO2_liquid_kg': 3.219934364602254e+20, 'CO2_solid_kg': 0.0, 'CO2_total_kg': 5.476283818082072e+20, 'H2_bar': 0.6213856910789204, 'H2_atm_kg': 3.465015972700822e+17, 'H2_liquid_kg': 0.0, 'H2_solid_kg': 0.0, 'H2_total_kg': 3.465015972700822e+17, 'CH4_bar': 8.425831772574856e-07, 'CH4_atm_kg': 3738492074385.3423, 'CH4_liquid_kg': 444260206273.1918, 'CH4_solid_kg': 0.0, 'CH4_total_kg': 4182752280658.534, 'CO_bar': 227.33373927234243, 'CO_atm_kg': 1.7613932215508305e+21, 'CO_liquid_kg': 7.817866615037986e+20, 'CO_solid_kg': 0.0, 'CO_total_kg': 2.543179883054629e+21, 'N2_bar': 16.148203674934333, 'N2_atm_kg': 1.2513496002581558e+20, 'N2_liquid_kg': 2.7275589698492256e+19, 'N2_solid_kg': 0.0, 'N2_total_kg': 1.5241054972430785e+20, 'S2_bar': 9.936901783106414e-05, 'S2_atm_kg': 1761928754263768.8, 'S2_liquid_kg': 1.1430770714730914e+22, 'S2_solid_kg': 0.0, 'S2_total_kg': 1.1430772476659668e+22, 'SO2_bar': 0.0022144618030195133, 'SO2_atm_kg': 3.9244167386619464e+16, 'SO2_liquid_kg': 0.0, 'SO2_solid_kg': 0.0, 'SO2_total_kg': 3.9244167386619464e+16, 'H2O_vmr': 0.0011704135210894836, 'CO2_vmr': 0.07048656315920641, 'H2_vmr': 0.002363153359658599, 'CH4_vmr': 3.2043757922242747e-09, 'CO_vmr': 0.8645588359017373, 'N2_vmr': 0.06141223126752966, 'S2_vmr': 3.7790414504995437e-07, 'SO2_vmr': 8.421682257528133e-06, 'H_atm_kg': 5.181473595445588e+17, 'H_liquid_kg': 1.239481852543336e+21, 'H_solid_kg': 0.0, 'H_total_kg': 1.2399999999028804e+21, 'C_atm_kg': 8.168842528828816e+20, 'C_liquid_kg': 4.2311574710665714e+20, 'C_solid_kg': 0.0, 'C_total_kg': 1.239999999989539e+21, 'N_atm_kg': 1.2513496002581558e+20, 'N_liquid_kg': 2.7275589698492256e+19, 'N_solid_kg': 0.0, 'N_total_kg': 1.5241054972430785e+20, 'S_atm_kg': 2.051986201951503e+16, 'S_liquid_kg': 1.1430770714730914e+22, 'S_solid_kg': 0.0, 'S_total_kg': 1.1430791234592933e+22, 'O_atm_kg': 1.1715204022219818e+21, 'O_liquid_kg': 1.0517199771454203e+22, 'O_solid_kg': 0.0, 'O_total_kg': 1.1688720173676186e+22, 'atm_kg_per_mol': 0.029065221864415187, 'H2O_mol_atm': 8.513104970288569e+19, 'H2O_mol_solid': 0.0, 'H2O_mol_liquid': 6.1482234743634e+23, 'H2O_mol_total': 6.149074784860428e+23, 'CO2_mol_atm': 5.126901734787136e+21, 'CO2_mol_solid': 0.0, 'CO2_mol_liquid': 7.316369835497055e+21, 'CO2_mol_total': 1.244327157028419e+22, 'H2_mol_atm': 1.7188602360759678e+20, 'H2_mol_solid': 0.0, 'H2_mol_liquid': 0.0, 'H2_mol_total': 1.7188602360759678e+20, 'CH4_mol_atm': 233073071969161.0, 'CH4_mol_solid': 0.0, 'CH4_mol_liquid': 27697020341221.434, 'CH4_mol_total': 260770092310382.44, 'CO_mol_atm': 6.288444204037239e+22, 'CO_mol_solid': 0.0, 'CO_mol_liquid': 2.7910983987997096e+22, 'CO_mol_total': 9.079542602836948e+22, 'N2_mol_atm': 4.4668722790681653e+21, 'N2_mol_solid': 0.0, 'N2_mol_liquid': 9.736413828261675e+20, 'N2_mol_total': 5.440513661894333e+21, 'S2_mol_atm': 2.748718805403695e+16, 'S2_mol_solid': 0.0, 'S2_mol_liquid': 1.783271562360517e+23, 'S2_mol_total': 1.7832718372323975e+23, 'SO2_mol_atm': 6.125584145509235e+17, 'SO2_mol_solid': 0.0, 'SO2_mol_liquid': 0.0, 'SO2_mol_total': 6.125584145509235e+17, 'H/O_atm': 0.00044228624491883094, 'H/C_atm': 0.0006342971574197387, 'H/N_atm': 0.004140708235633463, 'H/S_atm': 25.25101577446205, 'O/H_atm': 2260.9791995306605, 'O/C_atm': 1.434132679247454, 'O/N_atm': 9.36205519209256, 'O/S_atm': 57092.02143307928, 'C/H_atm': 1576.5481341078464, 'C/O_atm': 0.6972855541683489, 'C/N_atm': 6.528025842772929, 'C/S_atm': 39809.44180355594, 'N/H_atm': 241.50457919115271, 'N/O_atm': 0.10681415346115739, 'N/C_atm': 0.15318566808479836, 'N/S_atm': 6098.235938760617, 'S/H_atm': 0.03960236724462242, 'S/O_atm': 1.7515582298520563e-05, 'S/C_atm': 2.5119668970356572e-05, 'S/N_atm': 0.00016398184820038897, 'H_res': -7.832228500645161e-11, 'C_res': -8.436385858064516e-12, 'N_res': -8.525153224888622e-11, 'S_res': 3.75770415663184e-10}\n", + "{'M_atm': 2.1140842980826145e+21, 'P_surf': 262.9476790186874, 'H2O_bar': 0.30775751890664416, 'H2O_kg_atm': 1.5336596973329457e+18, 'H2O_kg_liquid': 1.1076196740192023e+22, 'H2O_kg_solid': 0.0, 'H2O_kg_total': 1.1077730399889356e+22, 'CO2_bar': 18.534278184620256, 'CO2_kg_atm': 2.256349453500211e+20, 'CO2_kg_liquid': 3.219934364629471e+20, 'CO2_kg_solid': 0.0, 'CO2_kg_total': 5.476283818129682e+20, 'H2_bar': 0.6213856911764326, 'H2_kg_atm': 3.4650159732465434e+17, 'H2_kg_liquid': 0.0, 'H2_kg_solid': 0.0, 'H2_kg_total': 3.4650159732465434e+17, 'CH4_bar': 8.425831775290706e-07, 'CH4_kg_atm': 3738492075592.4717, 'CH4_kg_liquid': 444260206416.3872, 'CH4_kg_solid': 0.0, 'CH4_kg_total': 4182752282008.859, 'CO_bar': 227.33373927426797, 'CO_kg_atm': 1.761393221566749e+21, 'CO_kg_liquid': 7.817866615092458e+20, 'CO_kg_solid': 0.0, 'CO_kg_total': 2.5431798830759946e+21, 'N2_bar': 16.148203676313003, 'N2_kg_atm': 1.2513496003657014e+20, 'N2_kg_liquid': 2.7275589700730954e+19, 'N2_kg_solid': 0.0, 'N2_kg_total': 1.5241054973730108e+20, 'S2_bar': 9.936901775580142e-05, 'S2_kg_atm': 1761928752930272.8, 'S2_kg_liquid': 1.1430770710402044e+22, 'S2_kg_solid': 0.0, 'S2_kg_total': 1.1430772472330798e+22, 'SO2_bar': 0.00221446180218089, 'SO2_kg_atm': 3.924416737177986e+16, 'SO2_kg_liquid': 0.0, 'SO2_kg_solid': 0.0, 'SO2_kg_total': 3.924416737177986e+16, 'H2O_vmr': 0.0011704135212571023, 'CO2_vmr': 0.0704865631588368, 'H2_vmr': 0.0023631533599970337, 'CH4_vmr': 3.2043757932131782e-09, 'CO_vmr': 0.8645588358972038, 'N2_vmr': 0.0614122312719306, 'S2_vmr': 3.779041447585448e-07, 'SO2_vmr': 8.421682254223322e-06, 'H_kg_atm': 5.181473596261645e+17, 'H_kg_liquid': 1.2394818526405898e+21, 'H_kg_solid': 0.0, 'H_kg_total': 1.240000000000216e+21, 'C_kg_atm': 8.168842528902642e+20, 'C_kg_liquid': 4.2311574710973576e+20, 'C_kg_solid': 0.0, 'C_kg_total': 1.24e+21, 'N_kg_atm': 1.2513496003657014e+20, 'N_kg_liquid': 2.7275589700730954e+19, 'N_kg_solid': 0.0, 'N_kg_total': 1.5241054973730108e+20, 'S_kg_atm': 2.0519862011422012e+16, 'S_kg_liquid': 1.1430770710402044e+22, 'S_kg_solid': 0.0, 'S_kg_total': 1.1430791230264055e+22, 'O_kg_atm': 1.171520402232764e+21, 'O_kg_liquid': 1.0517199772231104e+22, 'O_kg_solid': 0.0, 'O_kg_total': 1.1688720174463867e+22, 'atm_kg_per_mol': 0.0290652218643987, 'H2O_mol_atm': 8.513104971629337e+19, 'H2O_mol_solid': 0.0, 'H2O_mol_liquid': 6.14822347484581e+23, 'H2O_mol_total': 6.149074785342973e+23, 'CO2_mol_atm': 5.126901734833471e+21, 'CO2_mol_solid': 0.0, 'CO2_mol_liquid': 7.316369835558898e+21, 'CO2_mol_total': 1.244327157039237e+22, 'H2_mol_atm': 1.7188602363466793e+20, 'H2_mol_solid': 0.0, 'H2_mol_liquid': 0.0, 'H2_mol_total': 1.7188602363466793e+20, 'CH4_mol_atm': 233073072044418.44, 'CH4_mol_solid': 0.0, 'CH4_mol_liquid': 27697020350148.832, 'CH4_mol_total': 260770092394567.28, 'CO_mol_atm': 6.28844420409407e+22, 'CO_mol_solid': 0.0, 'CO_mol_liquid': 2.791098398819157e+22, 'CO_mol_total': 9.079542602913226e+22, 'N2_mol_atm': 4.4668722794520647e+21, 'N2_mol_solid': 0.0, 'N2_mol_liquid': 9.73641382906081e+20, 'N2_mol_total': 5.440513662358145e+21, 'S2_mol_atm': 2.7487188033233584e+16, 'S2_mol_solid': 0.0, 'S2_mol_liquid': 1.7832715616851863e+23, 'S2_mol_total': 1.7832718365570665e+23, 'SO2_mol_atm': 6.125584143192934e+17, 'SO2_mol_solid': 0.0, 'SO2_mol_liquid': 0.0, 'SO2_mol_total': 6.125584143192934e+17, 'H/O_atm': 0.00044228624498441824, 'H/C_atm': 0.0006342971575139049, 'H/N_atm': 0.0041407082359297374, 'H/S_atm': 25.251015788397947, 'O/H_atm': 2260.979199195376, 'O/C_atm': 1.4341326792476923, 'O/N_atm': 9.362055191374116, 'O/S_atm': 57092.021456121794, 'C/H_atm': 1576.548133873796, 'C/O_atm': 0.6972855541682331, 'C/N_atm': 6.528025842270885, 'C/S_atm': 39809.44181961654, 'N/H_atm': 241.5045791738727, 'N/O_atm': 0.1068141534693543, 'N/C_atm': 0.15318566809657927, 'N/S_atm': 6098.235941689862, 'S/H_atm': 0.0396023672227661, 'S/O_atm': 1.751558229145122e-05, 'S/C_atm': 2.5119668960222372e-05, 'S/N_atm': 0.00016398184812162144, 'H_res': 1.7419891612903225e-13, 'C_res': 0.0, 'N_res': 0.0, 'S_res': -2.9328741288827856e-12}\n", "Partial pressures\n", " H2O : 0.308 bar\n", " CO2 : 18.534 bar\n", @@ -146,11 +146,11 @@ " CH4 : 0.000 bar\n", " S2 : 0.000 bar\n", " SO2 : 0.002 bar\n", - "mubar = 0.029065221864415194\n", + "mubar = 0.0290652218643987\n", "ptot = 262.948 bar\n", " \n", - "{'H2O': 1.5328822021680773e+18, 'CO2': 2.25520558809126e+20, 'N2': 1.2507152235686263e+20, 'H2': 3.463259368981545e+17, 'CO': 1.7605002762055673e+21, 'CH4': 3736596830861.477, 'S2': 1761035537428053.2, 'SO2': 3.922427239885192e+16}\n", - "5.178846826662759e+17 8.164701301405516e+20 1.2507152235686263e+20 1761035537428053.2\n" + "{'H2O': 1.5328822024094979e+18, 'CO2': 2.2552055881116425e+20, 'N2': 1.2507152236761173e+20, 'H2': 3.4632593695269894e+17, 'CO': 1.760500276221478e+21, 'CH4': 3736596832067.9927, 'S2': 1761035536095232.8, 'SO2': 3.9224272384019816e+16}\n", + "5.1788468274784006e+17 8.164701301479304e+20 1.2507152236761173e+20 1761035536095232.8\n" ] }, { @@ -159,17 +159,15 @@ "text": [ "/home/n/nichollsh/PROTEUS/tools/../utils/surface_gases.py:297: RuntimeWarning: invalid value encountered in scalar power\n", " p_d['SO2'] = (gamma*pin['S2']*p_d['O2']**2)**0.5\n", - "/home/n/nichollsh/PROTEUS/tools/../utils/surface_gases.py:241: RuntimeWarning: invalid value encountered in log10\n", - " ppmw = 10 ** (-0.738 + 0.876 * np.log10(p) - 5.44e-5 * p_total)\n", "/home/n/nichollsh/PROTEUS/tools/../utils/surface_gases.py:109: RuntimeWarning: invalid value encountered in scalar power\n", " return const*p**exponent\n", - "/home/n/nichollsh/PROTEUS/tools/../utils/surface_gases.py:214: RuntimeWarning: invalid value encountered in scalar power\n", - " ppmw = pb_N2**0.5 * np.exp(5908.0 * pb_tot**0.5/temp - 1.6*fO2_shift)\n" + "/home/n/nichollsh/PROTEUS/tools/../utils/surface_gases.py:241: RuntimeWarning: invalid value encountered in log10\n", + " ppmw = 10 ** (-0.738 + 0.876 * np.log10(p) - 5.44e-5 * p_total)\n" ] } ], "source": [ - "p_d = solvevol_equilibrium_atmosphere(solvevol_target, COUPLER_options)\n", + "p_d = equilibrium_atmosphere(solvevol_target, COUPLER_options)\n", "\n", "print(p_d)\n", "\n", @@ -254,12 +252,12 @@ "text": [ "/home/n/nichollsh/PROTEUS/tools/../utils/surface_gases.py:214: RuntimeWarning: overflow encountered in exp\n", " ppmw = pb_N2**0.5 * np.exp(5908.0 * pb_tot**0.5/temp - 1.6*fO2_shift)\n", - "/home/n/nichollsh/PROTEUS/tools/../utils/surface_gases.py:413: RuntimeWarning: overflow encountered in scalar multiply\n", - " mass_int_d['N2'] = prefactor*ppmw_N2\n", + "/home/n/nichollsh/PROTEUS/tools/../utils/surface_gases.py:214: RuntimeWarning: invalid value encountered in scalar power\n", + " ppmw = pb_N2**0.5 * np.exp(5908.0 * pb_tot**0.5/temp - 1.6*fO2_shift)\n", "/home/n/nichollsh/PROTEUS/tools/../utils/surface_gases.py:229: RuntimeWarning: overflow encountered in exp\n", " ppmw = p*np.exp(4.93 - (0.000193 * p_total))\n", - "/home/n/nichollsh/PROTEUS/tools/../utils/surface_gases.py:183: RuntimeWarning: overflow encountered in scalar multiply\n", - " ppmw = 1.0E4*(4400*ppmw) / (36.6-44*ppmw)\n", + "/home/n/nichollsh/PROTEUS/tools/../utils/surface_gases.py:413: RuntimeWarning: overflow encountered in scalar multiply\n", + " mass_int_d['N2'] = prefactor*ppmw_N2\n", "/home/n/nichollsh/PROTEUS/tools/../utils/surface_gases.py:182: RuntimeWarning: overflow encountered in exp\n", " ppmw = (3.8E-7)*p*np.exp(-23*(p-1)/(83.15*temp))\n", "/home/n/nichollsh/PROTEUS/tools/../utils/surface_gases.py:183: RuntimeWarning: invalid value encountered in scalar divide\n", @@ -293,8 +291,8 @@ "for x in x_arr:\n", " COUPLER_options = deepcopy(BACKUP_options)\n", " COUPLER_options[x_key] = x\n", - " solvevol_target = solvevol_get_target_from_params(COUPLER_options)\n", - " p_d = solvevol_equilibrium_atmosphere(solvevol_target, COUPLER_options)\n", + " solvevol_target = get_target_from_params(COUPLER_options)\n", + " p_d = equilibrium_atmosphere(solvevol_target, COUPLER_options)\n", " for y in y_keys:\n", " y_out[y].append(p_d[y])\n", "\n", diff --git a/utils/dummy_atmosphere.py b/utils/dummy_atmosphere.py index 6b5c3c11..58be1195 100644 --- a/utils/dummy_atmosphere.py +++ b/utils/dummy_atmosphere.py @@ -15,7 +15,7 @@ def RunDummyAtm( dirs:dict, COUPLER_options:dict, T_magma:float, F_ins:float): # surface, relative to the surface temperature itself # Setting this to 0 will result in an entirely transparent atmosphere # Setting this to 1 will result in an OLR of zero - gamma = 0.9 + gamma = 0.5 # Parameters zenith_angle = COUPLER_options["zenith_angle"] diff --git a/utils/escape.py b/utils/escape.py index 28b53c2c..060a0761 100644 --- a/utils/escape.py +++ b/utils/escape.py @@ -7,13 +7,20 @@ log = logging.getLogger("PROTEUS") -def RunDummyEsc(): +def RunDummyEsc(hf_row:dict, dt:float): """Run dummy escape model. + Parameters + ---------- + hf_row : dict + Dictionary of helpfile variables, at this iteration only + dt : float + Time interval over which escape is occuring [yr] + Returns ---------- - phi : float - Bulk escape rate [kg/s] + esc_result : dict + Dictionary of elemental mass deltas [kg] """ log.info("Running dummy escape...") @@ -21,12 +28,42 @@ def RunDummyEsc(): # Hardcoded dummy value of bulk volatile escape rate [kg/s] phi = 1e10 - return phi + # store value + out = {} + out["rate_bulk"] = phi + + # calculate total mass of volatiles + M_vols = 0.0 + for e in element_list: + if e=='O': continue + M_vols += hf_row[e+"_kg_total"] + + # for each elem, calculate new total inventory while + # maintaining a constant mass mixing ratio + for e in element_list: + if e=='O': continue + + # current elemental mass ratio in atmosphere + emr = hf_row[e+"_kg_total"]/M_vols + + log.debug(" %s mass ratio = %.2e "%(e,emr)) + # new atmosphere mass of element e, keeping a constant mixing ratio of that element + e_atm = emr * (M_vols - phi * dt * secs_per_year) + + # calculate change in total mass of element e + out[e+"_dm"] = e_atm - hf_row[e+"_kg_total"] + + return out -# Zephyrus wrapper def RunZEPHYRUS(): + """Zephyrus wrapper + + Not yet implemented. + + """ log.info("Running ZEPHYRUS...") raise Exception("Not yet implemented") + diff --git a/utils/surface_gases.py b/utils/surface_gases.py index 2ec1a293..8fa2946e 100644 --- a/utils/surface_gases.py +++ b/utils/surface_gases.py @@ -244,7 +244,7 @@ def mafic_armstrong(self, p, p_total): def is_included(gas, ddict): return bool(ddict[gas+"_included"]>0) -def solvevol_get_partial_pressures(pin, ddict): +def get_partial_pressures(pin, ddict): """Partial pressure of all considered species from oxidised species""" # we only need to know pH2O, pCO2, and pN2, since reduced species @@ -300,14 +300,14 @@ def solvevol_get_partial_pressures(pin, ddict): -def solvevol_get_total_pressure(p_d): +def get_total_pressure(p_d): """Sum partial pressures to get total pressure""" return sum(p_d.values()) -def solvevol_atmosphere_mean_molar_mass(p_d): +def atmosphere_mean_molar_mass(p_d): """Mean molar mass of the atmosphere""" - ptot = solvevol_get_total_pressure(p_d) + ptot = get_total_pressure(p_d) mu_atm = 0 for key, value in p_d.items(): @@ -318,11 +318,11 @@ def solvevol_atmosphere_mean_molar_mass(p_d): -def solvevol_atmosphere_mass(pin, ddict): +def atmosphere_mass(pin, ddict): """Atmospheric mass of volatiles and totals for H, C, and N""" - p_d = solvevol_get_partial_pressures(pin, ddict) - mu_atm = solvevol_atmosphere_mean_molar_mass(p_d) + p_d = get_partial_pressures(pin, ddict) + mu_atm = atmosphere_mean_molar_mass(p_d) mass_atm_d = {} for key, value in p_d.items(): @@ -373,13 +373,13 @@ def solvevol_atmosphere_mass(pin, ddict): -def solvevol_dissolved_mass(pin, ddict): +def dissolved_mass(pin, ddict): """Volatile masses in the (molten) mantle""" mass_int_d = {} - p_d = solvevol_get_partial_pressures(pin, ddict) - ptot = solvevol_get_total_pressure(p_d) + p_d = get_partial_pressures(pin, ddict) + ptot = get_total_pressure(p_d) prefactor = 1E-6*ddict['M_mantle']*ddict['Phi_global'] @@ -442,7 +442,7 @@ def solvevol_dissolved_mass(pin, ddict): return mass_int_d -def solvevol_func(pin_arr, ddict, mass_target_d): +def func(pin_arr, ddict, mass_target_d): """Function to compute the residual of the mass balance given the partial pressures [bar]""" pin_dict = { @@ -453,10 +453,10 @@ def solvevol_func(pin_arr, ddict, mass_target_d): } # get atmospheric masses - mass_atm_d = solvevol_atmosphere_mass(pin_dict, ddict) + mass_atm_d = atmosphere_mass(pin_dict, ddict) # get (molten) mantle masses - mass_int_d = solvevol_dissolved_mass(pin_dict, ddict) + mass_int_d = dissolved_mass(pin_dict, ddict) # compute residuals res_l = [] @@ -481,7 +481,7 @@ def get_log_rand(rng): r = np.random.uniform(low=rng[0], high=rng[1]) return 10.0**r -def solvevol_get_initial_pressures(target_d): +def get_initial_pressures(target_d): """Get initial guesses of partial pressures""" # all in bar @@ -509,7 +509,7 @@ def solvevol_get_initial_pressures(target_d): return pH2O, pCO2, pN2, pS2 -def solvevol_get_target_from_params(ddict): +def get_target_from_params(ddict): N_ocean_moles = ddict['hydrogen_earth_oceans'] CH_ratio = ddict['CH_ratio'] @@ -523,7 +523,7 @@ def solvevol_get_target_from_params(ddict): target_d = {'H': H_kg, 'C': C_kg, 'N': N_kg, 'S': S_kg} return target_d -def solvevol_get_target_from_pressures(ddict): +def get_target_from_pressures(ddict): target_d = {} @@ -557,8 +557,8 @@ def solvevol_get_target_from_pressures(ddict): target_d['N'] = 0.0 # get dissolved+atmosphere masses from partial pressures - mass_atm_d = solvevol_atmosphere_mass(pin_dict, ddict) - mass_int_d = solvevol_dissolved_mass(pin_dict, ddict) + mass_atm_d = atmosphere_mass(pin_dict, ddict) + mass_int_d = dissolved_mass(pin_dict, ddict) for vol in ['H','C','N','S']: if vol in target_d.keys(): @@ -567,7 +567,7 @@ def solvevol_get_target_from_pressures(ddict): return target_d -def solvevol_equilibrium_atmosphere(target_d, ddict): +def equilibrium_atmosphere(target_d, ddict): """Solves for surface partial pressures assuming melt-vapour eqm @@ -593,8 +593,8 @@ def solvevol_equilibrium_atmosphere(target_d, ddict): # the ic never finds the physical solution (but in practice, # this doesn't seem to happen) while ier != 1: - x0 = solvevol_get_initial_pressures(target_d) - sol, info, ier, msg = fsolve(solvevol_func, x0, args=(ddict, target_d), full_output=True) + x0 = get_initial_pressures(target_d) + sol, info, ier, msg = fsolve(func, x0, args=(ddict, target_d), full_output=True) count += 1 # if any negative pressures, report ier!=1 @@ -603,7 +603,7 @@ def solvevol_equilibrium_atmosphere(target_d, ddict): ier = 0 # check residuals - this_resid = solvevol_func(sol, ddict, target_d) + this_resid = func(sol, ddict, target_d) if np.amax(np.abs(this_resid)) > 1.0: ier = 0 @@ -621,14 +621,14 @@ def solvevol_equilibrium_atmosphere(target_d, ddict): } # Final partial pressures [bar] - p_d = solvevol_get_partial_pressures(sol_dict, ddict) + p_d = get_partial_pressures(sol_dict, ddict) # Final masses [kg] - mass_atm_d = solvevol_atmosphere_mass(p_d, ddict) - mass_int_d = solvevol_dissolved_mass(p_d, ddict) + mass_atm_d = atmosphere_mass(p_d, ddict) + mass_int_d = dissolved_mass(p_d, ddict) # Residuals [relative] - res_l = solvevol_func(sol, ddict, target_d) + res_l = func(sol, ddict, target_d) log.debug(" Residuals: %s"%res_l) # Output dict From 527cb5954f1a665b23c2f4a9295f0bf5fb605166 Mon Sep 17 00:00:00 2001 From: Harrison Nicholls Date: Wed, 24 Jul 2024 21:09:37 +0100 Subject: [PATCH 6/8] Updated cfgs and docs with new params --- docs/source/usage.rst | 14 +++++++++++++- input/default.cfg | 4 ++++ input/dummy.cfg | 2 +- input/janus_mixed.cfg | 4 ++++ input/jgr_grid.cfg | 4 ++++ input/rce_mixed.cfg | 4 ++++ input/rce_steam.cfg | 4 ++++ proteus.py | 8 +++----- utils/dummy_atmosphere.py | 2 +- utils/escape.py | 2 +- 10 files changed, 39 insertions(+), 9 deletions(-) diff --git a/docs/source/usage.rst b/docs/source/usage.rst index a5a1d8df..78f2d5f2 100644 --- a/docs/source/usage.rst +++ b/docs/source/usage.rst @@ -239,7 +239,7 @@ configuration, but they must all be passed via the config file. - Greater than zero. * - ``shallow_ocean_layer`` - - Legaacy method for converging atmospheric and interior upward fluxes. + - Legacy method for converging atmospheric and interior upward fluxes. - True - Integer - 0: Off, 1: On @@ -256,6 +256,18 @@ configuration, but they must all be passed via the config file. - Float - Greater than or equal to 0. Set to 0 to disable. + * - ``escape_model`` + - Escape model to be used. + - False + - Integer + - 0: None, 1: ZEPHYRUS, 2: Dummy + + * - ``escape_stop`` + - Stop the simulation when the atmosphere mass drops below this fraction of its initial mass. + - False + - Float + - Values between zero and unity. + * - ``prevent_warming`` - Flag to ensure that the net upward energy flux is always positive, which prevents the star from causing net heating inside the planet. - False diff --git a/input/default.cfg b/input/default.cfg index 133896c5..b65ccb68 100644 --- a/input/default.cfg +++ b/input/default.cfg @@ -74,6 +74,10 @@ steady_dprel = 1.0e-9 # Percentage change in melt fraction over emit_stop = 1 # Enable this break condition F_crit = 0.2 # Critical flux, below which the model will terminate +# Atmospheric escape +escape_model = 0 # Escape model to be used, 0: None | 1: ZEPHYRUS | 2: Dummy +escape_stop = 3e-4 # Terminate when atm mass drops below this fraction of its initial mass + # Method for solving for T(p) profile atmosphere_model = 0 # Atmosphere model to be used, 0: JANUS | 1: AGNI atmosphere_solve_energy = 0 # Enable time-stepped atmosphere solution, 0: disabled | 1: enabled diff --git a/input/dummy.cfg b/input/dummy.cfg index bc89f728..3a29c40a 100644 --- a/input/dummy.cfg +++ b/input/dummy.cfg @@ -76,7 +76,7 @@ F_crit = 0.2 # Model will terminate when |F_atm| < F_ # Atmospheric escape escape_model = 2 # Escape model to be used, 0: None | 1: ZEPHYRUS | 2: Dummy -escape_stop_frac = 3e-4 # Terminate when atm mass drops below this fraction of its initial mass +escape_stop = 3e-4 # Terminate when atm mass drops below this fraction of its initial mass # Method for solving for T(p) profile atmosphere_model = 2 # Atmosphere model to be used, 0: JANUS | 1: AGNI | 2: Dummy diff --git a/input/janus_mixed.cfg b/input/janus_mixed.cfg index e1e2e274..dfc0be37 100644 --- a/input/janus_mixed.cfg +++ b/input/janus_mixed.cfg @@ -74,6 +74,10 @@ steady_dprel = 1.0e-9 # Percentage change in melt fraction ov emit_stop = 1 # Enable this break condition F_crit = 0.2 # Model will terminate when |F_atm| < F_crit +# Atmospheric escape +escape_model = 0 # Escape model to be used, 0: None | 1: ZEPHYRUS | 2: Dummy +escape_stop = 3e-4 # Terminate when atm mass drops below this fraction of its initial mass + # Method for solving for T(p) profile atmosphere_model = 0 # Atmosphere model to be used, 0: JANUS | 1: AGNI atmosphere_solve_energy = 0 # Enable time-stepped atmosphere solution, 0: disabled | 1: enabled diff --git a/input/jgr_grid.cfg b/input/jgr_grid.cfg index ef23496b..e7e383d1 100644 --- a/input/jgr_grid.cfg +++ b/input/jgr_grid.cfg @@ -74,6 +74,10 @@ steady_dprel = 1.0e-9 # Percentage change in melt fraction ov emit_stop = 1 # Enable this break condition F_crit = 0.2 # Model will terminate when |F_atm| < F_crit +# Atmospheric escape +escape_model = 0 # Escape model to be used, 0: None | 1: ZEPHYRUS | 2: Dummy +escape_stop = 3e-4 # Terminate when atm mass drops below this fraction of its initial mass + # Method for solving for T(p) profile atmosphere_model = 0 # Atmosphere model to be used, 0: JANUS | 1: AGNI atmosphere_solve_energy = 0 # Enable time-stepped atmosphere solution, 0: disabled | 1: enabled diff --git a/input/rce_mixed.cfg b/input/rce_mixed.cfg index a38b0dee..139260a4 100644 --- a/input/rce_mixed.cfg +++ b/input/rce_mixed.cfg @@ -74,6 +74,10 @@ steady_dprel = 1.0e-9 # Percentage change in melt fraction ov emit_stop = 1 # Enable this break condition F_crit = 0.2 # Model will terminate when |F_atm| < F_crit +# Atmospheric escape +escape_model = 0 # Escape model to be used, 0: None | 1: ZEPHYRUS | 2: Dummy +escape_stop = 3e-4 # Terminate when atm mass drops below this fraction of its initial mass + # Method for solving for T(p) profile atmosphere_model = 1 # Atmosphere model to be used, 0: JANUS | 1: AGNI atmosphere_solve_energy = 1 # Enable time-stepped atmosphere solution, 0: disabled | 1: enabled diff --git a/input/rce_steam.cfg b/input/rce_steam.cfg index 21ac46fe..8492cfe9 100644 --- a/input/rce_steam.cfg +++ b/input/rce_steam.cfg @@ -74,6 +74,10 @@ steady_dprel = 1.0e-9 # Percentage change in melt fraction ov emit_stop = 1 # Enable this break condition F_crit = 0.2 # Model will terminate when |F_atm| < F_crit +# Atmospheric escape +escape_model = 0 # Escape model to be used, 0: None | 1: ZEPHYRUS | 2: Dummy +escape_stop = 3e-4 # Terminate when atm mass drops below this fraction of its initial mass + # Method for solving for T(p) profile atmosphere_model = 1 # Atmosphere model to be used, 0: JANUS | 1: AGNI atmosphere_solve_energy = 1 # Enable time-stepped atmosphere solution, 0: disabled | 1: enabled diff --git a/proteus.py b/proteus.py index 2635d850..78ba57ac 100755 --- a/proteus.py +++ b/proteus.py @@ -413,10 +413,8 @@ def main(): log.debug(" escape %s: m=%.2e kg, dm=%+.2e (%.3f%%)"% (e, esc_m, esc_dm, 100*esc_dm/esc_m)) - # do not allow negative masses - solvevol_target[e] = max(0.0, solvevol_target[e]) - - + # do not allow negative masses + solvevol_target[e] = max(0.0, solvevol_target[e]) ############### / ESCAPE @@ -584,7 +582,7 @@ def main(): finished = True # Atmosphere has escaped - if hf_row["M_atm"] <= COUPLER_options["escape_stop_frac"]*hf_all.iloc[0]["M_atm"]: + if hf_row["M_atm"] <= COUPLER_options["escape_stop"]*hf_all.iloc[0]["M_atm"]: UpdateStatusfile(dirs, 15) log.info("") log.info("===> Atmosphere has escaped! <===") diff --git a/utils/dummy_atmosphere.py b/utils/dummy_atmosphere.py index 58be1195..70926767 100644 --- a/utils/dummy_atmosphere.py +++ b/utils/dummy_atmosphere.py @@ -15,7 +15,7 @@ def RunDummyAtm( dirs:dict, COUPLER_options:dict, T_magma:float, F_ins:float): # surface, relative to the surface temperature itself # Setting this to 0 will result in an entirely transparent atmosphere # Setting this to 1 will result in an OLR of zero - gamma = 0.5 + gamma = 0.7 # Parameters zenith_angle = COUPLER_options["zenith_angle"] diff --git a/utils/escape.py b/utils/escape.py index 060a0761..b630f73c 100644 --- a/utils/escape.py +++ b/utils/escape.py @@ -26,7 +26,7 @@ def RunDummyEsc(hf_row:dict, dt:float): log.info("Running dummy escape...") # Hardcoded dummy value of bulk volatile escape rate [kg/s] - phi = 1e10 + phi = 1e6 # store value out = {} From da404941cdc0f687fdab20516b449766b197f3fc Mon Sep 17 00:00:00 2001 From: Harrison Nicholls Date: Thu, 25 Jul 2024 09:07:50 +0100 Subject: [PATCH 7/8] RunPROTEUS bash script supports resume. Updated plots and printing --- docs/source/usage.rst | 5 +++-- input/dummy.cfg | 2 +- plot/cpl_global.py | 4 ++-- proteus.py | 25 +++++++++++++++++++------ tools/RunPROTEUS.sh | 10 ++++++++-- utils/escape.py | 2 +- utils/spider.py | 2 +- 7 files changed, 35 insertions(+), 15 deletions(-) diff --git a/docs/source/usage.rst b/docs/source/usage.rst index 78f2d5f2..c174e8f4 100644 --- a/docs/source/usage.rst +++ b/docs/source/usage.rst @@ -18,10 +18,11 @@ You can also run PROTEUS inside a Screen session using: .. code-block:: console - $ tools/RunPROTEUS.sh [cfgfile] [alias] [detach] + $ tools/RunPROTEUS.sh [cfgfile] [alias] [resume] [detach] Which runs PROTEUS using the config file ``[cfgfile]`` inside a Screen session with the -name ``[alias]``. The ``[detach]`` parameter (y/n) tells the session whether to immediately +name ``[alias]``. The ``[resume]`` parameter (y/n) tells the model whether to resume from +a previous state. The ``[detach]`` parameter (y/n) tells the session whether to immediately detach or not. This allows multiple instances of the model to be dispatched easily and safely. diff --git a/input/dummy.cfg b/input/dummy.cfg index 3a29c40a..6c7542f7 100644 --- a/input/dummy.cfg +++ b/input/dummy.cfg @@ -62,7 +62,7 @@ skin_k = 2.0 # W m-1 K-1 prevent_warming = 1 # Require that the planet only cool down over time, 0: disabled | 1: enabled # Break at solidification? -solid_stop = 1 +solid_stop = 0 phi_crit = 0.005 # melt fraction # Break at steady state? diff --git a/plot/cpl_global.py b/plot/cpl_global.py index 998f8372..7bacfa71 100755 --- a/plot/cpl_global.py +++ b/plot/cpl_global.py @@ -139,7 +139,7 @@ def plot_global( output_dir , COUPLER_options, logt=True, tmin=1e1): max_temp = np.amax(hf_all["T_surf"]) ax_cl.plot(hf_all["Time"], hf_all["T_surf"], ls="dashed", lw=lw, alpha=al, color=dict_colors["int"]) ax_cl.plot(hf_all["Time"], hf_all["T_surf"], ls="-", lw=lw, alpha=al, color=dict_colors["atm"]) - ax_cl.set_ylim(min(1000.0,min_temp) , max(3500.0,max_temp)) + ax_cl.set_ylim(min(1000.0,min_temp-25) , max(3500.0,max_temp+25)) # PLOT ax_bl @@ -147,7 +147,7 @@ def plot_global( output_dir , COUPLER_options, logt=True, tmin=1e1): ax_bl.plot( hf_all["Time"], 1.0-hf_all["RF_depth"], color=dict_colors["int"], ls="solid", lw=lw, alpha=al, label=r'Rheol. front') ax_bl.plot( hf_all["Time"], hf_all["Phi_global"], color=dict_colors["atm"], linestyle=':', lw=lw, alpha=al, label=r'Melt fraction') ax_bl.legend(loc='center left', **leg_kwargs) - ax_bl.set_ylim(0.0,1.0) + ax_bl.set_ylim(0.0,1.01) # PLOT ax_tr diff --git a/proteus.py b/proteus.py index 78ba57ac..e16833b4 100755 --- a/proteus.py +++ b/proteus.py @@ -26,8 +26,8 @@ def main(): resume = bool(args["resume"]) # Read in COUPLER input file - cfg_file = os.path.abspath(str(args["cfg"])) - COUPLER_options, time_dict = ReadInitFile( cfg_file , verbose=False ) + cfgsrc = os.path.abspath(str(args["cfg"])) + COUPLER_options, time_dict = ReadInitFile( cfgsrc , verbose=False ) # Set directories dictionary utils.constants.dirs = SetDirectories(COUPLER_options) @@ -60,7 +60,7 @@ def main(): log.info("Hostname : " + str(os.uname()[1])) log.info("PROTEUS hash: " + GitRevision(dirs["coupler"])) log.info("Py version : " + sys.version.split(' ')[0]) - log.info("Config file : " + cfg_file) + log.info("Config file : " + cfgsrc) log.info("Output dir : " + dirs["output"]) log.info("FWL data dir: " + dirs["fwl"]) if COUPLER_options["atmosphere_model"] in [0,1]: @@ -84,6 +84,9 @@ def main(): # Model has completed? finished = False + # Config file paths + cfgbak = os.path.join(dirs["output"],"init_coupler.cfg") + # Is the model resuming from a previous state? if not resume: # New simulation @@ -92,7 +95,7 @@ def main(): IC_INTERIOR = 1 # Copy config file to output directory, for future reference - shutil.copyfile(args["cfg"], os.path.join(dirs["output"],"init_coupler.cfg")) + shutil.copyfile(args["cfg"], cfgbak) # No previous iterations to be stored. It is therefore important that the # submodules do not try to read data from the 'past' iterations, since they do @@ -137,6 +140,16 @@ def main(): # Resuming from disk log.info("Resuming the simulation from the disk") + # Copy cfg file + if os.path.exists(cfgbak): + if cfgbak == cfgsrc: + # resuming from backed-up cfg file, not the original + pass + else: + # delete old and copy new + safe_rm(cfgbak) + shutil.copyfile(cfgsrc, cfgbak) + # SPIDER initial condition IC_INTERIOR = 2 @@ -304,7 +317,7 @@ def main(): hf_row["T_eqm"] = T_eqm_new hf_row["T_skin"] = T_eqm_new * (0.5**0.25) # Assuming a grey stratosphere in radiative eqm (https://doi.org/10.5194/esd-7-697-2016) - log.info("Instellation change: %+.4e W m-2 (to 4dp)" % abs(S_0 - F_inst_prev)) + log.debug("Instellation change: %+.4e W m-2 (to 4dp)" % abs(S_0 - F_inst_prev)) # Calculate a new (historical) stellar spectrum if ( ( abs( hf_row["Time"] - sspec_prev ) > COUPLER_options['sspec_dt_update'] ) \ @@ -410,7 +423,7 @@ def main(): esc_m = solvevol_target[e] esc_dm = esc_result[e+"_dm"] - log.debug(" escape %s: m=%.2e kg, dm=%+.2e (%.3f%%)"% + log.debug(" escape %s: m=%.2e kg, dm=%.2e (%.3f%%)"% (e, esc_m, esc_dm, 100*esc_dm/esc_m)) # do not allow negative masses diff --git a/tools/RunPROTEUS.sh b/tools/RunPROTEUS.sh index a7ba870b..941eebb0 100755 --- a/tools/RunPROTEUS.sh +++ b/tools/RunPROTEUS.sh @@ -24,14 +24,16 @@ then echo "ERROR: Config file or alias not provided" echo " First argument: config file (string)" echo " Second argument: screen alias (string)" - echo " Third argument: detach? (y or n)" + echo " Third argument: resume? (y or n)" + echo " Fourth argument: detach? (y or n)" sleep 1.0 exit 1 else # Set variables CFGFILE="$1" ALIAS="$2" - DETACH=$(echo "$3" | tr -d ' ' | tr '[:upper:]' '[:lower:]' | cut -c1-1) # strip spaces, covert to lowercase, get first char + RESUME=$(echo "$3" | tr -d ' ' | tr '[:upper:]' '[:lower:]' | cut -c1-1) # strip spaces, covert to lowercase, get first char + DETACH=$(echo "$4" | tr -d ' ' | tr '[:upper:]' '[:lower:]' | cut -c1-1) # strip spaces, covert to lowercase, get first char EXECUTABLE="$COUPLER_DIR/proteus.py" # Clear dead screens @@ -51,7 +53,11 @@ else # Dispatch screen session with PROTEUS inside echo " Dispatching screen session..." + COMMAND="python $EXECUTABLE --cfg $CFGFILE" + if [[ "$RESUME" == "y" ]]; then + COMMAND="$COMMAND --resume" + fi if [[ "$DETACH" == "y" ]]; then screen -S $ALIAS -d -m bash -c "$COMMAND" diff --git a/utils/escape.py b/utils/escape.py index b630f73c..8adc1f04 100644 --- a/utils/escape.py +++ b/utils/escape.py @@ -26,7 +26,7 @@ def RunDummyEsc(hf_row:dict, dt:float): log.info("Running dummy escape...") # Hardcoded dummy value of bulk volatile escape rate [kg/s] - phi = 1e6 + phi = 2e7 # store value out = {} diff --git a/utils/spider.py b/utils/spider.py index d5633951..b48217f4 100644 --- a/utils/spider.py +++ b/utils/spider.py @@ -467,7 +467,7 @@ def RunSPIDER( dirs:dict, COUPLER_options:dict, # info log.info("Running SPIDER...") - log.debug(" IC_INTERIOR = %d"%IC_INTERIOR) + log.debug("IC_INTERIOR = %d"%IC_INTERIOR) # parameters max_attempts = 7 # maximum number of attempts From 936e5a0145b8d9454a69e12663def2b9bf57a59b Mon Sep 17 00:00:00 2001 From: Harrison Nicholls Date: Thu, 25 Jul 2024 14:41:59 +0100 Subject: [PATCH 8/8] Tidied things up. Updated plots. Updated cfgs and docs with new parameter. --- docs/source/usage.rst | 8 ++++- input/default.cfg | 1 + input/dummy.cfg | 1 + input/janus_mixed.cfg | 1 + input/jgr_grid.cfg | 1 + input/rce_mixed.cfg | 2 ++ input/rce_steam.cfg | 1 + proteus.py | 13 +++++--- utils/coupler.py | 76 ++++++++++++++++++++++++------------------- utils/escape.py | 23 ++++++------- 10 files changed, 76 insertions(+), 51 deletions(-) diff --git a/docs/source/usage.rst b/docs/source/usage.rst index c174e8f4..76fc1292 100644 --- a/docs/source/usage.rst +++ b/docs/source/usage.rst @@ -267,7 +267,13 @@ configuration, but they must all be passed via the config file. - Stop the simulation when the atmosphere mass drops below this fraction of its initial mass. - False - Float - - Values between zero and unity. + - Values between zero and unity (exclusive). + + * - ``escape_dummy_rate`` + - Bulk escape rate for dummy escape model [kg s-1] + - False + - Float + - Any reasonable positive value. * - ``prevent_warming`` - Flag to ensure that the net upward energy flux is always positive, which prevents the star from causing net heating inside the planet. diff --git a/input/default.cfg b/input/default.cfg index b65ccb68..90004be8 100644 --- a/input/default.cfg +++ b/input/default.cfg @@ -77,6 +77,7 @@ F_crit = 0.2 # Critical flux, below which the model w # Atmospheric escape escape_model = 0 # Escape model to be used, 0: None | 1: ZEPHYRUS | 2: Dummy escape_stop = 3e-4 # Terminate when atm mass drops below this fraction of its initial mass +escape_dummy_rate = 0.0 # Bulk escape rate for dummy escape model [kg s-1] # Method for solving for T(p) profile atmosphere_model = 0 # Atmosphere model to be used, 0: JANUS | 1: AGNI diff --git a/input/dummy.cfg b/input/dummy.cfg index 6c7542f7..d7905f95 100644 --- a/input/dummy.cfg +++ b/input/dummy.cfg @@ -77,6 +77,7 @@ F_crit = 0.2 # Model will terminate when |F_atm| < F_ # Atmospheric escape escape_model = 2 # Escape model to be used, 0: None | 1: ZEPHYRUS | 2: Dummy escape_stop = 3e-4 # Terminate when atm mass drops below this fraction of its initial mass +escape_dummy_rate = 2e7 # Bulk escape rate for dummy escape model [kg s-1] # Method for solving for T(p) profile atmosphere_model = 2 # Atmosphere model to be used, 0: JANUS | 1: AGNI | 2: Dummy diff --git a/input/janus_mixed.cfg b/input/janus_mixed.cfg index dfc0be37..d0b38d53 100644 --- a/input/janus_mixed.cfg +++ b/input/janus_mixed.cfg @@ -77,6 +77,7 @@ F_crit = 0.2 # Model will terminate when |F_atm| < F_ # Atmospheric escape escape_model = 0 # Escape model to be used, 0: None | 1: ZEPHYRUS | 2: Dummy escape_stop = 3e-4 # Terminate when atm mass drops below this fraction of its initial mass +escape_dummy_rate = 0.0 # Bulk escape rate for dummy escape model [kg s-1] # Method for solving for T(p) profile atmosphere_model = 0 # Atmosphere model to be used, 0: JANUS | 1: AGNI diff --git a/input/jgr_grid.cfg b/input/jgr_grid.cfg index e7e383d1..f7344166 100644 --- a/input/jgr_grid.cfg +++ b/input/jgr_grid.cfg @@ -77,6 +77,7 @@ F_crit = 0.2 # Model will terminate when |F_atm| < F_ # Atmospheric escape escape_model = 0 # Escape model to be used, 0: None | 1: ZEPHYRUS | 2: Dummy escape_stop = 3e-4 # Terminate when atm mass drops below this fraction of its initial mass +escape_dummy_rate = 0.0 # Bulk escape rate for dummy escape model [kg s-1] # Method for solving for T(p) profile atmosphere_model = 0 # Atmosphere model to be used, 0: JANUS | 1: AGNI diff --git a/input/rce_mixed.cfg b/input/rce_mixed.cfg index 139260a4..6e59de44 100644 --- a/input/rce_mixed.cfg +++ b/input/rce_mixed.cfg @@ -77,6 +77,8 @@ F_crit = 0.2 # Model will terminate when |F_atm| < F_ # Atmospheric escape escape_model = 0 # Escape model to be used, 0: None | 1: ZEPHYRUS | 2: Dummy escape_stop = 3e-4 # Terminate when atm mass drops below this fraction of its initial mass +escape_dummy_rate = 0.0 # Bulk escape rate for dummy escape model [kg s-1] + # Method for solving for T(p) profile atmosphere_model = 1 # Atmosphere model to be used, 0: JANUS | 1: AGNI diff --git a/input/rce_steam.cfg b/input/rce_steam.cfg index 8492cfe9..dc7f7016 100644 --- a/input/rce_steam.cfg +++ b/input/rce_steam.cfg @@ -77,6 +77,7 @@ F_crit = 0.2 # Model will terminate when |F_atm| < F_ # Atmospheric escape escape_model = 0 # Escape model to be used, 0: None | 1: ZEPHYRUS | 2: Dummy escape_stop = 3e-4 # Terminate when atm mass drops below this fraction of its initial mass +escape_dummy_rate = 0.0 # Bulk escape rate for dummy escape model [kg s-1] # Method for solving for T(p) profile atmosphere_model = 1 # Atmosphere model to be used, 0: JANUS | 1: AGNI diff --git a/proteus.py b/proteus.py index e16833b4..2f3d767e 100755 --- a/proteus.py +++ b/proteus.py @@ -410,7 +410,7 @@ def main(): esc_result = RunZEPHYRUS() elif COUPLER_options["escape_model"] == 2: - esc_result = RunDummyEsc(hf_row, dt) + esc_result = RunDummyEsc(hf_row, dt, COUPLER_options["escape_dummy_rate"]) # store total escape rate hf_row["esc_rate_total"] = esc_result["rate_bulk"] @@ -419,10 +419,15 @@ def main(): # update elemental mass targets for e in element_list: if e=='O': continue - solvevol_target[e] += esc_result[e+"_dm"] - esc_m = solvevol_target[e] - esc_dm = esc_result[e+"_dm"] + # store change, for statistics + esc_m = esc_result[e+"_kg_total"] + esc_dm = esc_m - solvevol_target[e] + + # update total elemental inventory + solvevol_target[e] = esc_m + + # print info to user log.debug(" escape %s: m=%.2e kg, dm=%.2e (%.3f%%)"% (e, esc_m, esc_dm, 100*esc_dm/esc_m)) diff --git a/utils/coupler.py b/utils/coupler.py index 7fcedd2c..6fdb9db7 100644 --- a/utils/coupler.py +++ b/utils/coupler.py @@ -8,16 +8,17 @@ log = logging.getLogger("PROTEUS") -import plot.cpl_atmosphere as cpl_atmosphere -import plot.cpl_global as cpl_global -import plot.cpl_stacked as cpl_stacked -import plot.cpl_interior as cpl_interior -import plot.cpl_sflux as cpl_sflux -import plot.cpl_sflux_cross as cpl_sflux_cross -import plot.cpl_fluxes_global as cpl_fluxes_global -import plot.cpl_fluxes_atmosphere as cpl_fluxes_atmosphere -import plot.cpl_interior_cmesh as cpl_interior_cmesh -import plot.cpl_observables as cpl_observables +from plot.cpl_atmosphere import plot_atmosphere +from plot.cpl_global import plot_global +from plot.cpl_stacked import plot_stacked +from plot.cpl_interior import plot_interior +from plot.cpl_sflux import plot_sflux +from plot.cpl_sflux_cross import plot_sflux_cross +from plot.cpl_fluxes_global import plot_fluxes_global +from plot.cpl_fluxes_atmosphere import plot_fluxes_atmosphere +from plot.cpl_interior_cmesh import plot_interior_cmesh +from plot.cpl_observables import plot_observables +from plot.cpl_elements import plot_elements def GitRevision(dir:str) -> str: ''' @@ -117,8 +118,8 @@ def GetHelpfileKeys(): "z_obs", "transit_depth", "contrast_ratio", # observed from infinity # Escape - "esc_rate_total", - + "esc_rate_total", + # Atmospheric composition "M_atm", "P_surf", "atm_kg_per_mol", # more keys added below ] @@ -272,11 +273,14 @@ def ReadInitFile(init_file_passed:str, verbose=False): if not line.startswith("time_"): # Some parameters are int - if key in [ "solid_stop", "steady_stop", "iter_max", "emit_stop", "escape_model", - "plot_iterfreq", "stellar_heating", "mixing_length", "shallow_ocean_layer", - "atmosphere_chemistry", "solvevol_use_params", "insert_rscatter", "water_cloud", - "tropopause", "F_atm_bc", "atmosphere_solve_energy", "atmosphere_surf_state", - "dt_dynamic", "prevent_warming", "atmosphere_model", "atmosphere_nlev"]: + if key in [ "solid_stop", "steady_stop", "iter_max", "emit_stop", + "escape_model", "atmosphere_surf_state", "water_cloud", + "plot_iterfreq", "stellar_heating", "mixing_length", + "atmosphere_chemistry", "solvevol_use_params", + "tropopause", "F_atm_bc", "atmosphere_solve_energy", + "dt_dynamic", "prevent_warming", "atmosphere_model", + "atmosphere_nlev", "insert_rscatter", + "shallow_ocean_layer", "SEPARATION"]: val = int(val) # Some are str @@ -365,15 +369,22 @@ def UpdatePlots( output_dir, COUPLER_options, end=False, num_snapshots=7): """ + + # Check model configuration + dummy_atm = bool(COUPLER_options["atmosphere_model"] == 2) + escape = bool(COUPLER_options["escape_model"] > 0) + + # Get all JSON files output_times = get_all_output_times( output_dir ) # Global properties for all timesteps - if len(output_times) > 1: - cpl_global.plot_global(output_dir, COUPLER_options) + if len(output_times) > 2: + plot_global(output_dir, COUPLER_options) - # Check if we are using the dummy atmosphere - dummy_atm = (COUPLER_options["atmosphere_model"] == 2) + # Elemental mass inventory + if escape: + plot_elements(output_dir, COUPLER_options["plot_format"]) # Filter to JSON files with corresponding NetCDF files if not dummy_atm: @@ -402,25 +413,24 @@ def UpdatePlots( output_dir, COUPLER_options, end=False, num_snapshots=7): plot_times = sorted(set(plot_times)) # Remove any duplicates + resort log.debug("Snapshots to plot:" + str(plot_times)) - # Specific timesteps for paper plots - cpl_interior.plot_interior(output_dir, plot_times, COUPLER_options["plot_format"]) + # Temperature profiles + plot_interior(output_dir, plot_times, COUPLER_options["plot_format"]) if not dummy_atm: - cpl_atmosphere.plot_atmosphere(output_dir, plot_times, COUPLER_options["plot_format"]) - cpl_stacked.plot_stacked(output_dir, plot_times, COUPLER_options["plot_format"]) + plot_atmosphere(output_dir, plot_times, COUPLER_options["plot_format"]) + plot_stacked(output_dir, plot_times, COUPLER_options["plot_format"]) if COUPLER_options["atmosphere_model"] != 1: # don't make this plot for AGNI, since it will do it itself - cpl_fluxes_atmosphere.plot_fluxes_atmosphere(output_dir, COUPLER_options["plot_format"]) - + plot_fluxes_atmosphere(output_dir, COUPLER_options["plot_format"]) # Only at the end of the simulation if end: - cpl_global.plot_global(output_dir, COUPLER_options, logt=False) - cpl_interior_cmesh.plot_interior_cmesh(output_dir, plot_format=COUPLER_options["plot_format"]) - cpl_sflux.plot_sflux(output_dir, plot_format=COUPLER_options["plot_format"]) - cpl_sflux_cross.plot_sflux_cross(output_dir, plot_format=COUPLER_options["plot_format"]) - cpl_fluxes_global.plot_fluxes_global(output_dir, COUPLER_options) - cpl_observables.plot_observables(output_dir, plot_format=COUPLER_options["plot_format"]) + plot_global(output_dir, COUPLER_options, logt=False) + plot_interior_cmesh(output_dir, plot_format=COUPLER_options["plot_format"]) + plot_sflux(output_dir, plot_format=COUPLER_options["plot_format"]) + plot_sflux_cross(output_dir, plot_format=COUPLER_options["plot_format"]) + plot_fluxes_global(output_dir, COUPLER_options) + plot_observables(output_dir, plot_format=COUPLER_options["plot_format"]) # Close all figures plt.close() diff --git a/utils/escape.py b/utils/escape.py index 8adc1f04..04d0a501 100644 --- a/utils/escape.py +++ b/utils/escape.py @@ -7,7 +7,7 @@ log = logging.getLogger("PROTEUS") -def RunDummyEsc(hf_row:dict, dt:float): +def RunDummyEsc(hf_row:dict, dt:float, phi_bulk:float): """Run dummy escape model. Parameters @@ -16,43 +16,40 @@ def RunDummyEsc(hf_row:dict, dt:float): Dictionary of helpfile variables, at this iteration only dt : float Time interval over which escape is occuring [yr] + phi_bulk : float + Bulk escape rate [kg s-1] Returns ---------- esc_result : dict - Dictionary of elemental mass deltas [kg] + Dictionary of updated total elemental mass inventories [kg] """ log.info("Running dummy escape...") - # Hardcoded dummy value of bulk volatile escape rate [kg/s] - phi = 2e7 - # store value out = {} - out["rate_bulk"] = phi + out["rate_bulk"] = phi_bulk - # calculate total mass of volatiles + # calculate total mass of volatiles (except oxygen, which is set by fO2) M_vols = 0.0 for e in element_list: if e=='O': continue M_vols += hf_row[e+"_kg_total"] + # for each elem, calculate new total inventory while # maintaining a constant mass mixing ratio for e in element_list: if e=='O': continue - # current elemental mass ratio in atmosphere + # current elemental mass ratio in total emr = hf_row[e+"_kg_total"]/M_vols log.debug(" %s mass ratio = %.2e "%(e,emr)) - # new atmosphere mass of element e, keeping a constant mixing ratio of that element - e_atm = emr * (M_vols - phi * dt * secs_per_year) - - # calculate change in total mass of element e - out[e+"_dm"] = e_atm - hf_row[e+"_kg_total"] + # new total mass of element e, keeping a constant mixing ratio of that element + out[e+"_kg_total"] = emr * (M_vols - phi_bulk * dt * secs_per_year) return out