steer-core 0.1.16__py3-none-any.whl → 0.1.18__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -7,15 +7,15 @@ from .SliderComponents import SliderWithTextInput
7
7
  class MaterialSelector:
8
8
  """
9
9
  A custom Dash component that combines material selection controls in a horizontal layout.
10
-
10
+
11
11
  This component creates a horizontal row containing:
12
12
  - A dropdown menu for selecting material names
13
13
  - An input box for specifying weight fraction of the material
14
14
  - Two SliderWithTextInput components for specific cost and density
15
-
15
+
16
16
  The component is designed for material composition interfaces where users need to
17
17
  select materials and specify their properties and proportions.
18
-
18
+
19
19
  Attributes:
20
20
  id_base (dict): Base identifier dictionary used to construct unique IDs for child components
21
21
  material_options (List[Dict]): List of material options for the dropdown
@@ -30,7 +30,7 @@ class MaterialSelector:
30
30
  weight_fraction_id (dict): Computed ID for the weight fraction input
31
31
  cost_slider (SliderWithTextInput): Cost slider component
32
32
  density_slider (SliderWithTextInput): Density slider component
33
-
33
+
34
34
  Example:
35
35
  >>> material_selector = MaterialSelector(
36
36
  ... id_base={'type': 'material', 'index': 0},
@@ -74,12 +74,12 @@ class MaterialSelector:
74
74
  default_material: str = None,
75
75
  default_weight_percent: float = 0,
76
76
  slider_disable: bool = False,
77
- div_width: str = '100%',
77
+ div_width: str = "100%",
78
78
  hidden: bool = False,
79
79
  ):
80
80
  """
81
81
  Initialize the MaterialSelector component.
82
-
82
+
83
83
  Args:
84
84
  id_base (dict): Base dictionary for generating component IDs.
85
85
  material_options (List[Dict], optional): List of material options for dropdown.
@@ -94,7 +94,7 @@ class MaterialSelector:
94
94
  cost_config (dict, optional): Legacy cost slider configuration. Ignored if slider_config provided.
95
95
  density_config (dict, optional): Legacy density slider configuration. Ignored if slider_config provided.
96
96
  title (str, optional): Title displayed for the component. Defaults to "Material".
97
- default_material (str, optional): Default selected material. If None, no material
97
+ default_material (str, optional): Default selected material. If None, no material
98
98
  will be initially selected in the dropdown.
99
99
  default_weight_percent (float, optional): Default weight percentage (0.0-100.0). Defaults to 100.0.
100
100
  slider_disable (bool, optional): Whether to disable sliders. Defaults to False.
@@ -116,198 +116,208 @@ class MaterialSelector:
116
116
  self.hidden = hidden
117
117
 
118
118
  # Generate component IDs
119
- self.dropdown_id = self._make_id('dropdown')
120
- self.weight_fraction_id = self._make_id('weight_fraction')
119
+ self.dropdown_id = self._make_id("dropdown")
120
+ self.weight_fraction_id = self._make_id("weight_fraction")
121
121
 
122
122
  # Normalize configurations to handle both legacy and create_slider_config formats
123
123
  if slider_configs is not None and slider_configs: # Check if not empty
124
124
  # Use slider_configs arrays (index 0 = density, index 1 = cost)
125
- density_normalized = self._normalize_config_from_arrays(slider_configs, 0, 'Density')
126
- cost_normalized = self._normalize_config_from_arrays(slider_configs, 1, 'Cost')
125
+ density_normalized = self._normalize_config_from_arrays(
126
+ slider_configs, 0, "Density (g/cm³)"
127
+ )
128
+ cost_normalized = self._normalize_config_from_arrays(
129
+ slider_configs, 1, "Specific Cost ($/kg)"
130
+ )
127
131
  elif slider_configs is not None and not slider_configs: # Empty dictionary
128
132
  # Use sensible defaults for materials
129
133
  density_normalized = {
130
- 'min_val': 0.0,
131
- 'max_val': 0.1,
132
- 'step': 0.01,
133
- 'mark_interval': 0.1,
134
- 'title': 'Density',
135
- 'default_val': 0.05
134
+ "min_val": 0.0,
135
+ "max_val": 0.1,
136
+ "step": 0.01,
137
+ "mark_interval": 0.1,
138
+ "title": "Density (g/cm³)",
139
+ "default_val": 0.05,
136
140
  }
137
141
  cost_normalized = {
138
- 'min_val': 0.0,
139
- 'max_val': 0.1,
140
- 'step': 0.01,
141
- 'mark_interval': 0.1,
142
- 'title': 'Cost',
143
- 'default_val': 0.05
142
+ "min_val": 0.0,
143
+ "max_val": 0.1,
144
+ "step": 0.01,
145
+ "mark_interval": 0.1,
146
+ "title": "Specific Cost ($/kg)",
147
+ "default_val": 0.05,
144
148
  }
145
149
  else:
146
150
  # slider_configs is None - use sensible defaults with None values
147
151
  density_normalized = {
148
- 'min_val': 0.0,
149
- 'max_val': 0.1,
150
- 'step': 0.01,
151
- 'mark_interval': 0.1,
152
- 'title': 'Density',
153
- 'default_val': None # No initial value
152
+ "min_val": 0.0,
153
+ "max_val": 0.1,
154
+ "step": 0.01,
155
+ "mark_interval": 0.1,
156
+ "title": "Density (g/cm³)",
157
+ "default_val": None, # No initial value
154
158
  }
