Skip to content

Treat NaN as absent in v1 arithmetic#627

Draft
FBumann wants to merge 6 commits intoharmonize-linopy-operations-mixedfrom
nan-as-absent
Draft

Treat NaN as absent in v1 arithmetic#627
FBumann wants to merge 6 commits intoharmonize-linopy-operations-mixedfrom
nan-as-absent

Conversation

@FBumann
Copy link
Collaborator

@FBumann FBumann commented Mar 18, 2026

Summary

  • NaN in multiplicative/divisive constants now masks the term (makes it absent) instead of raising ValueError
  • NaN in additive/subtractive constants is treated as 0 (additive identity)
  • NaN in constraint RHS still raises ValueError — silently skipping constraints is too dangerous
  • Variable.to_linexpr(nan_coeff) now correctly produces absent slots in v1 (was inconsistently filling with 0)
  • New mixed-coordinate-arithmetic.ipynb notebook documenting patterns for combining variables with different coordinate subsets (from PR #591 feedback)

Motivation

Addresses @brynpickering's feedback in #591: the fillna(0) ceremony before every multiplication with incomplete cost data was verbose and error-prone. With NaN-as-mask, the pre-align pattern becomes clean:

# Before (NaN raises): fillna required everywhere
cap_a_al, cap_b_al, cost_a_al, cost_b_al = linopy.align(
    cap_a, cap_b, cost_a.fillna(0), cost_b.fillna(0), join="outer"
)
combined = cap_a_al * cost_a_al + cap_b_al * cost_b_al

# After (NaN masks): no fillna needed
cap_a_al, cap_b_al, cost_a_al, cost_b_al = linopy.align(
    cap_a, cap_b, cost_a, cost_b, join="outer"
)
combined = cap_a_al * cost_a_al + cap_b_al * cost_b_al

NaN behavior summary (v1)

Operation NaN behavior
expr + nan NaN → 0 (additive identity)
expr * nan NaN → absent (masks term)
expr / nan NaN → absent (masks term)
expr <= nan ValueError (use .sel()/mask=)

Test plan

  • All 5333 existing tests pass
  • Verify notebooks execute: arithmetic-convention, missing-data, mixed-coordinate-arithmetic
  • Review algebraic property consistency (NaN masking preserves commutativity, distributivity)
  • Check edge cases: scalar NaN, array NaN, Variable vs Expression paths

🤖 Generated with Claude Code

FBumann and others added 5 commits March 18, 2026 15:34
NaN in multiplicative/divisive constants now masks the term (makes it
absent) rather than raising. NaN in additive constants is treated as 0
(additive identity). NaN in constraint RHS skips the constraint. This
simplifies the API — users no longer need fillna() before every
operation with incomplete data.

Update arithmetic-convention, missing-data, and new
mixed-coordinate-arithmetic notebooks to reflect the new semantics.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Variable.to_linexpr() now treats NaN coefficients as absent in v1,
  consistent with Expression * NaN (was silently filling with 0)
- Restore ValueError for NaN in constraint RHS — silently skipping
  constraints is too dangerous at this API boundary
- Update notebooks to reflect both changes

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
NaN already in the data (user-intentional) still masks silently.
But NaN introduced by coordinate alignment (join="left"/"outer")
now raises ValueError, requiring explicit fill_value=0 or fill_value=1.

This catches the footgun where a partial scaling factor (e.g., rate
covering only some techs) silently masks terms instead of preserving
them with the identity element.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant