Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Touchups #54

Merged
merged 8 commits into from
Sep 7, 2023
Merged
Show file tree
Hide file tree
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
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
Loading