155
159
  cost_normalized = {
156
- 'min_val': 0.0,
157
- 'max_val': 0.1,
158
- 'step': 0.01,
159
- 'mark_interval': 0.1,
160
- 'title': 'Cost',
161
- 'default_val': None # No initial value
160
+ "min_val": 0.0,
161
+ "max_val": 0.1,
162
+ "step": 0.01,
163
+ "mark_interval": 0.1,
164
+ "title": "Specific Cost ($/kg)",
165
+ "default_val": None, # No initial value
162
166
  }
163
167
 
164
168
  # Create slider components
165
169
  cost_slider_kwargs = {
166
- 'id_base': id_base,
167
- 'min_val': cost_normalized['min_val'],
168
- 'max_val': cost_normalized['max_val'],
169
- 'step': cost_normalized['step'],
170
- 'mark_interval': cost_normalized['mark_interval'],
171
- 'property_name': "specific_cost",
172
- 'title': cost_normalized['title'],
173
- 'default_val': cost_normalized.get('default_val', cost_normalized['min_val']),
174
- 'with_slider_titles': True,
175
- 'slider_disable': slider_disable,
176
- 'div_width': '100%'
170
+ "id_base": id_base,
171
+ "min_val": cost_normalized["min_val"],
172
+ "max_val": cost_normalized["max_val"],
173
+ "step": cost_normalized["step"],
174
+ "mark_interval": cost_normalized["mark_interval"],
175
+ "property_name": "specific_cost",
176
+ "title": cost_normalized["title"],
177
+ "default_val": cost_normalized.get(
178
+ "default_val", cost_normalized["min_val"]
179
+ ),
180
+ "with_slider_titles": True,
181
+ "slider_disable": slider_disable,
182
+ "div_width": "100%",
177
183
  }
178
184
  # Add marks if available
179
- if 'marks' in cost_normalized:
180
- cost_slider_kwargs['marks'] = cost_normalized['marks']
181
-
185
+ if "marks" in cost_normalized:
186
+ cost_slider_kwargs["marks"] = cost_normalized["marks"]
187
+
182
188
  self.cost_slider = SliderWithTextInput(**cost_slider_kwargs)
183
189
 
184
190
  density_slider_kwargs = {
185
- 'id_base': id_base,
186
- 'min_val': density_normalized['min_val'],
187
- 'max_val': density_normalized['max_val'],
188
- 'step': density_normalized['step'],
189
- 'mark_interval': density_normalized['mark_interval'],
190
- 'property_name': "density",
191
- 'title': density_normalized['title'],
192
- 'default_val': density_normalized.get('default_val', density_normalized['min_val']),
193
- 'with_slider_titles': True,
194
- 'slider_disable': slider_disable,
195
- 'div_width': '100%'
191
+ "id_base": id_base,
192
+ "min_val": density_normalized["min_val"],
193
+ "max_val": density_normalized["max_val"],
194
+ "step": density_normalized["step"],
195
+ "mark_interval": density_normalized["mark_interval"],
196
+ "property_name": "density",
197
+ "title": density_normalized["title"],
198
+ "default_val": density_normalized.get(
199
+ "default_val", density_normalized["min_val"]
200
+ ),
201
+ "with_slider_titles": True,
202
+ "slider_disable": slider_disable,
203
+ "div_width": "100%",
196
204
  }
197
205
  # Add marks if available
198
- if 'marks' in density_normalized:
199
- density_slider_kwargs['marks'] = density_normalized['marks']
200
-
206
+ if "marks" in density_normalized:
207
+ density_slider_kwargs["marks"] = density_normalized["marks"]
208
+
201
209
  self.density_slider = SliderWithTextInput(**density_slider_kwargs)
202
210
 
203
- def _normalize_config_from_arrays(self, config: dict, index: int, default_title: str) -> dict:
211
+ def _normalize_config_from_arrays(
212
+ self, config: dict, index: int, default_title: str
213
+ ) -> dict:
204
214
  """
205
215
  Normalize slider configuration from create_slider_config output arrays.
206
-
216
+
207
217
  Args:
208
218
  config (dict): create_slider_config output with array values
209
219
  index (int): Index to extract from each array (0 = density, 1 = cost)
210
220
  default_title (str): Default title if not provided
211
-
221
+
212
222
  Returns:
213
223
  dict: Normalized configuration with min_val, max_val, step, mark_interval, title keys
214
224
  """
215
225
  normalized = {
216
- 'min_val': config['min_vals'][index],
217
- 'max_val': config['max_vals'][index],
218
- 'step': config['step_vals'][index],
219
- 'title': default_title # Use provided title
226
+ "min_val": config["min_vals"][index],
227
+ "max_val": config["max_vals"][index],
228
+ "step": config["step_vals"][index],
229
+ "title": default_title, # Use provided title
220
230
  }
221
-
231
+
222
232
  # Get pre-computed marks if available, otherwise calculate mark_interval
223
- if 'mark_vals' in config and len(config['mark_vals']) > index:
224
- marks = config['mark_vals'][index]
225
- normalized['marks'] = marks # Pass the actual marks dictionary
233
+ if "mark_vals" in config and len(config["mark_vals"]) > index:
234
+ marks = config["mark_vals"][index]
235
+ normalized["marks"] = marks # Pass the actual marks dictionary
226
236
  if len(marks) >= 2:
227
237
  # Calculate interval from first two marks
228
238
  mark_positions = sorted(marks.keys())
229
- normalized['mark_interval'] = mark_positions[1] - mark_positions[0]
239
+ normalized["mark_interval"] = mark_positions[1] - mark_positions[0]
230
240
  else:
231
241
  # Fallback: use step as mark interval
