Skip to content

Commit

Permalink
adding simulation run table
Browse files Browse the repository at this point in the history
  • Loading branch information
JBris committed Sep 9, 2024
1 parent f853a14 commit 811d2c6
Show file tree
Hide file tree
Showing 3 changed files with 225 additions and 31 deletions.
42 changes: 42 additions & 0 deletions app/conf/form/common.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,7 @@ components:
kwargs:
type: number
step: 1
value: 0
persistence: true
- id: max-val-attempts-input
param: max_val_attempts
Expand Down Expand Up @@ -325,3 +326,44 @@ components:
handler: file_upload
kwargs:
children: Load parameters
- id: save-runs-button
label: Download
help: Download the simulation run table as a csv file
class_name: dash_bootstrap_components.Button
kwargs:
children: Save simulations
color: primary
className: me-1
- id: upload-runs-file-button
label: Upload
help: Upload the simulation run table from a csv file
class_name: dash.dcc.Upload
handler: file_upload
kwargs:
children: Load simulations
- id: clear-runs-button
label: Clear
help: Clear the simulation run table
class_name: dash_bootstrap_components.Button
kwargs:
children: Clear simulations
color: primary
className: me-1
results:
children:
- id: simulation-runs-table
label: Simulation Runs
help: A table of simulation runs.
class_name: dash.dash_table.DataTable
handler: data_table
kwargs:
filter_action: native
sort_action: native
sort_mode: multi
column_selectable: multi
row_selectable: multi
page_action: native
page_current: 0
page_size: 10
editable: true
persistence: true
162 changes: 134 additions & 28 deletions app/pages/generate_root_system.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
from dash import ALL, Input, Output, State, callback, dcc, get_app, html, register_page
from prefect.deployments import run_deployment

