Skip to content

Commit

Permalink
(PR #54) Various touchups
Browse files Browse the repository at this point in the history
* Improve samples preview table

* Add local samples reset feature

* Improve saving/loading protocols

* Improve protocol querying

* Update widget names

* Add widget defaults register

* Add local settings reset feature

* Add settings initialization method
  • Loading branch information
edan-bainglass authored Sep 7, 2023
1 parent 1464144 commit 06616fe
Show file tree
Hide file tree
Showing 4 changed files with 244 additions and 148 deletions.
193 changes: 109 additions & 84 deletions aurora/common/models/battery_experiment.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from IPython.display import display

AVAILABLE_SAMPLES_FILE = 'available_samples.json'
AVAILABLE_PROTOCOLS_FILE = 'available_protocols.json'

STD_RECIPIES = {
'Load fresh battery into cycler':
Expand All @@ -18,37 +19,6 @@
'Synthesize - Recipe 2': ['step 1', 'step 2', 'step 3'],
}

STD_PROTOCOLS = {
"Formation cycles": {
"Procedure": [
"6h rest", "CCCV Charge, C/10 (CV condition: i<C/50)",
"CC Discharge, D/10", "Repeat 3 times"
],
"Cutoff conditions":
"2.5 to 4.2 V, upper voltage to be changed depending on the chemistry",
},
"Power test Charge focus": {
"Procedure": [
"6h rest", "CC Charge (CV condition: i<C/50)", "(C/20 + D/20) x 5",
"(C/10 + D/20) x 5", "(C/5 + D/20) x 5", "(C/2 + D/20) x 5",
"(1C + D/20) x 5", "(2C + D/20) x 5", "(5C + D/20) x 5",
"(C/20 + D/20) x 5"
],
"Cutoff conditions":
"2.5 to 4.2 V, upper voltage to be changed depending on the chemistry",
},
"Power test Discharge focus": {
"Procedure": [
"6h rest", "CCCV Charge (CV condition: i<C/50)",
"(C/20 + D/20) x 5", "(C/20 + D/10) x 5", "(C/20 + D/50) x 5",
"(C/20 + D/2) x 5", "(C/20 + 1D) x 5", "(C/20 + 2D) x 5",
"(C/20 + 5D) x 5", "(C/20 + D/20) x 5"
],
"Cutoff conditions":
"2.5 to 4.2 V, upper voltage to be changed depending on the chemistry",
},
}


class BatteryExperimentModel():
"""The model that controls the submission of a process for a set of batteries."""
Expand All @@ -59,20 +29,25 @@ def __init__(self):
self.list_of_observers = []
self.list_of_observations = []

self.available_samples = None
self.available_samples = pd.DataFrame()
self.available_specs = None
self.available_recipies = None
self.available_protocols = None
self.available_protocols = []

self.update_available_samples()
self.update_available_specs()
self.update_available_recipies()
self.update_available_protocols()

self.selected_samples = []
self.selected_samples = pd.DataFrame()
self.selected_protocol = ElectroChemSequence(method=[])
self.add_protocol_step() # initialize sequence with first default step

def reset_inputs(self):
"""Resets all inputs."""
# Not implemented yet...
return None

# ----------------------------------------------------------------------#
# METHODS RELATED TO OBSERVABLES
# ----------------------------------------------------------------------#
Expand All @@ -93,10 +68,9 @@ def update_observers(self, observators_chain=None):
# ----------------------------------------------------------------------#
# METHODS RELATED TO SAMPLES
# ----------------------------------------------------------------------#
def reset_inputs(self):
def reset_selected_samples(self):
"""Resets all inputs."""
# Not implemented yet...
return None
self.selected_samples = self.available_samples[:0]

def add_selected_samples(self, sample_ids: List[int]) -> None:
"""Add selected samples to list.
Expand Down Expand Up @@ -230,14 +204,6 @@ def update_available_recipies(self, observators_chain=None):
observators_chain += ' -> update_available_recipies'
self.update_observers(observators_chain)

def update_available_protocols(self, observators_chain=None):
global STD_PROTOCOLS
self.available_protocols = STD_PROTOCOLS.copy()
if observators_chain is None:
observators_chain = ''
observators_chain += ' -> update_available_protocols'
self.update_observers(observators_chain)

def query_available_specs(
self,
field: Optional[str] = None,
Expand Down Expand Up @@ -300,8 +266,26 @@ def query_available_recipies(self):
"""A mock function that returns the available synthesis recipies."""
return self.available_recipies

def query_available_protocols(self):
"""A mock function that returns the available synthesis recipies."""
def query_available_protocols(
self,
ids: Optional[List[int]] = None,
) -> List[ElectroChemSequence]:
"""Return protocols from local cache.
Optionally filtered by protocol `ids`.
Parameters
----------
`ids` : `Optional[List[int]]`
A list of protocol ids, `None` by default.
Returns
-------
`List[ElectroChemSequence]`
A list of protocols.
"""
if ids is not None:
return [self.available_protocols[i] for i in ids]
return self.available_protocols

def write_pd_query_from_dict(self, query_dict: dict) -> Optional[str]:
Expand Down Expand Up @@ -343,34 +327,43 @@ def display_query_results(self, query: dict) -> None:
col = df.pop("battery_id")
df.insert(0, col.name, col)

display(
df.rename(
columns={
"battery_id": 'id',
"form_factor": 'form factor',
"composition.description": 'composition',
"capacity.nominal": 'C nominal',
"capacity.actual": 'C actual',
"capacity.units": 'C units',
"metadata.name": 'name',
"metadata.creation_datetime": 'creation date',
"metadata.creation_process": 'creation process',
}).style.set_table_styles([
dict(
selector='th',
props=[
('text-align', 'center'),
("width", "100vw"),
],
),
dict(
selector='td',
props=[
('text-align', 'center'),
("width", "100vw"),
],
)
]).hide_index())
df = df.rename(
columns={
"battery_id": 'id',
"form_factor": 'form factor',
"composition.description": 'composition',
"capacity.nominal": 'C nominal',
"capacity.actual": 'C actual',
"capacity.units": 'C units',
"metadata.name": 'name',
"metadata.creation_datetime": 'creation date',
"metadata.creation_process": 'creation process',
})

styler = df.style

styler = styler.set_table_attributes('style="margin-top: 0"')

styler = styler.set_table_styles([
dict(
selector='th',
props=[
('text-align', 'center'),
("width", "100vw"),
],
),
dict(
selector='td',
props=[
('text-align', 'center'),
("width", "100vw"),
],
)
])

styler = styler.hide(axis="index")

display(styler)

# ----------------------------------------------------------------------#
# METHODS RELATED TO PROTOCOLS
Expand Down Expand Up @@ -415,18 +408,50 @@ def load_protocol(self, filepath):
self.selected_protocol = ElectroChemSequence(**json_data)
self.update_observers()

def save_protocol(self, filepath):
"""Saves the protocol from a file."""
if filepath is None:
return
def save_protocol(self):
"""Save the protocol to a file."""

self.available_protocols.append(self.selected_protocol)

protocols = [p.dict() for p in self.available_protocols]

try:
json_data = json.dumps(self.selected_protocol.dict(), indent=2)
except Exception as err:
json_data = str(err)

with open(filepath, 'w') as fileobj:
fileobj.write(str(json_data))
try:
json_data = json.dumps(protocols, indent=2)
except Exception as err:
json_data = str(err)

with open(AVAILABLE_PROTOCOLS_FILE, 'w+') as fileobj:
fileobj.write(json_data)

except OSError as err:
print(f"Failed to save protocols => '{str(err)}'")

def update_available_protocols(
self,
source_file=AVAILABLE_PROTOCOLS_FILE,
) -> None:
"""Update available protocols cache from local file.
Considered empty if failure to find/load file.
Parameters
----------
`source_file` : `str`
The path to the local protocols file,
`AVAILABLE_PROTOCOLS_FILE` by default.
"""

try:
with open(source_file) as f:
data = json.load(f)
except OSError:
data = {}

self.available_protocols = [
ElectroChemSequence(**protocol) for protocol in data
]


def _process_dict(query_dict: dict) -> dict:
Expand Down
58 changes: 44 additions & 14 deletions aurora/experiment/samples/from_id.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,10 +61,9 @@ class SampleFromId(ipw.VBox):
}

PREVIEW_LAYOUT = {
"margin": "0 auto 10px auto",
"height": "150px",
"max_height": "150px",
"overflow": "auto",
"margin": "0 2px 20px",
"max_height": "300px",
"overflow_y": "scroll",
"align_items": "center",
}

Expand Down Expand Up @@ -95,7 +94,7 @@ def __init__(

self.experiment_model = experiment_model

filters_container = self._build_filter_container()
self.filters_container = self._build_filter_container()

selection_container = self._build_selection_container()

Expand All @@ -109,12 +108,29 @@ def __init__(
layout=self.VALIDATE_BUTTON_LAYOUT,
)

self.reset_button = ipw.Button(
layout={},
style={},
description="Reset",
button_style='danger',
tooltip="Clear selection",
icon='times',
)

super().__init__(
layout={},
children=[
filters_container,
self.filters_container,
selection_container,
self.w_validate,
ipw.HBox(
layout={
"align_items": "center",
},
children=[
self.w_validate,
self.reset_button,
],
),
],
)

Expand Down Expand Up @@ -188,7 +204,7 @@ def selected_samples(self) -> List[BatterySample]:
# widgets
#########

def _build_filter_container(self) -> ipw.VBox:
def _build_filter_container(self) -> ipw.Accordion:
"""Build the filters section. Includes filter widgets and
controls.
Expand Down Expand Up @@ -527,19 +543,23 @@ def on_update_filters_button_click(self, _=None) -> None:

def on_reset_filters_button_click(self, _=None) -> None:
"""Reset current spec selections."""
self.w_specs_manufacturer.value = None
self.w_specs_composition.value = None
self.w_specs_form_factor.value = None
self.w_specs_capacity.value = None
# self.w_specs_metadata_creation_date.value = None
# self.w_specs_metadata_creation_process.value = None
self.reset_filters()

def on_spec_value_change(self, _=None):
"""Update spec and sample options."""
# TODO this does a lot per value change. Consider decoupling!
self.update_spec_options()
self.update_sample_options()

def reset_filters(self) -> None:
"""docstring"""
self.w_specs_manufacturer.value = None
self.w_specs_composition.value = None
self.w_specs_form_factor.value = None
self.w_specs_capacity.value = None
# self.w_specs_metadata_creation_date.value = None
# self.w_specs_metadata_creation_process.value = None

def update_sample_options(self) -> None:
"""Fetch and update current sample options."""
self.experiment_model.update_available_samples()
Expand Down Expand Up @@ -616,6 +636,14 @@ def update_selected_list_options(self) -> None:
# TODO discard redundant method!
self.w_selected_list.options = self._update_selected_list()

def reset(self, _=None) -> None:
"""docstring"""
self.filters_container.selected_index = None
self.w_sample_list.value = []
self.w_selected_list.options = []
self.reset_filters()
self.experiment_model.reset_selected_samples()

def _build_single_spec_options(
self,
spec_field: str,
Expand Down Expand Up @@ -749,6 +777,8 @@ def _set_event_listeners(self, validate_callback_f) -> None:
self.w_validate.on_click(
lambda arg: self.on_validate_button_click(validate_callback_f))

self.reset_button.on_click(self.reset)

def _set_specs_observers(self) -> None:
"""Set up event listeners for spec filters."""

Expand Down
Loading

0 comments on commit 06616fe

Please sign in to comment.