232
- normalized['mark_interval'] = normalized['step']
242
+ normalized["mark_interval"] = normalized["step"]
233
243
  else:
234
- normalized['mark_interval'] = normalized['step']
235
-
244
+ normalized["mark_interval"] = normalized["step"]
245
+
236
246
  # Add default value if available from grid values
237
- if 'grid_slider_vals' in config and len(config['grid_slider_vals']) > index:
238
- normalized['default_val'] = config['grid_slider_vals'][index]
247
+ if "grid_slider_vals" in config and len(config["grid_slider_vals"]) > index:
248
+ normalized["default_val"] = config["grid_slider_vals"][index]
239
249
  else:
240
- normalized['default_val'] = normalized['min_val']
241
-
250
+ normalized["default_val"] = normalized["min_val"]
251
+
242
252
  return normalized
243
253
 
244
254
  def _normalize_config(self, config: dict, config_type: str) -> dict:
245
255
  """
246
256
  Normalize slider configuration to handle both legacy and create_slider_config formats.
247
-
257
+
248
258
  Args:
249
259
  config (dict): Either legacy format or create_slider_config output
250
260
  config_type (str): Type identifier ('cost' or 'density') for default title
251
-
261
+
252
262
  Returns:
253
263
  dict: Normalized configuration with min_val, max_val, step, mark_interval, title keys
254
264
  """
255
265
  # Check if this is a create_slider_config output (has min_vals, max_vals, etc.)
256
- if 'min_vals' in config and 'max_vals' in config:
266
+ if "min_vals" in config and "max_vals" in config:
257
267
  # This is create_slider_config output - use first element from arrays
258
268
  normalized = {
259
- 'min_val': config['min_vals'][0],
260
- 'max_val': config['max_vals'][0],
261
- 'step': config['step_vals'][0],
262
- 'title': config_type.capitalize() # Default title
269
+ "min_val": config["min_vals"][0],
270
+ "max_val": config["max_vals"][0],
271
+ "step": config["step_vals"][0],
272
+ "title": config_type.capitalize(), # Default title
263
273
  }
264
-
274
+
265
275
  # Get pre-computed marks if available, otherwise calculate mark_interval
266
- if 'mark_vals' in config and len(config['mark_vals']) > 0:
267
- marks = config['mark_vals'][0]
268
- normalized['marks'] = marks # Pass the actual marks dictionary
276
+ if "mark_vals" in config and len(config["mark_vals"]) > 0:
277
+ marks = config["mark_vals"][0]
278
+ normalized["marks"] = marks # Pass the actual marks dictionary
269
279
  if len(marks) >= 2:
270
280
  # Calculate interval from first two marks
271
281
  mark_positions = sorted(marks.keys())
272
- normalized['mark_interval'] = mark_positions[1] - mark_positions[0]
282
+ normalized["mark_interval"] = mark_positions[1] - mark_positions[0]
273
283
  else:
274
284
  # Fallback: use step as mark interval
275
- normalized['mark_interval'] = normalized['step']
285
+ normalized["mark_interval"] = normalized["step"]
276
286
  else:
277
- normalized['mark_interval'] = normalized['step']
278
-
287
+ normalized["mark_interval"] = normalized["step"]
288
+
279
289
  # Add default value if available from grid values
280
- if 'grid_slider_vals' in config and len(config['grid_slider_vals']) > 0:
281
- normalized['default_val'] = config['grid_slider_vals'][0]
290
+ if "grid_slider_vals" in config and len(config["grid_slider_vals"]) > 0:
291
+ normalized["default_val"] = config["grid_slider_vals"][0]
282
292
  else:
283
- normalized['default_val'] = normalized['min_val']
284
-
293
+ normalized["default_val"] = normalized["min_val"]
294
+
285
295
  else:
286
296
  # This is legacy format - use as-is but ensure all required keys exist
287
297
  normalized = config.copy()
288
- if 'title' not in normalized:
289
- normalized['title'] = config_type.capitalize()
290
- if 'default_val' not in normalized:
291
- normalized['default_val'] = normalized.get('min_val', 0)
292
-
298
+ if "title" not in normalized:
299
+ normalized["title"] = config_type.capitalize()
300
+ if "default_val" not in normalized:
301
+ normalized["default_val"] = normalized.get("min_val", 0)
302
+
293
303
  return normalized
294
304
 
295
305
  def _make_id(self, subtype: str):
296
306
  """
297
307
  Generate a unique ID dictionary for component sub-elements.
298
-
308
+
299
309
  Args:
300
310
  subtype (str): The specific component subtype.
301
-
311
+
302
312
  Returns:
303
313
  dict: Complete ID dictionary containing base ID information plus subtype.
304
314
  """
305
- return {**self.id_base, 'subtype': subtype}
315
+ return {**self.id_base, "subtype": subtype}
306
316
 
307
317
  def _make_dropdown(self):
308
318
  """
309
319
  Create and configure the material selection dropdown.
310
-
320
+
311
321
  Returns:
312
322
  dash.dcc.Dropdown: Configured dropdown component for material selection.
313
323
  """
@@ -316,131 +326,175 @@ class MaterialSelector:
316
326
  options=self.material_options,
317
327
  value=self.default_material, # Can be None for no initial selection
318
328
  disabled=self.slider_disable,
319
- style={'width': '100%', 'margin-bottom': '10px'}
329
+ style={"width": "100%", "margin-bottom": "10px"},
320
330
  )
321
331
 
322
332
  def _make_weight_fraction_input(self):
323
333
  """
324
334
  Create and configure the weight percentage input.
325
-
335
+
326
336
  Returns:
327
337
  dash.dcc.Input: Configured numeric input for weight percentage.
328
338
  """
