-
Notifications
You must be signed in to change notification settings - Fork 9
Feature/v3/several improvements #372
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
Changes from all commits
e663d35
a674194
7d20b94
d4d0dda
12f1203
85d499a
c2ecc28
4248f81
487cce4
97c258d
37ff43a
b6b8ac3
e69c504
36f4986
c4b57cf
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -53,49 +53,67 @@ def to_binary( | |||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| @staticmethod | ||||||||||||||||||||||||||||||
| def count_consecutive_states( | ||||||||||||||||||||||||||||||
| binary_values: xr.DataArray, | ||||||||||||||||||||||||||||||
| binary_values: xr.DataArray | np.ndarray | list[int, float], | ||||||||||||||||||||||||||||||
| dim: str = 'time', | ||||||||||||||||||||||||||||||
| epsilon: float = None, | ||||||||||||||||||||||||||||||
| epsilon: float | None = None, | ||||||||||||||||||||||||||||||
| ) -> float: | ||||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||||
| Counts the number of consecutive states in a binary time series. | ||||||||||||||||||||||||||||||
| """Count consecutive steps in the final active state of a binary time series. | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| This function counts how many consecutive time steps the series remains "on" | ||||||||||||||||||||||||||||||
| (non-zero) at the end of the time series. If the final state is "off", returns 0. | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| Args: | ||||||||||||||||||||||||||||||
| binary_values: Binary DataArray | ||||||||||||||||||||||||||||||
| dim: Dimension to count consecutive states over | ||||||||||||||||||||||||||||||
| epsilon: Tolerance for zero detection (uses CONFIG.modeling.EPSILON if None) | ||||||||||||||||||||||||||||||
| binary_values: Binary DataArray with values close to 0 (off) or 1 (on). | ||||||||||||||||||||||||||||||
| dim: Dimension along which to count consecutive states. | ||||||||||||||||||||||||||||||
| epsilon: Tolerance for zero detection. Uses CONFIG.modeling.EPSILON if None. | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| Returns: | ||||||||||||||||||||||||||||||
| The consecutive number of steps spent in the final state of the timeseries | ||||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||||
| if epsilon is None: | ||||||||||||||||||||||||||||||
| epsilon = CONFIG.modeling.EPSILON | ||||||||||||||||||||||||||||||
| Sum of values in the final consecutive "on" period. Returns 0.0 if the | ||||||||||||||||||||||||||||||
| final state is "off". | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| Examples: | ||||||||||||||||||||||||||||||
| >>> arr = xr.DataArray([0, 0, 1, 1, 1, 0, 1, 1], dims=['time']) | ||||||||||||||||||||||||||||||
| >>> ModelingUtilitiesAbstract.count_consecutive_states(arr) | ||||||||||||||||||||||||||||||
| 2.0 | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| binary_values = binary_values.any(dim=[d for d in binary_values.dims if d != dim]) | ||||||||||||||||||||||||||||||
| >>> arr = [0, 0, 1, 0, 1, 1, 1, 1] | ||||||||||||||||||||||||||||||
| >>> ModelingUtilitiesAbstract.count_consecutive_states(arr) | ||||||||||||||||||||||||||||||
| 4.0 | ||||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||||
| epsilon = epsilon or CONFIG.modeling.EPSILON | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| if isinstance(binary_values, xr.DataArray): | ||||||||||||||||||||||||||||||
| # xarray path | ||||||||||||||||||||||||||||||
| other_dims = [d for d in binary_values.dims if d != dim] | ||||||||||||||||||||||||||||||
| if other_dims: | ||||||||||||||||||||||||||||||
| binary_values = binary_values.any(dim=other_dims) | ||||||||||||||||||||||||||||||
| arr = binary_values.values | ||||||||||||||||||||||||||||||
| else: | ||||||||||||||||||||||||||||||
| # numpy/array-like path | ||||||||||||||||||||||||||||||
| arr = np.asarray(binary_values) | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| # Handle scalar case | ||||||||||||||||||||||||||||||
| if binary_values.ndim == 0: | ||||||||||||||||||||||||||||||
| return float(binary_values.item()) | ||||||||||||||||||||||||||||||
| # Flatten to 1D if needed | ||||||||||||||||||||||||||||||
| arr = arr.ravel() if arr.ndim > 1 else arr | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| # Check if final state is off | ||||||||||||||||||||||||||||||
| if np.isclose(binary_values.isel({dim: -1}), 0, atol=epsilon).all(): | ||||||||||||||||||||||||||||||
| # Handle edge cases | ||||||||||||||||||||||||||||||
| if arr.size == 0: | ||||||||||||||||||||||||||||||
| return 0.0 | ||||||||||||||||||||||||||||||
| if arr.size == 1: | ||||||||||||||||||||||||||||||
| return float(arr[0]) if not np.isclose(arr[0], 0, atol=epsilon) else 0.0 | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| # Find consecutive 'on' period from the end | ||||||||||||||||||||||||||||||
| is_zero = np.isclose(binary_values, 0, atol=epsilon) | ||||||||||||||||||||||||||||||
| # Return 0 if final state is off | ||||||||||||||||||||||||||||||
| if np.isclose(arr[-1], 0, atol=epsilon): | ||||||||||||||||||||||||||||||
| return 0.0 | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| # Find the last zero, then sum everything after it | ||||||||||||||||||||||||||||||
| # Find the last zero position (treat NaNs as off) | ||||||||||||||||||||||||||||||
| arr = np.nan_to_num(arr, nan=0.0) | ||||||||||||||||||||||||||||||
| is_zero = np.isclose(arr, 0, atol=epsilon) | ||||||||||||||||||||||||||||||
| zero_indices = np.where(is_zero)[0] | ||||||||||||||||||||||||||||||
| if len(zero_indices) == 0: | ||||||||||||||||||||||||||||||
| # All 'on' - sum everything | ||||||||||||||||||||||||||||||
| start_idx = 0 | ||||||||||||||||||||||||||||||
| else: | ||||||||||||||||||||||||||||||
| # Start after last zero | ||||||||||||||||||||||||||||||
| start_idx = zero_indices[-1] + 1 | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| consecutive_values = binary_values.isel({dim: slice(start_idx, None)}) | ||||||||||||||||||||||||||||||
| # Calculate sum from last zero to end | ||||||||||||||||||||||||||||||
| start_idx = zero_indices[-1] + 1 if zero_indices.size > 0 else 0 | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| return float(consecutive_values.sum().item()) # TODO: Som only over one dim? | ||||||||||||||||||||||||||||||
| return float(np.sum(arr[start_idx:])) | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| class ModelingUtilities: | ||||||||||||||||||||||||||||||
|
|
@@ -308,7 +326,13 @@ def consecutive_duration_tracking( | |||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| # Handle initial condition for minimum duration | ||||||||||||||||||||||||||||||
| if previous_duration > 0 and previous_duration < minimum_duration.isel({duration_dim: 0}).max(): | ||||||||||||||||||||||||||||||
| prev = ( | ||||||||||||||||||||||||||||||
| float(previous_duration) | ||||||||||||||||||||||||||||||
| if not isinstance(previous_duration, xr.DataArray) | ||||||||||||||||||||||||||||||
| else float(previous_duration.max().item()) | ||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||
| min0 = float(minimum_duration.isel({duration_dim: 0}).max().item()) | ||||||||||||||||||||||||||||||
| if prev > 0 and prev < min0: | ||||||||||||||||||||||||||||||
|
Comment on lines
329
to
335
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Use duration_dim instead of hard-coded 'time'. Hard-coding time ignores the duration_dim parameter and breaks for other coordinate names. - prev = (
- float(previous_duration)
- if not isinstance(previous_duration, xr.DataArray)
- else float(previous_duration.max().item())
- )
- min0 = float(minimum_duration.isel(time=0).max().item())
+ prev = (
+ float(previous_duration)
+ if not isinstance(previous_duration, xr.DataArray)
+ else float(previous_duration.max().item())
+ )
+ min0 = float(minimum_duration.isel({duration_dim: 0}).max().item())📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||
| constraints['initial_lb'] = model.add_constraints( | ||||||||||||||||||||||||||||||
| state_variable.isel({duration_dim: 0}) == 1, name=f'{duration.name}|initial_lb' | ||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||
|
|
@@ -435,7 +459,7 @@ def bounds_with_state( | |||||||||||||||||||||||||||||
| lower_bound, upper_bound = bounds | ||||||||||||||||||||||||||||||
| name = name or f'{variable.name}' | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| if np.all(lower_bound - upper_bound) < 1e-10: | ||||||||||||||||||||||||||||||
| if np.allclose(lower_bound, upper_bound, atol=1e-10, equal_nan=True): | ||||||||||||||||||||||||||||||
| fix_constraint = model.add_constraints(variable == variable_state * upper_bound, name=f'{name}|fix') | ||||||||||||||||||||||||||||||
| return [fix_constraint] | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
|
|
@@ -481,7 +505,7 @@ def scaled_bounds( | |||||||||||||||||||||||||||||
| rel_lower, rel_upper = relative_bounds | ||||||||||||||||||||||||||||||
| name = name or f'{variable.name}' | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| if np.abs(rel_lower - rel_upper).all() < 10e-10: | ||||||||||||||||||||||||||||||
| if np.allclose(rel_lower, rel_upper, atol=1e-10, equal_nan=True): | ||||||||||||||||||||||||||||||
| return [model.add_constraints(variable == scaling_variable * rel_lower, name=f'{name}|fixed')] | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| upper_constraint = model.add_constraints(variable <= scaling_variable * rel_upper, name=f'{name}|ub') | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix: ambiguous truth value with xarray and improve NaN handling for “constant” detection.
(da.max(dim) == da.min(dim)).all()returns a 0‑D DataArray; using it directly inifcan raise “truth value is ambiguous.” It may also fail to treat “all‑NaN” slices as constant. Replace with a robust check that handles non‑numeric dtypes and NaNs.Apply this diff:
📝 Committable suggestion
🤖 Prompt for AI Agents