from deeprootgen.data_model import RootSimulationModel
from deeprootgen.form import (
build_collapsible,
build_common_components,
Expand Down Expand Up @@ -129,35 +128,119 @@ def save_param(n_clicks: int, param_inputs: list) -> None:
return dcc.send_file(outfile)


@callback(
Output(f"{PAGE_ID}-download-results", "data"),
[Input({"index": f"{PAGE_ID}-save-runs-button", "type": ALL}, "n_clicks")],
State({"index": f"{PAGE_ID}-simulation-runs-table", "type": ALL}, "data"),
prevent_initial_call=True,
)
def save_runs(n_clicks: int, simulation_runs: list) -> None:
"""Save simulation runs to file.
Args:
n_clicks (int):
The number of times that the button has been clicked.
simulation_runs (list):
A list of simulation run data.
"""
simulation_runs = simulation_runs[0]
df = pd.DataFrame(simulation_runs)
date_now = datetime.today().strftime("%Y-%m-%d-%H-%M")
outfile = osp.join("outputs", f"{date_now}-{PAGE_ID}-runs.csv")
df.to_csv(outfile, index=False)
return dcc.send_file(outfile)


@callback(
Output(
{"index": f"{PAGE_ID}-simulation-runs-table", "type": ALL},
"data",
allow_duplicate=True,
),
Output(f"{PAGE_ID}-load-toast", "is_open", allow_duplicate=True),
Output(f"{PAGE_ID}-load-toast", "children", allow_duplicate=True),
Input({"index": f"{PAGE_ID}-upload-runs-file-button", "type": ALL}, "contents"),
State({"index": f"{PAGE_ID}-upload-runs-file-button", "type": ALL}, "filename"),
prevent_initial_call=True,
)
def load_runs(list_of_contents: list, list_of_names: list) -> tuple:
_, content_string = list_of_contents[0].split(",")
decoded = base64.b64decode(content_string).decode("utf-8")
split_lines = decoded.split("\n")
split_lines.pop(0)

workflow_urls = []
for workflow_url in split_lines:
if workflow_url != "":
workflow_urls.append({"workflow_url": workflow_url})

toast_message = f"Loading run history from: {list_of_names[0]}"
return [workflow_urls], True, toast_message


@callback(
Output(
{"index": f"{PAGE_ID}-simulation-runs-table", "type": ALL},
"data",
allow_duplicate=True,
),
[Input({"index": f"{PAGE_ID}-clear-runs-button", "type": ALL}, "n_clicks")],
prevent_initial_call=True,
)
def clear_runs(n_clicks: int) -> list:
"""Clear the simulation runs table.
Args:
n_clicks (int):
The number of times that the button has been clicked.
Returns:
list:
An empty list.
"""
return [[]]


@callback(
Output({"type": f"{PAGE_ID}-parameters", "index": ALL}, "value"),
Output(f"{PAGE_ID}-load-toast", "is_open"),
Output(f"{PAGE_ID}-load-toast", "children"),
Output(f"{PAGE_ID}-load-toast", "is_open", allow_duplicate=True),
Output(f"{PAGE_ID}-load-toast", "children", allow_duplicate=True),
Input({"index": f"{PAGE_ID}-upload-param-file-button", "type": ALL}, "contents"),
State({"index": f"{PAGE_ID}-upload-param-file-button", "type": ALL}, "filename"),
prevent_initial_call=True,
)
def update_output(list_of_contents: list, list_of_names: list) -> tuple:
def load_params(list_of_contents: list, list_of_names: list) -> tuple:
_, content_string = list_of_contents[0].split(",")
decoded = base64.b64decode(content_string)
input_dict = yaml.safe_load(decoded.decode("utf-8"))
inputs = list(input_dict.values())

app = get_app()
form_model = app.settings["form"]
inputs = []
for input in form_model.components["parameters"]["children"]:
k = input["param"]
inputs.append(input_dict[k])

toast_message = f"Loading parameter specification from: {list_of_names[0]}"
return inputs, True, toast_message


@callback(
# Output("generate-root-system-plot", "figure"),
# Output(f"{PAGE_ID}-download-content", "data"),
Output(
{"index": f"{PAGE_ID}-simulation-runs-table", "type": ALL},
"data",
allow_duplicate=True,
),
Output(f"{PAGE_ID}-results-toast", "is_open"),
Output(f"{PAGE_ID}-results-toast", "children"),
Input({"index": f"{PAGE_ID}-run-sim-button", "type": ALL}, "n_clicks"),
State({"type": f"{PAGE_ID}-parameters", "index": ALL}, "value"),
State({"index": f"{PAGE_ID}-enable-soil-input", "type": ALL}, "on"),
State({"index": f"{PAGE_ID}-simulation-runs-table", "type": ALL}, "data"),
prevent_initial_call=True,
)
def run_root_model(
n_clicks: list,
form_values: list,
enable_soils: list,
n_clicks: list, form_values: list, enable_soils: list, simulation_runs: list
) -> dcc.Graph:
"""Run and plot the root model.
Expand All @@ -168,6 +251,8 @@ def run_root_model(
The form input data.
enable_soils (list):
Enable visualisation of soil data.
simulation_runs (list):
A list of simulation run data.
Returns:
dcc.Graph: The visualised root model.
Expand All @@ -187,26 +272,38 @@ def run_root_model(
form_inputs["enable_soil"] = enable_soil == True # noqa: E712

simulation_uuid = get_simulation_uuid()
run_deployment(
flow_data = run_deployment(
"simulation/run_simulation_flow",
parameters=dict(input_params=form_inputs, simulation_uuid=simulation_uuid),
parameters=dict(input_parameters=form_inputs, simulation_uuid=simulation_uuid),
flow_run_name=f"run-{simulation_uuid}",
timeout=0,
)

# download_data = download_data[0]
# if download_data:
# from datetime import datetime
simulation_uuid
flow_run_id = str(flow_data.id)
flow_name = flow_data.name
simulation_tag = form_inputs["simulation_tag"]

simulation_runs = simulation_runs[0]

# now = datetime.today().strftime("%Y-%m-%d-%H-%M")
# outfile = osp.join("outputs", f"{now}-nodes.csv")
# df = pd.DataFrame(results.nodes)
# df.to_csv(outfile, index=False)
# download_file = dcc.send_file(outfile)
# else:
# download_file = None
import os

# return results.figure, download_file
app_prefect_host = os.environ.get("APP_PREFECT_USER_HOST")
if app_prefect_host is None:
app_prefect_host = "http://localhost:4200"
prefect_flow_url = f"{app_prefect_host}/flow-runs/flow-run/{flow_run_id}"

simulation_runs.append(
{
"workflow_url": f"<a href='{prefect_flow_url}' target='_blank'>{prefect_flow_url}</a>"
}
)

toast_message = f"""
Running simulation workflow: {flow_name}
Simulation tag: {simulation_tag}
"""
return [simulation_runs], True, toast_message


######################################
Expand Down Expand Up @@ -246,12 +343,21 @@ def layout() -> html.Div:
)

input_components = dbc.Col([parameter_components, data_io_components])
simulation_run_df = pd.DataFrame([], columns=["workflow_url"])

simulation_results_data = {"simulation-runs-table": simulation_run_df}

k = "results"
simulation_results_components = build_common_components(
form_model.components[k]["children"],
PAGE_ID,
k,
simulation_results_data,
resize_component=False,
)

output_components = dbc.Row(
[
dbc.Col(
dcc.Graph(id="generate-root-system-plot"),
)
]
dbc.Col(simulation_results_components, style={"margin-left": "0.5em"})
)

page_description = """
Expand Down
52 changes: 49 additions & 3 deletions deeprootgen/form/components.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,11 @@


def build_common_components(
component_specs: list, page_id: str, component_type: str
component_specs: list,
page_id: str,
component_type: str,
component_data: dict | None = None,
resize_component: bool = True,
) -> list:
"""Build form components that are common across pages.
Expand All @@ -24,6 +28,10 @@ def build_common_components(
The page ID.
component_type (str):
The type of component. Used for grouping common components.
component_data: (dict, optional):
A dictionary of data to render within form component.
resize_component: (bool, optional):
Whether to resize the last component in the row.
Returns:
list: The common form components.
Expand All @@ -41,7 +49,11 @@ def build_common_components(
id=f"{page_id}-{component_spec.id}-label",
),
id=f"{page_id}-{component_spec.id}-tooltip-target",
style={"cursor": "pointer"},
style={
"cursor": "pointer",
"padding-left": "0.5em",
"padding-top": "0.5em",
},
),
style={"margin": "0"},
)
Expand All @@ -62,6 +74,7 @@ def build_common_components(
},
**kwargs,
) # type: ignore
component_instance.style = {"padding-left": "0.5em"}

if hasattr(component_spec, "handler"):
if component_spec.handler == "file_upload":
Expand All @@ -74,6 +87,24 @@ def build_common_components(
"textAlign": "center",
}

if component_spec.handler == "data_table":
if (
component_data is not None
and component_data.get(component_spec.id) is not None
):
table_df = component_data[component_spec.id]
component_instance.data = table_df.to_dict("records")
component_instance.columns = [
{
"name": i,
"id": i,
"selectable": True,
"presentation": "markdown",
}
for i in table_df.columns
]
component_instance.markdown_options = {"html": True}

row.append(dbc.Col([component_label, component_tooltip, component_instance]))

col_num += 1
Expand All @@ -82,8 +113,12 @@ def build_common_components(
components.append(dbc.Row(row))
row = []

if resize_component:
width = "52.5%"
else:
width = "100%"
if len(row) == 1:
components.append(dbc.Row(dbc.Col(row), style={"width": "52.5%"}))
components.append(dbc.Row(dbc.Col(row), style={"width": width}))

return components

Expand Down Expand Up @@ -208,6 +243,7 @@ def build_common_layout(
input_components,
dcc.Download(id=f"{page_id}-download-params"),
dcc.Download(id=f"{page_id}-download-content"),
dcc.Download(id=f"{page_id}-download-results"),
dbc.Toast(
"",
id=f"{page_id}-load-toast",
Expand All @@ -218,6 +254,16 @@ def build_common_layout(
duration=5000,
style={"position": "fixed", "bottom": "0", "left": "0", "zIndex": "9999"},
),
dbc.Toast(
"",
id=f"{page_id}-results-toast",
header="Simulation Results",
is_open=False,
dismissable=True,
icon="primary",
duration=5000,
style={"position": "fixed", "bottom": "0", "left": "0", "zIndex": "9999"},
),
external_links_collapsible,
]
layout = html.Div(
Expand Down

0 comments on commit 811d2c6

Please sign in to comment.