329
339
  return dcc.Input(
330
340
  id=self.weight_fraction_id,
331
- type='number',
341
+ type="number",
332
342
  value=self.default_weight_percent, # Use percentage directly
333
343
  min=0.0,
334
344
  max=100.0,
335
345
  step=0.1,
336
346
  disabled=self.slider_disable,
337
- style={'width': '100%', 'margin-bottom': '10px'}
347
+ style={"width": "100%", "margin-bottom": "10px"},
338
348
  )
339
349
 
340
350
  def __call__(self):
341
351
  """
342
352
  Generate the complete component layout as a callable object.
343
-
353
+
344
354
  Creates and returns a Dash HTML Div containing all components arranged
345
355
  in a horizontal layout with proper spacing and styling.
346
-
356
+
347
357
  Returns:
348
358
  dash.html.Div: Complete component layout with horizontal arrangement.
349
359
  """
350
- return html.Div([
351
- # Main horizontal layout
352
- html.Div([
353
- # Material selection column
354
- html.Div([
355
- html.P("Material:", style={'margin': '0px 0px 5px 0px', 'font-weight': 'bold'}),
356
- self._make_dropdown()
357
- ], style={'width': '20%', 'display': 'inline-block', 'vertical-align': 'top', 'padding-right': '25px'}),
358
-
359
- # Weight percentage column
360
- html.Div([
361
- html.P("Weight Percentage:", style={'margin': '0px 0px 5px 0px', 'font-weight': 'bold'}),
362
- self._make_weight_fraction_input()
363
- ], style={'width': '15%', 'display': 'inline-block', 'vertical-align': 'top', 'padding-right': '25px'}),
364
-
365
- # Density slider column
366
- html.Div([
367
- self.density_slider()
368
- ], style={'width': '30%', 'display': 'inline-block', 'vertical-align': 'top', 'padding-right': '25px'}),
369
-
370
- # Cost slider column
371
- html.Div([
372
- self.cost_slider()
373
- ], style={'width': '30%', 'display': 'inline-block', 'vertical-align': 'top'})
374
-
375
- ], style={'display': 'flex', 'align-items': 'flex-start', 'width': '100%', 'gap': '15px'})
376
-
377
- ],
378
- id=self.id_base,
379
- style={
380
- 'border': '1px solid #ddd',
381
- 'border-radius': '5px',
382
- 'padding': '15px',
383
- 'margin': '10px 0px',
384
- 'background-color': '#f9f9f9',
385
- 'width': self.div_width,
386
- 'display': 'none' if self.hidden else 'block'
387
- })
360
+ return html.Div(
361
+ [
362
+ # Main horizontal layout
363
+ html.Div(
364
+ [
365
+ # Material selection column
366
+ html.Div(
367
+ [
368
+ html.P(
369
+ "Material:",
370
+ style={
371
+ "margin": "0px 0px 5px 0px",
372
+ "font-weight": "bold",
373
+ },
374
+ ),
375
+ self._make_dropdown(),
376
+ ],
377
+ style={
378
+ "width": "20%",
379
+ "display": "inline-block",
380
+ "vertical-align": "top",
381
+ "padding-right": "25px",
382
+ },
383
+ ),
384
+ # Weight percentage column
385
+ html.Div(
386
+ [
387
+ html.P(
388
+ "Weight (%):",
389
+ style={
390
+ "margin": "0px 0px 5px 0px",
391
+ "font-weight": "bold",
392
+ },
393
+ ),
394
+ self._make_weight_fraction_input(),
395
+ ],
396
+ style={
397
+ "width": "15%",
398
+ "display": "inline-block",
399
+ "vertical-align": "top",
400
+ "padding-right": "25px",
401
+ },
402
+ ),
403
+ # Density slider column
404
+ html.Div(
405
+ [self.density_slider()],
406
+ style={
407
+ "width": "30%",
408
+ "display": "inline-block",
409
+ "vertical-align": "top",
410
+ "padding-right": "25px",
411
+ },
412
+ ),
413
+ # Cost slider column
414
+ html.Div(
415
+ [self.cost_slider()],
416
+ style={
417
+ "width": "30%",
418
+ "display": "inline-block",
419
+ "vertical-align": "top",
420
+ },
421
+ ),
422
+ ],
423
+ style={
424
+ "display": "flex",
425
+ "align-items": "flex-start",
426
+ "width": "100%",
427
+ "gap": "15px",
428
+ },
429
+ )
430
+ ],
431
+ id=self.id_base,
432
+ style={
433
+ "border": "1px solid #ddd",
434
+ "border-radius": "5px",
435
+ "padding": "15px",
436
+ "margin": "10px 0px",
437
+ "background-color": "#f9f9f9",
438
+ "width": self.div_width,
439
+ "display": "none" if self.hidden else "block",
440
+ },
441
+ )
388
442
 
389
443
  @property
390
444
  def components(self):
391
445
  """
392
446
  Get a dictionary mapping component types to their IDs.
393
-
447
+
394
448
  Returns:
395
449
  dict: Dictionary with component type keys mapping to their ID dictionaries.
396
450
  """
397
451
  return {
398
- 'dropdown': self.dropdown_id,
399
- 'weight_fraction': self.weight_fraction_id,
400
- 'cost_slider': self.cost_slider.slider_id,
401
- 'cost_input': self.cost_slider.input_id,
402
- 'density_slider': self.density_slider.slider_id,
403
- 'density_input': self.density_slider.input_id
452
+ "dropdown": self.dropdown_id,
453
+ "weight_fraction": self.weight_fraction_id,
454
+ "cost_slider": self.cost_slider.slider_id,
455
+ "cost_input": self.cost_slider.input_id,
456
+ "density_slider": self.density_slider.slider_id,
457
+ "density_input": self.density_slider.input_id,
404
458
  }
