steer-core 0.1.7__py3-none-any.whl → 0.1.8__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.
@@ -0,0 +1,677 @@
1
+ """
2
+ Utility functions for slider controls and parameter management.
3
+
4
+ This module provides helper functions for automatically determining appropriate
5
+ slider configurations based on parameter ranges.
6
+ """
7
+
8
+ import math
9
+ from typing import List, Union, Dict
10
+ import numpy as np
11
+
12
+
13
+
14
+ def calculate_slider_steps(min_values: List[float], max_values: List[float]) -> List[float]:
15
+ """
16
+ Calculate reasonable slider steps based on parameter ranges.
17
+
18
+ This function analyzes the range of each parameter and suggests an appropriate
19
+ step size that provides good granularity. The step sizes are the same as input
20
+ steps to ensure consistent behavior between sliders and input fields:
21
+ - Ranges < 100: step = 0.01
22
+ - Ranges 100-999: step = 0.1
23
+ - Ranges 1000+: step = 1.0
24
+
25
+ Args:
26
+ min_values (List[float]): List of minimum values for each parameter
27
+ max_values (List[float]): List of maximum values for each parameter
28
+
29
+ Returns:
30
+ List[float]: List of recommended step sizes for each parameter
31
+
32
+ Raises:
33
+ ValueError: If lists have different lengths or if any max < min
34
+
35
+ Examples:
36
+ >>> min_vals = [0, 0, 0, 0]
37
+ >>> max_vals = [10, 100, 1000, 0.1]
38
+ >>> steps = calculate_slider_steps(min_vals, max_vals)
39
+ >>> steps
40
+ [0.01, 0.1, 1.0, 0.01]
41
+
42
+ >>> # Temperature range
43
+ >>> steps = calculate_slider_steps([20], [100])
44
+ >>> steps[0]
45
+ 0.01
46
+ """
47
+ if len(min_values) != len(max_values):
48
+ raise ValueError("min_values and max_values must have the same length")
49
+
50
+ steps = []
51
+
52
+ for min_val, max_val in zip(min_values, max_values):
53
+ if max_val < min_val:
54
+ raise ValueError(f"max_value ({max_val}) cannot be less than min_value ({min_val})")
55
+
56
+ # Calculate the range
57
+ range_val = max_val - min_val
58
+
59
+ if range_val == 0:
60
+ # If no range, use a very small step
61
+ steps.append(0.001)
62
+ continue
63
+
64
+ # Calculate step based on range magnitude
65
+ step = _calculate_step_for_range(range_val)
66
+ steps.append(step)
67
+
68
+ return steps
69
+
70
+
71
+ def _calculate_step_for_range(range_val: float) -> float:
72
+ """
73
+ Calculate an appropriate step size for a given range.
74
+
75
+ Uses the same step sizes as input steps to ensure sliders and inputs
76
+ have consistent granularity:
77
+ - Ranges < 100: step = 0.01
78
+ - Ranges 100-999: step = 0.1
79
+ - Ranges 1000+: step = 1.0
80
+
81
+ Args:
82
+ range_val (float): The range (max - min) for the parameter
83
+
84
+ Returns:
85
+ float: Recommended step size
86
+ """
87
+ if range_val <= 0:
88
+ return 0.01
89
+
90
+ # Use the same logic as input steps for consistency
91
+ if range_val < 100:
92
+ return 0.01
93
+ elif range_val < 1000:
94
+ return 0.1
95
+ else:
96
+ return 1.0
97
+
98
+
99
+ def _calculate_input_step_for_range(range_val: float) -> float:
100
+ """
101
+ Calculate an appropriate input step size for a given range.
102
+
103
+ Input steps are finer than slider steps to allow more precise control:
104
+ - Ranges < 100: input step = 0.01
105
+ - Ranges 100-999: input step = 0.1
106
+ - Ranges 1000+: input step = 1.0
107
+
108
+ Args:
109
+ range_val (float): The range (max - min) for the parameter
110
+
111
+ Returns:
112
+ float: Recommended input step size
113
+ """
114
+ if range_val < 100:
115
+ return 0.01
116
+ elif range_val < 1000:
117
+ return 0.1
118
+ else:
119
+ return 1.0
120
+
121
+
122
+ def calculate_input_steps(min_values: List[float], max_values: List[float]) -> List[float]:
123
+ """
124
+ Calculate appropriate input step sizes based on parameter ranges.
125
+
126
+ Input steps are typically finer than slider steps to allow precise adjustment.
127
+
128
+ Args:
129
+ min_values (List[float]): List of minimum values for each parameter
130
+ max_values (List[float]): List of maximum values for each parameter
131
+
132
+ Returns:
133
+ List[float]: List of recommended input step sizes for each parameter
134
+ """
135
+ if len(min_values) != len(max_values):
136
+ raise ValueError("min_values and max_values must have the same length")
137
+
138
+ input_steps = []
139
+
140
+ for min_val, max_val in zip(min_values, max_values):
141
+ if max_val < min_val:
142
+ raise ValueError(f"max_value ({max_val}) cannot be less than min_value ({min_val})")
143
+
144
+ # Calculate the range
145
+ range_val = max_val - min_val
146
+
147
+ # Calculate input step based on range magnitude
148
+ input_step = _calculate_input_step_for_range(range_val)
149
+ input_steps.append(input_step)
150
+
151
+ return input_steps
152
+
153
+
154
+ def _round_to_reasonable_precision(value: float) -> float:
155
+ """
156
+ Round a step value to a reasonable precision for UI display.
157
+
158
+ Args:
159
+ value (float): The step value to round
160
+
161
+ Returns:
162
+ float: Rounded step value
163
+ """
164
+ if value >= 1:
165
+ # For values >= 1, round to reasonable whole numbers or simple decimals
166
+ if value >= 10:
167
+ return round(value)
168
+ else:
169
+ return round(value, 1)
170
+ else:
171
+ # For values < 1, determine appropriate decimal places
172
+ magnitude = math.floor(math.log10(value))
173
+ decimal_places = max(1, -magnitude + 1)
174
+ return round(value, decimal_places)
175
+
176
+
177
+ def calculate_mark_intervals(min_values: List[float], max_values: List[float],
178
+ target_marks: int = 5) -> List[float]:
179
+ """
180
+ Calculate mark intervals for sliders based on range magnitude.
181
+
182
+ Creates marks at regular intervals that align with intuitive values:
183
+ - Range < 1: marks on every 0.1 (interval = 0.1)
184
+ - Range < 10: marks on every integer (interval = 1.0)
185
+ - Range < 100: marks on every multiple of 10 (interval = 10.0)
186
+ - Range >= 100: marks on every multiple of 100 (interval = 100.0)
187
+
188
+ Args:
189
+ min_values (List[float]): List of minimum values for each parameter
190
+ max_values (List[float]): List of maximum values for each parameter
191
+ target_marks (int): Target number of marks to display (unused, kept for compatibility)
192
+
193
+ Returns:
194
+ List[float]: List of recommended mark intervals for each parameter
195
+
196
+ Examples:
197
+ >>> min_vals = [0, 0, 0, 0]
198
+ >>> max_vals = [0.5, 5, 50, 500]
199
+ >>> intervals = calculate_mark_intervals(min_vals, max_vals)
200
+ >>> intervals
201
+ [0.1, 1.0, 10.0, 100.0]
202
+ """
203
+ if len(min_values) != len(max_values):
204
+ raise ValueError("min_values and max_values must have the same length")
205
+
206
+ intervals = []
207
+
208
+ for min_val, max_val in zip(min_values, max_values):
209
+ range_val = max_val - min_val
210
+
211
+ if range_val == 0:
212
+ intervals.append(1.0)
213
+ continue
214
+
215
+ # Determine interval based on range magnitude
216
+ # Also consider the number of marks to avoid overcrowding
217
+ if range_val < 1:
218
+ interval = 0.1 # Every 0.1 for very small ranges
219
+ elif range_val < 10:
220
+ interval = 1.0 # Every integer for ranges like 0-9.9
221
+ elif range_val < 100:
222
+ interval = 10.0 # Every multiple of 10
223
+ else:
224
+ interval = 100.0 # Every multiple of 100
225
+
226
+ intervals.append(interval)
227
+
228
+ return intervals
229
+
230
+
231
+ def create_slider_config(min_values: List[float], max_values: List[float],
232
+ property_values: List[float] = None) -> dict:
233
+ """
234
+ Create complete slider configurations with automatically calculated steps and marks.
235
+
236
+ This is a convenience function that combines step and mark interval calculations
237
+ to create ready-to-use slider configurations. If property values are provided,
238
+ they will be validated and adjusted to align with the calculated slider grid.
239
+
240
+ Args:
241
+ min_values (List[float]): Minimum values for each slider
242
+ max_values (List[float]): Maximum values for each slider
243
+ property_values (List[float], optional): Current property values to validate
244
+ against slider grid. If provided, values
245
+ will be snapped to nearest valid step.
246
+
247
+ Returns:
248
+ dict: Configuration dictionary with keys:
249
+ - 'min_vals' (List[float]): Minimum values for each slider (snapped to slider grid)
250
+ - 'max_vals' (List[float]): Maximum values for each slider (snapped to slider grid)
251
+ - 'step_vals' (List[float]): Calculated step values for each slider (same as input steps)
252
+ - 'input_step_vals' (List[float]): Step values for inputs (same as slider steps)
253
+ - 'mark_vals' (List[dict]): Mark positions for each slider (position → None mapping)
254
+ - 'grid_slider_vals' (List[float], optional): Property values snapped to slider grid if provided
255
+ - 'grid_input_vals' (List[float], optional): Property values snapped to input grid if provided
256
+
257
+ Example:
258
+ >>> config = create_slider_config(
259
+ ... min_values=[0, 20, 0],
260
+ ... max_values=[100, 80, 1000],
261
+ ... property_values=[23.7, 45.3, 567.8]
262
+ ... )
263
+ >>> config['step_vals']
264
+ [0.01, 0.01, 1.0]
265
+ >>> config['input_step_vals']
266
+ [0.01, 0.01, 1.0]
267
+ >>> config['grid_vals'][0] # 23.7 snapped to grid
268
+ 23.7
269
+ """
270
+ # Validate inputs
271
+ if len(min_values) != len(max_values):
272
+ raise ValueError("min_values and max_values must have the same length")
273
+
274
+ if property_values is not None and len(property_values) != len(min_values):
275
+ raise ValueError("property_values must have the same length as min_values and max_values")
276
+
277
+ # Calculate steps and mark intervals
278
+ steps = calculate_slider_steps(min_values, max_values) # Use the calculated steps directly
279
+
280
+ input_steps = calculate_input_steps(min_values, max_values) # Calculate input steps based on range
281
+
282
+ # Snap min and max values to the slider grid
283
+ grid_min_values = []
284
+ grid_max_values = []
285
+ for i, (min_val, max_val, step) in enumerate(zip(min_values, max_values, steps)):
286
+ # For min value: round down to nearest grid point (floor)
287
+ grid_min = min_val - (min_val % step) if min_val % step != 0 else min_val
288
+
289
+ # For max value: round up to nearest grid point (ceil)
290
+ if max_val % step == 0:
291
+ grid_max = max_val
292
+ else:
293
+ grid_max = max_val + (step - (max_val % step))
294
+
295
+ grid_min_values.append(grid_min)
296
+ grid_max_values.append(grid_max)
297
+
298
+ mark_intervals = calculate_mark_intervals(grid_min_values, grid_max_values)
299
+
300
+ # Create mark dictionaries for each slider
301
+ mark_vals = []
302
+ grid_slider_vals = []
303
+ grid_input_vals = []
304
+
305
+ for i, (min_val, max_val, step, input_step, mark_interval) in enumerate(
306
+ zip(grid_min_values, grid_max_values, steps, input_steps, mark_intervals)
307
+ ):
308
+ # Generate marks dictionary for this slider (positions only, no labels)
309
+ marks = {}
310
+
311
+ # Find the first mark position that's a multiple of mark_interval
312
+ # Start from the nearest multiple of mark_interval >= min_val
313
+ if mark_interval > 0:
314
+ first_mark = math.ceil(min_val / mark_interval) * mark_interval
315
+ current_mark = first_mark
316
+
317
+ while current_mark <= max_val:
318
+ # Round to avoid floating-point precision issues
319
+ rounded_mark = round(current_mark, 10) # Round to 10 decimal places
320
+ marks[rounded_mark] = '' # No label
321
+ current_mark += mark_interval
322
+
323
+ # Ensure we don't exceed max_val due to floating point precision
324
+ if current_mark > max_val + mark_interval * 0.01:
325
+ break
326
+
327
+ mark_vals.append(marks)
328
+
329
+ # Handle property value if provided
330
+ if property_values is not None:
331
+ property_val = property_values[i]
332
+ # Snap to both slider grid (coarser) and input grid (finer) without clamping to range
333
+ slider_grid_value = snap_to_grid_no_clamp(property_val, min_val, step)
334
+ input_grid_value = snap_to_grid_no_clamp(property_val, min_val, input_step)
335
+ grid_slider_vals.append(slider_grid_value)
336
+ grid_input_vals.append(input_grid_value)
337
+
338
+ # Create the configuration dictionary
339
+ config = {
340
+ 'min_vals': grid_min_values,
341
+ 'max_vals': grid_max_values,
342
+ 'step_vals': steps,
343
+ 'input_step_vals': input_steps,
344
+ 'mark_vals': mark_vals
345
+ }
346
+
347
+ # Add grid values if property values were provided
348
+ if property_values is not None:
349
+ config['grid_slider_vals'] = grid_slider_vals
350
+ config['grid_input_vals'] = grid_input_vals
351
+
352
+ return config
353
+
354
+
355
+ def create_range_slider_config(min_values: List[float], max_values: List[float],
356
+ lower_values: List[float] = None,
357
+ upper_values: List[float] = None) -> dict:
358
+ """
359
+ Create complete range slider configurations with automatically calculated steps and marks.
360
+
361
+ This is a convenience function that combines step and mark interval calculations
362
+ to create ready-to-use range slider configurations. If lower and upper values are provided,
363
+ they will be validated, adjusted to ensure proper ordering, and aligned with the
364
+ calculated slider grids.
365
+
366
+ Args:
367
+ min_values (List[float]): Minimum values for each range slider
368
+ max_values (List[float]): Maximum values for each range slider
369
+ lower_values (List[float], optional): Current lower bound values for each range slider.
370
+ Will be snapped to grid and validated.
371
+ upper_values (List[float], optional): Current upper bound values for each range slider.
372
+ Will be snapped to grid and validated.
373
+
374
+ Returns:
375
+ dict: Complete configuration dictionary containing:
376
+ - 'min_vals' (List[float]): Minimum values for each range slider (snapped to slider grid)
377
+ - 'max_vals' (List[float]): Maximum values for each range slider (snapped to slider grid)
378
+ - 'step_vals' (List[float]): Calculated step values for each range slider (same as input steps)
379
+ - 'input_step_vals' (List[float]): Step values for inputs (same as slider steps)
380
+ - 'mark_vals' (List[dict]): Mark positions for each range slider (position → '' mapping)
381
+ - 'grid_slider_vals' (List[tuple], optional): Range values snapped to slider grid if provided [(lower, upper), ...]
382
+ - 'grid_input_vals' (List[tuple], optional): Range values snapped to input grid if provided [(lower, upper), ...]
383
+
384
+ Example:
385
+ >>> config = create_range_slider_config(
386
+ ... min_values=[0, 20, 0],
387
+ ... max_values=[100, 80, 1000],
388
+ ... lower_values=[23.7, 45.3, 167.8],
389
+ ... upper_values=[67.2, 55.1, 834.5]
390
+ ... )
391
+ >>> config['step_vals']
392
+ [0.01, 0.01, 1.0]
393
+ >>> config['input_step_vals']
394
+ [0.01, 0.01, 1.0]
395
+ >>> config['grid_slider_vals'][0] # (23.7, 67.2) snapped to slider grid
396
+ (23.7, 67.2)
397
+ """
398
+ # Validate inputs
399
+ if len(min_values) != len(max_values):
400
+ raise ValueError("min_values and max_values must have the same length")
401
+
402
+ if lower_values is not None and len(lower_values) != len(min_values):
403
+ raise ValueError("lower_values must have the same length as min_values and max_values")
404
+
405
+ if upper_values is not None and len(upper_values) != len(min_values):
406
+ raise ValueError("upper_values must have the same length as min_values and max_values")
407
+
408
+ # Both lower_values and upper_values must be provided together or not at all
409
+ if (lower_values is None) != (upper_values is None):
410
+ raise ValueError("lower_values and upper_values must both be provided or both be None")
411
+
412
+ # Calculate steps and mark intervals
413
+ steps = calculate_slider_steps(min_values, max_values) # Use the calculated steps directly
414
+
415
+ input_steps = calculate_input_steps(min_values, max_values) # Calculate input steps based on range
416
+
417
+ # Snap min and max values to the slider grid
418
+ grid_min_values = []
419
+ grid_max_values = []
420
+ for i, (min_val, max_val, step) in enumerate(zip(min_values, max_values, steps)):
421
+ # For min value: round down to nearest grid point (floor)
422
+ grid_min = min_val - (min_val % step) if min_val % step != 0 else min_val
423
+
424
+ # For max value: round up to nearest grid point (ceil)
425
+ remainder = max_val % step
426
+ grid_max = max_val + (step - remainder) if remainder != 0 else max_val
427
+
428
+ grid_min_values.append(grid_min)
429
+ grid_max_values.append(grid_max)
430
+
431
+ mark_intervals = calculate_mark_intervals(grid_min_values, grid_max_values)
432
+
433
+ # Generate marks for each slider
434
+ mark_vals = []
435
+ for min_val, max_val, interval in zip(grid_min_values, grid_max_values, mark_intervals):
436
+ marks = {}
437
+
438
+ # Find the first mark position that's a multiple of interval
439
+ # Start from the nearest multiple of interval >= min_val
440
+ if interval > 0:
441
+ first_mark = math.ceil(min_val / interval) * interval
442
+ current = first_mark
443
+
444
+ while current <= max_val:
445
+ # Round to avoid floating-point precision issues
446
+ rounded_mark = round(current, 10) # Round to 10 decimal places
447
+ marks[rounded_mark] = '' # Empty string for clean appearance
448
+ current += interval
449
+
450
+ mark_vals.append(marks)
451
+
452
+ # Process property values if provided
453
+ grid_slider_vals = []
454
+ grid_input_vals = []
455
+
456
+ if lower_values is not None and upper_values is not None:
457
+ for i, (min_val, max_val, step, input_step, lower_val, upper_val) in enumerate(
458
+ zip(grid_min_values, grid_max_values, steps, input_steps, lower_values, upper_values)
459
+ ):
460
+ # Ensure proper ordering of lower/upper values
461
+ actual_lower = min(lower_val, upper_val)
462
+ actual_upper = max(lower_val, upper_val)
463
+
464
+ # Snap to grids
465
+ lower_slider_grid = snap_to_grid_no_clamp(actual_lower, min_val, step)
466
+ upper_slider_grid = snap_to_grid_no_clamp(actual_upper, min_val, step)
467
+ lower_input_grid = snap_to_grid_no_clamp(actual_lower, min_val, input_step)
468
+ upper_input_grid = snap_to_grid_no_clamp(actual_upper, min_val, input_step)
469
+
470
+ # Store as tuples
471
+ grid_slider_vals.append((lower_slider_grid, upper_slider_grid))
472
+ grid_input_vals.append((lower_input_grid, upper_input_grid))
473
+
474
+ # Create the configuration dictionary
475
+ config = {
476
+ 'min_vals': grid_min_values,
477
+ 'max_vals': grid_max_values,
478
+ 'step_vals': steps,
479
+ 'input_step_vals': input_steps,
480
+ 'mark_vals': mark_vals
481
+ }
482
+
483
+ # Add grid values if property values were provided
484
+ if lower_values is not None and upper_values is not None:
485
+ config['grid_slider_vals'] = grid_slider_vals
486
+ config['grid_input_vals'] = grid_input_vals
487
+
488
+ return config
489
+
490
+
491
+ def snap_to_slider_grid(value: float, min_val: float, max_val: float, step: float) -> float:
492
+ """
493
+ Snap a value to the nearest valid position on a slider grid.
494
+
495
+ This ensures that property values align with the discrete steps of a slider,
496
+ preventing issues with values that fall between valid slider positions.
497
+
498
+ Args:
499
+ value (float): The value to snap to the grid
500
+ min_val (float): Minimum value of the slider range
501
+ max_val (float): Maximum value of the slider range
502
+ step (float): Step size of the slider
503
+
504
+ Returns:
505
+ float: Value snapped to the nearest valid slider position
506
+
507
+ Examples:
508
+ >>> # Snap 23.7 to a slider with step 0.1
509
+ >>> snap_to_slider_grid(23.7, 0, 100, 0.1)
510
+ 23.7
511
+
512
+ >>> # Snap 23.75 to a slider with step 0.1
513
+ >>> snap_to_slider_grid(23.75, 0, 100, 0.1)
514
+ 23.8
515
+
516
+ >>> # Clamp value outside range
517
+ >>> snap_to_slider_grid(150, 0, 100, 1.0)
518
+ 100.0
519
+ """
520
+ # First clamp to range
521
+ clamped_value = max(min_val, min(max_val, value))
522
+
523
+ # Calculate offset from minimum
524
+ offset = clamped_value - min_val
525
+
526
+ # Round to nearest step
527
+ num_steps = round(offset / step)
528
+
529
+ # Calculate snapped value
530
+ snapped_value = min_val + (num_steps * step)
531
+
532
+ # Ensure we stay within bounds (floating point precision issues)
533
+ snapped_value = max(min_val, min(max_val, snapped_value))
534
+
535
+ # Round to appropriate precision based on step size
536
+ if step >= 1:
537
+ return round(snapped_value)
538
+ else:
539
+ decimal_places = max(0, -math.floor(math.log10(step)))
540
+ return round(snapped_value, decimal_places)
541
+
542
+
543
+ def snap_to_grid_no_clamp(value: float, min_val: float, step: float) -> float:
544
+ """
545
+ Snap a value to the nearest grid position without clamping to range.
546
+
547
+ This function snaps values to the grid defined by min_val and step,
548
+ but does NOT clamp values to stay within a min/max range. This allows
549
+ property values outside the slider range to maintain their relative
550
+ position on the extended grid.
551
+
552
+ Args:
553
+ value (float): The value to snap to the grid
554
+ min_val (float): Minimum value of the slider range (grid reference point)
555
+ step (float): Step size of the grid
556
+
557
+ Returns:
558
+ float: Value snapped to the nearest valid grid position
559
+
560
+ Examples:
561
+ >>> # Snap value within range
562
+ >>> snap_to_grid_no_clamp(23.75, 0, 0.1)
563
+ 23.8
564
+
565
+ >>> # Snap value outside range (not clamped)
566
+ >>> snap_to_grid_no_clamp(150.3, 0, 0.1)
567
+ 150.3
568
+
569
+ >>> # Snap value below range (not clamped)
570
+ >>> snap_to_grid_no_clamp(-5.67, 0, 0.1)
571
+ -5.7
572
+ """
573
+ # Calculate offset from minimum (no clamping)
574
+ offset = value - min_val
575
+
576
+ # Round to nearest step
577
+ num_steps = round(offset / step)
578
+
579
+ # Calculate snapped value
580
+ snapped_value = min_val + (num_steps * step)
581
+
582
+ # Round to appropriate precision based on step size
583
+ if step >= 1:
584
+ return round(snapped_value)
585
+ else:
586
+ decimal_places = max(0, -math.floor(math.log10(step)))
587
+ return round(snapped_value, decimal_places)
588
+
589
+
590
+ def format_slider_value(value: float, step: float) -> str:
591
+ """
592
+ Format a slider value for display based on the step size.
593
+
594
+ Automatically determines appropriate decimal places based on the step size
595
+ to avoid displaying unnecessary precision.
596
+
597
+ Args:
598
+ value (float): The value to format
599
+ step (float): The step size of the slider
600
+
601
+ Returns:
602
+ str: Formatted value string
603
+
604
+ Examples:
605
+ >>> format_slider_value(123.456, 1.0)
606
+ '123'
607
+ >>> format_slider_value(123.456, 0.1)
608
+ '123.5'
609
+ >>> format_slider_value(123.456, 0.01)
610
+ '123.46'
611
+ """
612
+ if step >= 1:
613
+ return f"{value:.0f}"
614
+ else:
615
+ # Determine decimal places based on step size
616
+ decimal_places = max(0, -math.floor(math.log10(step)))
617
+ return f"{value:.{decimal_places}f}"
618
+
619
+
620
+ def are_slider_input_values_incompatible(
621
+ slider_value: float,
622
+ input_value: float,
623
+ slider_step: float,
624
+ input_step: float
625
+ ) -> bool:
626
+ """
627
+ Check if slider and input values are incompatible with each other.
628
+
629
+ Two values are considered incompatible if there is no single original value
630
+ that would snap to both the given slider grid value AND the given input grid value.
631
+ This typically happens when the slider value and input value represent different
632
+ original values that have been snapped to their respective grids.
633
+
634
+ Args:
635
+ slider_value (float): The value from the slider grid
636
+ input_value (float): The value from the input grid
637
+ slider_step (float): Step size for the slider grid
638
+ input_step (float): Step size for the input grid
639
+
640
+ Returns:
641
+ bool: True if the values are incompatible, False if they could represent
642
+ the same original value snapped to different grids
643
+
644
+ Examples:
645
+ >>> # Compatible values: both could come from original value 12.36
646
+ >>> are_slider_input_values_incompatible(12.4, 12.36, 0, 0.1, 0.01)
647
+ False
648
+
649
+ >>> # Incompatible values: no single value could snap to both
650
+ >>> are_slider_input_values_incompatible(12.3, 12.36, 0, 0.1, 0.01)
651
+ True
652
+
653
+ >>> # Compatible values: both could come from original value 12.35
654
+ >>> are_slider_input_values_incompatible(12.4, 12.35, 0, 0.1, 0.01)
655
+ False
656
+ """
657
+ # Calculate the range of original values that would snap to the slider_value
658
+ slider_half_step = slider_step / 2.0
659
+ slider_min_original = slider_value - slider_half_step
660
+ slider_max_original = slider_value + slider_half_step
661
+
662
+ # Calculate the range of original values that would snap to the input_value
663
+ input_half_step = input_step / 2.0
664
+ input_min_original = input_value - input_half_step
665
+ input_max_original = input_value + input_half_step
666
+
667
+ # Check if the ranges overlap
668
+ # If they overlap, there exists at least one original value that would snap to both
669
+ overlap_start = max(slider_min_original, input_min_original)
670
+ overlap_end = min(slider_max_original, input_max_original)
671
+
672
+ # If overlap_start > overlap_end, there's no overlap (incompatible)
673
+ # Use a small tolerance for floating-point comparison
674
+ tolerance = 1e-12
675
+ return overlap_start > overlap_end + tolerance
676
+
677
+
File without changes
steer_core/__init__.py CHANGED
@@ -1 +1 @@
1
- __version__ = "0.1.7"
1
+ __version__ = "0.1.8"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: steer-core
3
- Version: 0.1.7
3
+ Version: 0.1.8
4
4
  Summary: Modelling energy storage from cell to site - STEER OpenCell Design
5
5
  Home-page: https://github.com/nicholas9182/steer-core/
6
6
  Author: Nicholas Siemons