Select the sites you want to simulate "\
-+"(multi-selection possible):",
-sizing_mode="stretch_width")
+ text="
Pick sites
Select the sites you want to simulate "
+ + "(multi-selection possible):",
+ sizing_mode="stretch_width")
# Site names
site_labels = [row["name"] for _, row in nlp_sites_gdf.iterrows()]
sites_checkbox_group = CheckboxGroup(labels=site_labels, active=[])
@@ -75,42 +96,45 @@ def _input_changed(attr, new, old):
# Reset checks if anything changes
sites_checkbox_group.on_change('active', _input_changed)
+
def _check_sites():
return bool(sites_checkbox_group.active)
+
sites_section = column(site_description_div, sites_checkbox_group)
-################################################################################
+###############################################################################
"""
Directory paths
"""
-################################################################################
+###############################################################################
input_dir_paths_div = Div(
-text="
Set directory paths
Skip this section to use the default paths "\
-+ "as indicated. Paths can be absolute or relative to the repository root. ",
-sizing_mode="stretch_width")
+ text="
Set directory paths
Skip this section to use the default paths "
+ + "as indicated. Paths can be absolute or relative to the repository root. ",
+ sizing_mode="stretch_width")
clm_in_div = Div(
-text='''Enter the path for the CLM input: ''',
-css_classes=['item-description'], sizing_mode='stretch_width')
+ text='''Enter the path for the CLM input: ''',
+ css_classes=['item-description'], sizing_mode='stretch_width')
clm_input_dir_text_input = TextInput(value="data/input/clm")
clm_input_dir_text_input.on_change('value', _input_changed)
cases_div = Div(
-text='''Enter the path for the case folders: ''',
-css_classes=['item-description'], sizing_mode='stretch_width')
+ text='''Enter the path for the case folders: ''',
+ css_classes=['item-description'], sizing_mode='stretch_width')
cases_dir_text_input = TextInput(value="data/cases")
cases_dir_text_input.on_change('value', _input_changed)
output_div = Div(
-text='''Enter the path for the model output: ''',
-css_classes=['item-description'], sizing_mode='stretch_width')
+ text='''Enter the path for the model output: ''',
+ css_classes=['item-description'], sizing_mode='stretch_width')
output_dir_text_input = TextInput(value="data/output")
output_dir_text_input.on_change('value', _input_changed)
+
def _check_paths():
for input in [clm_input_dir_text_input, cases_dir_text_input,
- output_dir_text_input]:
+ output_dir_text_input]:
try:
if pth.is_valid_path(Path(input.value), type="dir"):
continue
@@ -120,31 +144,34 @@ def _check_paths():
except:
return False
return True
+
+
# Layout
path_input_section = column(input_dir_paths_div,
-row(clm_in_div, clm_input_dir_text_input),
-row(cases_div, cases_dir_text_input),
-row(output_div, output_dir_text_input)
-)
+ row(clm_in_div, clm_input_dir_text_input),
+ row(cases_div, cases_dir_text_input),
+ row(output_div, output_dir_text_input)
+ )
-################################################################################
+###############################################################################
"""
Simulation period
"""
-################################################################################
+###############################################################################
period_div = Div(
-text="
",
-sizing_mode="stretch_width")
+ sizing_mode="stretch_width")
### Radio buttons - run type
type_run_div = Div(text='''Select the model run type.''',
-css_classes=['item-description'], sizing_mode='stretch_width')
+ css_classes=['item-description'], sizing_mode='stretch_width')
type_run_labels = constraints_dict['type_run']['valid_values']
type_run_radio = RadioButtonGroup(labels=type_run_labels, active=0)
#show(runtype_radio_buttons)
### Radio buttons - run mode
type_model_div = Div(
-text='''Select the model run mode.''',
-css_classes=['item-description'], sizing_mode='stretch_width')
+ text='''Select the model run mode.''',
+ css_classes=['item-description'], sizing_mode='stretch_width')
type_model_labels = constraints_dict['type_model']['valid_values']
type_model_radio = RadioButtonGroup(labels=type_model_labels, active=0)
### Column for display
clm_settings_section = column(clm_div,
-type_run_div, type_run_radio,
-type_model_div, type_model_radio)
+ type_run_div, type_run_radio,
+ type_model_div, type_model_radio)
-################################################################################
+###############################################################################
"""
FATES settings
"""
-################################################################################
+###############################################################################
fates_div = Div(
-text="
FATES settings
OBS! Only used if model run mode includes FATES.",
-sizing_mode="stretch_width")
+ text="
FATES settings
OBS! Only used if model run mode includes FATES.",
+ sizing_mode="stretch_width")
### PFTs to include
-pft_div = Div(text='''Select all the ''' \
-+ '''''' \
-+ '''PFTs you want to include.''',
-css_classes=['item-description'], sizing_mode='stretch_width')
+pft_div = Div(text='''Select all the '''
+ + ''''''
+ + '''PFTs you want to include.''',
+ css_classes=['item-description'], sizing_mode='stretch_width')
pft_options = constraints_dict['pft_indices']['long_names']
pft_multi_choice = MultiChoice(value=pft_options, options=pft_options)
fates_settings_section = column(fates_div, pft_div, pft_multi_choice)
-################################################################################
+###############################################################################
"""
Button to create settings file
"""
-################################################################################
+###############################################################################
create_button_div = Div(
-text="
Create file
Enter a name for the new settings file: ",
-sizing_mode="stretch_width")
+ text="
Create file
Enter a name for the new settings file: ",
+ sizing_mode="stretch_width")
input = TextInput(value="file-name")
check_input_button = Button(label="Check input", button_type='primary',
-disabled=False)
+ disabled=False)
create_button = Button(label="Create file!", button_type='primary',
-disabled=True)
+ disabled=True)
output = Paragraph(text=default_text, css_classes=["invalid"])
create_file_section = column(create_button_div, input,
-row(check_input_button, create_button), output)
+ row(check_input_button, create_button), output)
"""
Updates when buttons are clicked
"""
+
+
def check_input():
"""Button handler for checking the user input."""
@@ -234,13 +264,14 @@ def check_input():
pass
elif not _check_paths():
output.text = "One of the specified paths does not exist. " \
- + "See console for details."
+ + "See console for details."
_input_invalid()
pass
else:
output.text = "Input checks passed. You can now create the file."
_input_valid()
+
def update():
"""Button handler for creating a new file."""
@@ -272,16 +303,16 @@ def update():
# Write settings file
helpers.write_settings_file(
- file_name=str(file_name),
- sites2run=",".join(site_names),
- dir_cases=str(clm_input_dir_text_input.value),
- dir_clm_input=str(cases_dir_text_input.value),
- dir_output=str(output_dir_text_input.value),
- start_time=str(start_date),
- end_time=str(end_date),
- type_run=str(type_run_labels[type_run_radio.active]),
- type_model=str(type_model_labels[type_model_radio.active]),
- pft_indices=",".join([str(x) for x in pft_indices])
+ file_name=str(file_name),
+ sites2run=",".join(site_names),
+ dir_cases=str(clm_input_dir_text_input.value),
+ dir_clm_input=str(cases_dir_text_input.value),
+ dir_output=str(output_dir_text_input.value),
+ start_time=str(start_date),
+ end_time=str(end_date),
+ type_run=str(type_run_labels[type_run_radio.active]),
+ type_model=str(type_model_labels[type_model_radio.active]),
+ pft_indices=",".join([str(x) for x in pft_indices])
)
output.text = f"Created '{file_name}'!"
@@ -289,15 +320,166 @@ def update():
output.text = f"Error when creating {file_name}."
raise
+
check_input_button.on_click(check_input)
create_button.on_click(update)
-################################################################################
+###############################################################################
+###############################################################################
+###############################################################################
+"""
+Build and run cases tab
+"""
+###############################################################################
+###############################################################################
+###############################################################################
+
+"""
+Set settings header as defined in html file.
+"""
+with open(app_root_dir_path / 'static/html/run_header.html') as html_file:
+ run_html_text = html_file.read()
+
+run_header_div = Div(text=run_html_text, sizing_mode="stretch_width")
+
+###############################################################################
+"""
+Build cases
+"""
+###############################################################################
+
+
+root_path = Path(__file__).parents[2]
+settings_path = root_path / 'landsites_tools/custom_settings/'
+script_path = root_path / 'landsites_tools/simulation/'
+
+
+def get_settings_files():
+ """Get all settings files in 'landsites_tools/custom_settings' in a list"""
+
+ file_path_str_list = glob.glob(str(settings_path) + "/*.txt")
+
+ file_paths_list = [Path(fname) for fname in file_path_str_list]
+ file_names_list = [p.name for p in file_paths_list]
+
+ return(file_paths_list, file_names_list)
+
+
+def retrieve_select_options(settings_file_names):
+
+ make_cases_select_options = \
+ [(str(idx), f_name) for idx, f_name in enumerate(settings_file_names)]
+
+ return(make_cases_select_options)
+
+
+make_cases_div = Div(
+ text="
Make case(s)
",
+ sizing_mode="stretch_width")
+
+# Settings file picker
+make_cases_select_div = Div(text="Select one settings file.",
+ css_classes=['item-description'],
+ sizing_mode='stretch_width')
+
+# List for picking a file
+settings_file_paths, settings_file_names = get_settings_files()
+make_cases_select_options = retrieve_select_options(settings_file_names)
+
+make_cases_select = MultiSelect(value=[],
+ options=make_cases_select_options)
+
+# Make cases button
+make_cases_button = Button(label="Make case(s)", button_type='primary',
+ disabled=False)
+refresh_button = Button(label="Refresh files", button_type='default',
+ disabled=False)
+
+make_cases_output = Div(text="Click the button above to make new cases.",
+ css_classes=["item-description"],
+ style={'overflow': 'auto',
+ 'width': '100%',
+ 'height': '250px'}
+ )
+
+"""
+JS_scroll_bottom = CustomJS(args={'bokeh_div': make_cases_output}, code='''
+function updateScroll(){
+
+ var element = document.getElementsByClassName("build-log")[0].children[0];
+
+ element.scrollTop = element.scrollHeight;
+ console.log(element.scrollTop)
+}
+updateScroll();
+
+''')
+
+make_cases_output.js_on_change("text", JS_scroll_bottom)
+
+#make_cases_output.js_on_event(X, )
+"""
+
+
+def make_cases():
+
+ if len(make_cases_select.value) != 1:
+ make_cases_output.text = \
+ "You must select one single settings file!"
+ return
+
+ print(make_cases_select.value)
+ file_idx = int(make_cases_select.value[0])
+ print(file_idx)
+
+ make_cases_output.text = \
+ f"Making cases for {settings_file_names[file_idx]}" \
+ + "... This step will take approx. 10 minutes per case. Check " \
+ + "your local terminal for progress information."
+
+ make_cases_settings_file = settings_file_paths[file_idx]
+
+ curdoc().hold("combine")
+
+ proc = subprocess.run(
+ f"python3 {script_path}/make_cases.py -f {make_cases_settings_file}",
+ shell=True, check=True, stdout=PIPE, stderr=PIPE)
+
+ curdoc().unhold()
+
+ make_cases_output.text = \
+ f"Finished for {settings_file_names[file_idx]}! " \
+ + "You can find logging information below.
+The NorESM landsites platform uses settings files to build and run customized
+single-point NorESM model experiments for selected locations. In this section,
+you can specify a previously created settings file to
+
+make cases, i.e., automatically download the input data and create and
+adapt directories that contain the files required by the underlying model. In
+addition, you can subsequently
+
+run cases, i.e., start the actual model simulation.
+
+Attention! There are two important constraints
+you need to be aware of:
+
+
You can only run cases that have previously been built. If you try to
+ run an experiment from a settings file that has not been used as input
+ for the 'build' step before, an error will occur.
+
If you make any changes to a previously built case (e.g., change
+ parameter values in a settings file after creating the case
+ directory), you need to rebuild the case, i.e., reexecute the 'build' step
+ to apply the changes.
+
+
diff --git a/bokeh_app/make_settings/static/html/header.html b/bokeh_app/make_settings/static/html/settings_header.html
similarity index 100%
rename from bokeh_app/make_settings/static/html/header.html
rename to bokeh_app/make_settings/static/html/settings_header.html
From a639ec24489db9a0aed36703a2b0c86808f1fb98 Mon Sep 17 00:00:00 2001
From: lasseke <57358428+lasseke@users.noreply.github.com>
Date: Mon, 28 Feb 2022 23:48:13 +0100
Subject: [PATCH 4/6] Finalized working draft of making/running cases in bokeh
app
---
bokeh_app/make_settings/main.py | 174 ++++++++++++++++++++++++--------
1 file changed, 133 insertions(+), 41 deletions(-)
diff --git a/bokeh_app/make_settings/main.py b/bokeh_app/make_settings/main.py
index d05086ea..8de0f0d6 100644
--- a/bokeh_app/make_settings/main.py
+++ b/bokeh_app/make_settings/main.py
@@ -9,7 +9,7 @@
from datetime import datetime
import geopandas as gpd
import subprocess
-from subprocess import PIPE
+from subprocess import PIPE, STDOUT
import glob
# Local imports
@@ -20,8 +20,9 @@
from bokeh.io import curdoc
from bokeh.layouts import column, row, grid
from bokeh.models import Div, CheckboxGroup, DatePicker, RadioButtonGroup
-from bokeh.models import MultiChoice, Panel, Tabs, FileInput, MultiSelect
+from bokeh.models import MultiChoice, Panel, Tabs, MultiSelect, CustomJS
from bokeh.models.widgets import TextInput, Button, Paragraph
+from bokeh.events import ButtonClick
###############################################################################
"""
@@ -344,7 +345,7 @@ def update():
###############################################################################
"""
-Build cases
+Make cases
"""
###############################################################################
@@ -357,20 +358,21 @@ def update():
def get_settings_files():
"""Get all settings files in 'landsites_tools/custom_settings' in a list"""
- file_path_str_list = glob.glob(str(settings_path) + "/*.txt")
+ global settings_file_paths
+ global settings_file_names
- file_paths_list = [Path(fname) for fname in file_path_str_list]
- file_names_list = [p.name for p in file_paths_list]
+ file_path_str_list = glob.glob(str(settings_path) + "/*.txt")
- return(file_paths_list, file_names_list)
+ settings_file_paths = [Path(fname) for fname in file_path_str_list]
+ settings_file_names = [p.name for p in settings_file_paths]
def retrieve_select_options(settings_file_names):
- make_cases_select_options = \
+ settings_files_select_options = \
[(str(idx), f_name) for idx, f_name in enumerate(settings_file_names)]
- return(make_cases_select_options)
+ return(settings_files_select_options)
make_cases_div = Div(
@@ -383,11 +385,11 @@ def retrieve_select_options(settings_file_names):
sizing_mode='stretch_width')
# List for picking a file
-settings_file_paths, settings_file_names = get_settings_files()
-make_cases_select_options = retrieve_select_options(settings_file_names)
+get_settings_files()
+settings_files_select_options = retrieve_select_options(settings_file_names)
make_cases_select = MultiSelect(value=[],
- options=make_cases_select_options)
+ options=settings_files_select_options)
# Make cases button
make_cases_button = Button(label="Make case(s)", button_type='primary',
@@ -395,30 +397,25 @@ def retrieve_select_options(settings_file_names):
refresh_button = Button(label="Refresh files", button_type='default',
disabled=False)
-make_cases_output = Div(text="Click the button above to make new cases.",
+make_cases_output = Div(text="Click the button above to create and build "
+ + "the cases specified in the chosen settings file.",
css_classes=["item-description"],
style={'overflow': 'auto',
'width': '100%',
- 'height': '250px'}
+ 'height': '200px'}
)
"""
-JS_scroll_bottom = CustomJS(args={'bokeh_div': make_cases_output}, code='''
-function updateScroll(){
-
- var element = document.getElementsByClassName("build-log")[0].children[0];
-
- element.scrollTop = element.scrollHeight;
- console.log(element.scrollTop)
-}
-updateScroll();
-
+JS_executing_feedback = CustomJS(args={'bokeh_div': make_cases_output,
+ 'make_button': make_cases_button},
+ code='''
+bokeh_div.text="Making cases... This step will take approx. 10 minutes " +
+"per case. Logging information will be printed here when the process " +
+"is finished.";
+make_button.disabled=true;
''')
-
-make_cases_output.js_on_change("text", JS_scroll_bottom)
-
-#make_cases_output.js_on_event(X, )
"""
+#make_cases_button.js_on_event(ButtonClick, JS_executing_feedback)
def make_cases():
@@ -428,40 +425,50 @@ def make_cases():
"You must select one single settings file!"
return
- print(make_cases_select.value)
file_idx = int(make_cases_select.value[0])
- print(file_idx)
make_cases_output.text = \
f"Making cases for {settings_file_names[file_idx]}" \
+ "... This step will take approx. 10 minutes per case. Check " \
+ "your local terminal for progress information."
- make_cases_settings_file = settings_file_paths[file_idx]
+ make_cases_button.disabled = True
+
+
+def make_cases_subprocess():
- curdoc().hold("combine")
+ file_idx = int(make_cases_select.value[0])
+
+ make_cases_settings_file = settings_file_paths[file_idx]
proc = subprocess.run(
f"python3 {script_path}/make_cases.py -f {make_cases_settings_file}",
- shell=True, check=True, stdout=PIPE, stderr=PIPE)
-
- curdoc().unhold()
+ shell=True, check=True) # , stdout=PIPE, stderr=STDOUT)
+ # new_line = '\n'
make_cases_output.text = \
f"Finished for {settings_file_names[file_idx]}! " \
- + "You can find logging information below.