405
459
 
406
460
  def get_all_inputs(self):
407
461
  """
408
462
  Get Input objects for all component values.
409
-
463
+
410
464
  Returns:
411
465
  list: List containing Input objects for all components.
412
466
  """
413
467
  return [
414
- Input(self.dropdown_id, 'value'),
415
- Input(self.weight_fraction_id, 'value'),
416
- Input(self.cost_slider.slider_id, 'value'),
417
- Input(self.cost_slider.input_id, 'value'),
418
- Input(self.density_slider.slider_id, 'value'),
419
- Input(self.density_slider.input_id, 'value')
468
+ Input(self.dropdown_id, "value"),
469
+ Input(self.weight_fraction_id, "value"),
470
+ Input(self.cost_slider.slider_id, "value"),
471
+ Input(self.cost_slider.input_id, "value"),
472
+ Input(self.density_slider.slider_id, "value"),
473
+ Input(self.density_slider.input_id, "value"),
420
474
  ]
421
475
 
422
476
  def get_all_outputs(self):
423
477
  """
424
478
  Get Output objects for updating all component values.
425
-
479
+
426
480
  Returns:
427
481
  list: List containing Output objects for all components.
428
482
  """
429
483
  return [
430
- Output(self.dropdown_id, 'value'),
431
- Output(self.weight_fraction_id, 'value'),
432
- Output(self.cost_slider.slider_id, 'value'),
433
- Output(self.cost_slider.input_id, 'value'),
434
- Output(self.density_slider.slider_id, 'value'),
435
- Output(self.density_slider.input_id, 'value')
484
+ Output(self.dropdown_id, "value"),
485
+ Output(self.weight_fraction_id, "value"),
486
+ Output(self.cost_slider.slider_id, "value"),
487
+ Output(self.cost_slider.input_id, "value"),
488
+ Output(self.density_slider.slider_id, "value"),
489
+ Output(self.density_slider.input_id, "value"),
436
490
  ]
437
491
 
438
492
 
439
493
  class ActiveMaterialSelector(MaterialSelector):
440
494
  """
441
495
  A custom Dash component for active material selection with capacity controls.
442
-
443
- This component extends MaterialSelector functionality by adding reversible and
496
+
497
+ This component extends MaterialSelector functionality by adding reversible and
444
498
  irreversible capacity sliders alongside the standard density and cost controls.
445
499
  It creates a horizontal row containing:
446
500
  - A dropdown menu for selecting material names
@@ -450,9 +504,9 @@ class ActiveMaterialSelector(MaterialSelector):
450
504
  * Specific cost
451
505
  * Reversible capacity scaling
452
506
  * Irreversible capacity scaling
453
-
507
+
454
508
  This component is designed for battery active material interfaces where users need to
455
- specify both physical properties (cost, density) and electrochemical scaling factors
509
+ specify both physical properties (cost, density) and electrochemical scaling factors
456
510
  (reversible/irreversible capacity scaling).
457
511
  """
458
512
 
@@ -465,12 +519,12 @@ class ActiveMaterialSelector(MaterialSelector):
465
519
  default_material: str = None,
466
520
  default_weight_percent: float = 0,
467
521
  slider_disable: bool = False,
468
- div_width: str = '100%',
522
+ div_width: str = "100%",
469
523
  hidden: bool = False,
470
524
  ):
