Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
189 changes: 140 additions & 49 deletions crystal_toolkit/components/pourbaix.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
MAX_PH = 16
MIN_V = -4
MAX_V = 4
PANEL_LINE_HEIGHT = "2em"


class PourbaixDiagramComponent(MPComponent):
Expand Down Expand Up @@ -127,6 +128,16 @@ class PourbaixDiagramComponent(MPComponent):
}
)

@staticmethod
def create_centered_object(content):
return html.Div(
content,
style={
"display": "flex",
"justifyContent": "center",
},
)

@staticmethod
def get_figure(
pourbaix_diagram: PourbaixDiagram, heatmap_entry=None, show_water_lines=True
Expand Down Expand Up @@ -440,11 +451,10 @@ def _sub_layouts(self) -> dict[str, Component]:
default=self.default_state["filter_solids"],
label="Filter Solids",
help_str="Whether to filter solid phases by stability on the compositional phase diagram. "
"The practical consequence of this is that highly oxidized or reduced phases that "
"might show up in experiments due to kinetic limitations on oxygen/hydrogen evolution "
"won't appear in the diagram, but they are not actually “stable” (and are frequently "
"overstabilized from DFT errors). Hence, including only the stable solid phases generally "
"leads to the most accurate Pourbaix diagrams.",
"The practical consequence of this is that we only include materials that are predicted to "
"be thermodynamically stable at RT within the limitations of DFT. Notably, there may be "
"disagreements with experiments e.g., highly oxidized or reduced phases, which are kinetically "
"stabilized through surface passivation.",
),
html.Div(
[
Expand All @@ -454,23 +464,56 @@ def _sub_layouts(self) -> dict[str, Component]:
id=self.id("invalid-comp-alarm"),
message="Illegal composition entry!",
),
html.H5(
"Composition",
id=self.id("composition-title"),
style={"fontWeight": "bold"},
html.Div(
[
html.H5(
"Composition Control",
style={
"fontWeight": "bold",
"textAlign": "center",
"flex": "0 0 100%",
},
),
html.H5(
"Composition of",
id=self.id("composition-title"),
# style={"fontWeight": "bold"},
),
],
style={
"line-height": PANEL_LINE_HEIGHT,
"display": "flex",
"flexWrap": "wrap",
"justifyContent": "center",
},
),
dcc.Input(
id=self.id("comp-text"),
type="text",
# placeholder="composition e.g. 1:1:1",
PourbaixDiagramComponent.create_centered_object(
dcc.Input(
id=self.id("comp-text"),
className="input",
type="text",
style={
"textAlign": "center",
"width": "10rem",
"marginRight": "0.2rem",
"marginBottom": "0.2rem",
"height": "36px",
"fontSize": "14px",
},
),
),
html.Button(
"Update",
id=self.id("comp-btn"),
ctl.Block(
PourbaixDiagramComponent.create_centered_object(
html.Div(
id=self.id("display-composition"),
)
)
),
html.Hr(
style={
"backgroundColor": "#C5C5C6",
}
),
ctl.Block(html.Div(id=self.id("display-composition"))),
html.Br(),
html.Br(),
dcc.Store(id=self.id("elements-store")),
],
id=self.id("comp-panel"),
Expand All @@ -486,9 +529,22 @@ def _sub_layouts(self) -> dict[str, Component]:
id=self.id("conc-panel"),
style={"display": "none"},
),
html.Div(id=self.id("element_specific_controls")),
]
html.Div(
id=self.id("element_specific_controls"),
),
PourbaixDiagramComponent.create_centered_object(
html.Button(
"Update",
id=self.id("comp-conc-btn"),
style={"display": "none"},
),
),
],
style={
"backgroundColor": "#F1F1F5",
},
),
html.Br(),
self.get_bool_input(
"show_heatmap", # kwarg_label
# state=self.default_state,
Expand Down Expand Up @@ -629,6 +685,8 @@ def update_heatmap_choices(entries, mat_detials, filter_solids):
Output(self.id("elements-store"), "data"),
Output(self.id("comp-text"), "value"),
Output(self.id("composition-title"), "children"),
Output(self.id("comp-conc-btn"), "children"),
Output(self.id("comp-conc-btn"), "style"),
Input(self.id(), "data"),
prevent_initial_call=True,
)
Expand All @@ -651,57 +709,83 @@ def update_element_specific_sliders(
# exclude O and H
elements = elements - ELEMENTS_HO

comp_inputs = []
conc_inputs = []

for element in sorted(elements):
conc_input = html.Div(
[
self.get_numerical_input(
f"conc-{element}",
default=1e-6,
min=MIN_CONCENTRATION,
max=MAX_CONCENTRATION,
label=f"Concentration of {element} ion",
style={"width": "10rem", "fontSize": "14px"},
)
]
conc_input = PourbaixDiagramComponent.create_centered_object(
self.get_numerical_input(
f"conc-{element}",
default=1e-6,
min=MIN_CONCENTRATION,
max=MAX_CONCENTRATION,
label=f"concentration of {element} ion",
style={
"width": "10rem",
"fontSize": "14px",
},
)
)

conc_inputs.append(conc_input)

comp_conc_controls = []
comp_conc_controls += comp_inputs

ion_label = (
"Set Ion Concentrations (M)"
"Ion Concentrations Control"
if len(elements) > 1
else "Set Ion Concentration"
else "Ion Concentration Control"
)

comp_conc_controls.append(
html.H5(
ion_label,
style={"fontWeight": "bold", "textAlign": "center"},
),
)
comp_conc_controls.append(
PourbaixDiagramComponent.create_centered_object(
html.H6(
f"💡 Set the range between {MIN_CONCENTRATION} and {MAX_CONCENTRATION} (M)"
)
)
)
comp_conc_controls.append(ctl.Label(ion_label))

comp_conc_controls += conc_inputs

#
# comp_panel_style
comp_panel_style = {"display": "none"}
if len(elements) > 1:
comp_panel_style = {"display": "block"}
comp_panel_style = {
"display": "block",
}

#
# elements store
elements = [element.symbol for element in elements]

#
# default_comp
default_comp = ":".join(["1" for _ in elements])

#
title = "Composition of " + ":".join(elements)
# composition title
title = "💡 Composition of " + ":".join(elements)

# update_string
update_string = "Concentration update"
if len(elements) > 1:
update_string = "Composition & concentration update"

return (
html.Div(comp_conc_controls),
html.Div(
comp_conc_controls,
style={
"line-height": PANEL_LINE_HEIGHT,
},
),
comp_panel_style,
elements,
default_comp,
title,
update_string,
{"display": "block", "height": "36px"},
)

@cache.memoize(timeout=5 * 60)
Expand All @@ -715,26 +799,32 @@ def get_pourbaix_diagram(pourbaix_entries, **kwargs):
Output(self.id("display-composition"), "children"),
Input(self.id(), "data"),
Input(self.id("display-composition"), "children"),
Input(self.get_all_kwargs_id(), "value"),
Input(self.id("comp-btn"), "n_clicks"),
State(self.get_all_kwargs_id(), "value"),
Input(self.id("comp-conc-btn"), "n_clicks"),
State(self.id("elements-store"), "data"),
State(self.id("comp-text"), "value"),
Input(self.id("element_specific_controls"), "children"),
Input(self.get_kwarg_id("filter_solids"), "value"),
Input(self.get_kwarg_id("show_heatmap"), "value"),
Input(self.get_kwarg_id("heatmap_choice"), "value"),
prevent_initial_call=True,
)
def make_figure(
pourbaix_entries,
dependency,
display_composition,
kwargs,
n_clicks,
elements,
comp_text,
dependency2,
element_specific_controls,
filter_solids,
show_heatmap,
heatmap_choice,
) -> go.Figure:
if pourbaix_entries is None:
raise PreventUpdate

# check if composition input
# Only update
if n_clicks:
raw_comp_list = comp_text.split(":")
else:
Expand Down Expand Up @@ -808,6 +898,7 @@ def make_figure(
pourbaix_diagram,
heatmap_entry=heatmap_entry,
)

return (
self.get_figure_div(figure=figure),
False,
Expand Down
Loading