471
525
  """
472
526
  Initialize the ActiveMaterialSelector component.
473
-
527
+
474
528
  Args:
475
529
  id_base (dict): Base dictionary for generating component IDs.
476
530
  material_options (List[Dict], optional): List of material options for dropdown.
@@ -478,13 +532,13 @@ class ActiveMaterialSelector(MaterialSelector):
478
532
  If None, defaults to empty list (no options).
479
533
  slider_configs (dict): Output from create_slider_config with arrays where:
480
534
  - Index 0 = density configuration
481
- - Index 1 = cost configuration
535
+ - Index 1 = cost configuration
482
536
  - Index 2 = reversible capacity scaling configuration
483
537
  - Index 3 = irreversible capacity scaling configuration
484
538
  If empty dictionary {}, uses sensible defaults with preset values.
485
539
  If None (default), uses sensible defaults with no initial values.
486
540
  title (str, optional): Title displayed for the component. Defaults to "Active Material".
487
- default_material (str, optional): Default selected material. If None, no material
541
+ default_material (str, optional): Default selected material. If None, no material
488
542
  will be initially selected in the dropdown.
489
543
  default_weight_percent (float, optional): Default weight percentage (0.0-100.0). Defaults to 100.0.
490
544
  slider_disable (bool, optional): Whether to disable sliders. Defaults to False.
@@ -501,26 +555,42 @@ class ActiveMaterialSelector(MaterialSelector):
501
555
  elif slider_configs is not None and not slider_configs:
502
556
  # Empty dictionary - create sensible defaults with preset values
503
557
  from ..Utils.SliderControls import create_slider_config
504
- min_vals = [0.0, 0.0, 0.0, 0.0] # density, cost, rev_cap_scaling, irrev_cap_scaling
505
- max_vals = [0.1, 0.1, 0.1, 0.1] # density, cost, rev_cap_scaling, irrev_cap_scaling
506
- default_vals = [0.05, 0.05, 0.05, 0.05] # density, cost, rev_cap_scaling, irrev_cap_scaling
558
+
559
+ min_vals = [
560
+ 0.0,
561
+ 0.0,
562
+ 0.0,
563
+ 0.0,
564
+ ] # density, cost, rev_cap_scaling, irrev_cap_scaling
565
+ max_vals = [
566
+ 0.1,
567
+ 0.1,
568
+ 0.1,
569
+ 0.1,
570
+ ] # density, cost, rev_cap_scaling, irrev_cap_scaling
571
+ default_vals = [
572
+ 0.05,
573
+ 0.05,
574
+ 0.05,
575
+ 0.05,
576
+ ] # density, cost, rev_cap_scaling, irrev_cap_scaling
507
577
  slider_configs = create_slider_config(min_vals, max_vals, default_vals)
508
578
  else:
509
579
  # slider_configs is None - create sensible defaults with None values
510
580
  # Create manual config since create_slider_config doesn't handle None values
511
581
  slider_configs = {
512
- 'min_vals': [0.0, 0.0, 0.0, 0.0],
513
- 'max_vals': [0.1, 0.1, 0.1, 0.1],
514
- 'step_vals': [0.01, 0.01, 0.01, 0.01],
515
- 'grid_slider_vals': [None, None, None, None],
516
- 'mark_vals': [
517
- {0.0: '', 0.1: ''}, # density
518
- {0.0: '', 0.1: ''}, # cost
519
- {0.0: '', 0.1: ''}, # rev_cap_scaling
520
- {0.0: '', 0.1: ''} # irrev_cap_scaling
521
- ]
582
+ "min_vals": [0.0, 0.0, 0.0, 0.0],
583
+ "max_vals": [0.1, 0.1, 0.1, 0.1],
584
+ "step_vals": [0.01, 0.01, 0.01, 0.01],
585
+ "grid_slider_vals": [None, None, None, None],
586
+ "mark_vals": [
587
+ {0.0: "", 0.1: ""}, # density
588
+ {0.0: "", 0.1: ""}, # cost
589
+ {0.0: "", 0.1: ""}, # rev_cap_scaling
590
+ {0.0: "", 0.1: ""}, # irrev_cap_scaling
591
+ ],
522
592
  }
523
-
593
+
524
594
  # Initialize the parent MaterialSelector with first two sliders (density, cost)
525
595
  super().__init__(
526
596
  id_base=id_base,
@@ -531,164 +601,236 @@ class ActiveMaterialSelector(MaterialSelector):
531
601
  default_weight_percent=default_weight_percent,
532
602
  slider_disable=slider_disable,
533
603
  div_width=div_width,
534
- hidden=hidden
604
+ hidden=hidden,
535
605
  )
536
606
 
537
607
  # Add the additional capacity sliders (indices 2 and 3)
538
- reversible_capacity_normalized = self._normalize_config_from_arrays(slider_configs, 2, 'Reversible Capacity Scaling')
539
- irreversible_capacity_normalized = self._normalize_config_from_arrays(slider_configs, 3, 'Irreversible Capacity Scaling')
608
+ reversible_capacity_normalized = self._normalize_config_from_arrays(
609
+ slider_configs, 2, "Reversible Capacity Scaling"
610
+ )
611
+ irreversible_capacity_normalized = self._normalize_config_from_arrays(
612
+ slider_configs, 3, "Irreversible Capacity Scaling"
613
+ )
540
614
 
541
615
  # Create additional slider components
542
616
  reversible_capacity_slider_kwargs = {
543
- 'id_base': id_base,
544
- 'min_val': reversible_capacity_normalized['min_val'],
545
- 'max_val': reversible_capacity_normalized['max_val'],
546
- 'step': reversible_capacity_normalized['step'],
547
- 'mark_interval': reversible_capacity_normalized['mark_interval'],
548
- 'property_name': "reversible_capacity_scaling",
549
- 'title': reversible_capacity_normalized['title'],
550
- 'default_val': reversible_capacity_normalized.get('default_val', reversible_capacity_normalized['min_val']),
551
- 'with_slider_titles': True,
552
- 'slider_disable': slider_disable,
553
- 'div_width': '100%'
617
+ "id_base": id_base,
618
+ "min_val": reversible_capacity_normalized["min_val"],
619
+ "max_val": reversible_capacity_normalized["max_val"],
620
+ "step": reversible_capacity_normalized["step"],
621
+ "mark_interval": reversible_capacity_normalized["mark_interval"],
622
+ "property_name": "reversible_capacity_scaling",
623
+ "title": reversible_capacity_normalized["title"],
624
+ "default_val": reversible_capacity_normalized.get(
625
+ "default_val", reversible_capacity_normalized["min_val"]
626
+ ),
627
+ "with_slider_titles": True,
628
+ "slider_disable": slider_disable,
629
+ "div_width": "100%",
554
630
  }
555
- if 'marks' in reversible_capacity_normalized:
556
- reversible_capacity_slider_kwargs['marks'] = reversible_capacity_normalized['marks']
557
- self.reversible_capacity_slider = SliderWithTextInput(**reversible_capacity_slider_kwargs)
631
+ if "marks" in reversible_capacity_normalized:
632
+ reversible_capacity_slider_kwargs["marks"] = reversible_capacity_normalized[
633
+ "marks"
634
+ ]
635
+ self.reversible_capacity_slider = SliderWithTextInput(
636
+ **reversible_capacity_slider_kwargs
637
+ )
558
638
 
559
639
  irreversible_capacity_slider_kwargs = {
560
- 'id_base': id_base,
561
- 'min_val': irreversible_capacity_normalized['min_val'],
562
- 'max_val': irreversible_capacity_normalized['max_val'],
563
- 'step': irreversible_capacity_normalized['step'],
564
- 'mark_interval': irreversible_capacity_normalized['mark_interval'],
565
- 'property_name': "irreversible_capacity_scaling",
566
- 'title': irreversible_capacity_normalized['title'],
567
- 'default_val': irreversible_capacity_normalized.get('default_val', irreversible_capacity_normalized['min_val']),
568
- 'with_slider_titles': True,
569
- 'slider_disable': slider_disable,
570
- 'div_width': '100%'
640
+ "id_base": id_base,
641
+ "min_val": irreversible_capacity_normalized["min_val"],
642
+ "max_val": irreversible_capacity_normalized["max_val"],
643
+ "step": irreversible_capacity_normalized["step"],
644
+ "mark_interval": irreversible_capacity_normalized["mark_interval"],
645
+ "property_name": "irreversible_capacity_scaling",
646
+ "title": irreversible_capacity_normalized["title"],
647
+ "default_val": irreversible_capacity_normalized.get(
648
+ "default_val", irreversible_capacity_normalized["min_val"]
649
+ ),
650
+ "with_slider_titles": True,
651
+ "slider_disable": slider_disable,
652
+ "div_width": "100%",
571
653
  }
572
- if 'marks' in irreversible_capacity_normalized:
573
- irreversible_capacity_slider_kwargs['marks'] = irreversible_capacity_normalized['marks']
574
- self.irreversible_capacity_slider = SliderWithTextInput(**irreversible_capacity_slider_kwargs)
654
+ if "marks" in irreversible_capacity_normalized:
655
+ irreversible_capacity_slider_kwargs[
656
+ "marks"
657
+ ] = irreversible_capacity_normalized["marks"]
658
+ self.irreversible_capacity_slider = SliderWithTextInput(
659
+ **irreversible_capacity_slider_kwargs
660
+ )
575
661
 
576
662
  def __call__(self):
577
663
  """
578
664
  Generate the complete component layout as a callable object.
579
-
665
+
580
666
  Creates and returns a Dash HTML Div containing all components arranged
581
667
  in a horizontal layout with proper spacing and styling.
582
-
668
+
583
669
  Returns:
584
670
  dash.html.Div: Complete component layout with horizontal arrangement.
585
671
  """
586
- return html.Div([
587
- # Main horizontal layout
588
- html.Div([
589
- # Material selection column (15%)
590
- html.Div([
591
- html.P("Material:", style={'margin': '0px 0px 5px 0px', 'font-weight': 'bold'}),
592
- self._make_dropdown()
593
- ], style={'width': '15%', 'display': 'inline-block', 'vertical-align': 'top', 'padding-right': '15px'}),
594
-
595
- # Weight percentage column (15%)
596
- html.Div([
597
- html.P("Weight %:", style={'margin': '0px 0px 5px 0px', 'font-weight': 'bold'}),
598
- self._make_weight_fraction_input()
599
- ], style={'width': '15%', 'display': 'inline-block', 'vertical-align': 'top', 'padding-right': '15px'}),
600
-
601
- # Density slider column (17.5%)
602
- html.Div([
603
- self.density_slider()
604
- ], style={'width': '17.5%', 'display': 'inline-block', 'vertical-align': 'top', 'padding-right': '15px'}),
605
-
606
- # Cost slider column (17.5%)
607
- html.Div([
608
- self.cost_slider()
609
- ], style={'width': '17.5%', 'display': 'inline-block', 'vertical-align': 'top', 'padding-right': '15px'}),
610
-
611
- # Reversible capacity slider column (17.5%)
612
- html.Div([
613
- self.reversible_capacity_slider()
614
- ], style={'width': '17.5%', 'display': 'inline-block', 'vertical-align': 'top', 'padding-right': '15px'}),
615
-
616
- # Irreversible capacity slider column (17.5%)
617
- html.Div([
618
- self.irreversible_capacity_slider()
619
- ], style={'width': '17.5%', 'display': 'inline-block', 'vertical-align': 'top'})
620
-
621
- ], style={'display': 'flex', 'align-items': 'flex-start', 'width': '100%', 'gap': '10px'})
622
-
623
- ],
624
- id=self.id_base,
625
- style={
626
- 'border': '1px solid #ddd',
627
- 'border-radius': '5px',
628
- 'padding': '15px',
629
- 'margin': '10px 0px',
630
- 'background-color': '#f9f9f9',
631
- 'width': self.div_width,
632
- 'display': 'none' if self.hidden else 'block'
633
- })
672
+ return html.Div(
673
+ [
674
+ # Main horizontal layout
675
+ html.Div(
676
+ [
677
+ # Material selection column (15%)
678
+ html.Div(
679
+ [
680
+ html.P(
681
+ "Material:",
682
+ style={
683
+ "margin": "0px 0px 5px 0px",
684
+ "font-weight": "bold",
685
+ },
686
+ ),
687
+ self._make_dropdown(),
688
+ ],
689
+ style={
690
+ "width": "15%",
691
+ "display": "inline-block",
692
+ "vertical-align": "top",
693
+ "padding-right": "15px",
694
+ },
695
+ ),
696
+ # Weight percentage column (15%)
697
+ html.Div(
698
+ [
699
+ html.P(
700
+ "Weight (%):",
701
+ style={
702
+ "margin": "0px 0px 5px 0px",
703
+ "font-weight": "bold",
704
+ },
705
+ ),
706
+ self._make_weight_fraction_input(),
707
+ ],
708
+ style={
709
+ "width": "15%",
710
+ "display": "inline-block",
711
+ "vertical-align": "top",
712
+ "padding-right": "15px",
713
+ },
714
+ ),
715
+ # Density slider column (17.5%)
716
+ html.Div(
717
+ [self.density_slider()],
718
+ style={
719
+ "width": "17.5%",
720
+ "display": "inline-block",
721
+ "vertical-align": "top",
722
+ "padding-right": "15px",
723
+ },
724
+ ),
725
+ # Cost slider column (17.5%)
726
+ html.Div(
727
+ [self.cost_slider()],
728
+ style={
729
+ "width": "17.5%",
730
+ "display": "inline-block",
731
+ "vertical-align": "top",
732
+ "padding-right": "15px",
733
+ },
734
+ ),
735
+ # Reversible capacity slider column (17.5%)
736
+ html.Div(
737
+ [self.reversible_capacity_slider()],
738
+ style={
739
+ "width": "17.5%",
740
+ "display": "inline-block",
741
+ "vertical-align": "top",
742
+ "padding-right": "15px",
743
+ },
744
+ ),
745
+ # Irreversible capacity slider column (17.5%)
746
+ html.Div(
747
+ [self.irreversible_capacity_slider()],
748
+ style={
749
+ "width": "17.5%",
750
+ "display": "inline-block",
751
+ "vertical-align": "top",
752
+ },
753
+ ),
754
+ ],
755
+ style={
756
+ "display": "flex",
757
+ "align-items": "flex-start",
758
+ "width": "100%",
759
+ "gap": "10px",
760
+ },
761
+ )
762
+ ],
763
+ id=self.id_base,
764
+ style={
765
+ "border": "1px solid #ddd",
766
+ "border-radius": "5px",
767
+ "padding": "15px",
768
+ "margin": "10px 0px",
769
+ "background-color": "#f9f9f9",
770
+ "width": self.div_width,
771
+ "display": "none" if self.hidden else "block",
772
+ },
773
+ )
634
774
 
635
775
  @property
636
776
  def components(self):
637
777
  """
638
778
  Get a dictionary mapping component types to their IDs.
639
-
779
+
640
780
  Returns:
641
781
  dict: Dictionary with component type keys mapping to their ID dictionaries.
642
782
  """
643
783
  # Get base components from parent
644
784
  base_components = super().components
645
-
785
+
646
786
  # Add the additional capacity slider components
647
- base_components.update({
648
- 'reversible_capacity_scaling_slider': self.reversible_capacity_slider.slider_id,
649
- 'reversible_capacity_scaling_input': self.reversible_capacity_slider.input_id,
650
- 'irreversible_capacity_scaling_slider': self.irreversible_capacity_slider.slider_id,
651
- 'irreversible_capacity_scaling_input': self.irreversible_capacity_slider.input_id
652
- })
653
-
787
+ base_components.update(
788
+ {
789
+ "reversible_capacity_scaling_slider": self.reversible_capacity_slider.slider_id,
790
+ "reversible_capacity_scaling_input": self.reversible_capacity_slider.input_id,
791
+ "irreversible_capacity_scaling_slider": self.irreversible_capacity_slider.slider_id,
792
+ "irreversible_capacity_scaling_input": self.irreversible_capacity_slider.input_id,
793
+ }
794
+ )
795
+
654
796
  return base_components
655
797
 
656
798
  def get_all_inputs(self):
657
799
  """
658
800
  Get Input objects for all component values.
659
-
801
+
660
802
  Returns:
661
803
  list: List containing Input objects for all components.
662
804
  """
663
805
  # Get base inputs from parent
664
806
  base_inputs = super().get_all_inputs()
665
-
807
+
666
808
  # Add the additional capacity slider inputs
667
809
  capacity_inputs = [
668
- Input(self.reversible_capacity_slider.slider_id, 'value'),
669
- Input(self.reversible_capacity_slider.input_id, 'value'),
670
- Input(self.irreversible_capacity_slider.slider_id, 'value'),
671
- Input(self.irreversible_capacity_slider.input_id, 'value')
810
+ Input(self.reversible_capacity_slider.slider_id, "value"),
811
+ Input(self.reversible_capacity_slider.input_id, "value"),
812
+ Input(self.irreversible_capacity_slider.slider_id, "value"),
813
+ Input(self.irreversible_capacity_slider.input_id, "value"),
672
814
  ]
673
-
815
+
674
816
  return base_inputs + capacity_inputs
675
817
 
676
818
  def get_all_outputs(self):
677
819
  """
678
820
  Get Output objects for updating all component values.
679
-
821
+
680
822
  Returns:
681
823
  list: List containing Output objects for all components.
682
824
  """
683
825
  # Get base outputs from parent
684
826
  base_outputs = super().get_all_outputs()
685
-
827
+
686
828
  # Add the additional capacity slider outputs
687
829
  capacity_outputs = [
688
- Output(self.reversible_capacity_slider.slider_id, 'value'),
689
- Output(self.reversible_capacity_slider.input_id, 'value'),
690
- Output(self.irreversible_capacity_slider.slider_id, 'value'),
691
- Output(self.irreversible_capacity_slider.input_id, 'value')
830
+ Output(self.reversible_capacity_slider.slider_id, "value"),
831
+ Output(self.reversible_capacity_slider.input_id, "value"),
832
+ Output(self.irreversible_capacity_slider.slider_id, "value"),
833
+ Output(self.irreversible_capacity_slider.input_id, "value"),
692
834
  ]
693
-
694
- return base_outputs + capacity_outputs
835
+
836
+ return base_outputs + capacity_outputs