Repository navigation
Conversation
…it, and a file names its value instead of neutral The content of #880 without `Program.reads` and its `WindowPredicate`: the tool that builds the model checks a missing row where it reads it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A2crTbivTGt2WzGpTebdmf
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A2crTbivTGt2WzGpTebdmf
Documentation build overview
45 files changed ·
|
…again `refused` keeps the check a tool makes from the table alone. A table with gaps on purpose is declared `absent`, and an assumption says where it must have a row. The two load refusals that rely on a row at every coordinate come back, without `neutral` in their rewrites, and the seven PyPSA module sizes are declared `missing: absent`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A2crTbivTGt2WzGpTebdmf
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A2crTbivTGt2WzGpTebdmf
missing: neutral is gone
…ariable's missing needs no alias of its own `RelationMissing` was the same literal as `MissingReading`, and `VariableReading` named the one word `absent`. Both are gone. The two checks on a variable's number are one validator, and the divisor walk drops a guard no file reaches. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A2crTbivTGt2WzGpTebdmf
…ares them `RelationMissing`, `ParameterMissing` and `VariableMissing` type the `missing:` of each entry, as `VariableDomain` and `ObjectiveSense` type their keys. `MissingReading` and `Missing` are gone, and the null refusal no longer says "reading". Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A2crTbivTGt2WzGpTebdmf
…missing is never None `GivenTargets.parameters` holds `GivenParameter`: the dims, the dtype and the description, which is what a reading file states. `Parameter` is a declaration only, and its `missing` has no `None` for the case where another file owns it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A2crTbivTGt2WzGpTebdmf
…be any whole number Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A2crTbivTGt2WzGpTebdmf
… claude/bold-wozniak-jewqij
|
@FBumann how does this PR link to the one I previously commented on (#869)? It was closed in preference for #880 which was closed in preference for this PR. These trails of superseded PRs doesn't feel particularly productive. Would be better to hash out these in single issues first, then open a single PR following that discussion. |
|
I was trying to improve the missing: feature and was not satisfied with the solution I came up with. Your comments on it and me not being satisfied lead to me closing them and rethinking the concept of missing data. This new PR now removes the Sorry for the churn. Im not trying to merge this quickly if you or @FabianHofmann want to review it and have the time, I would appreciate it. |
brynpickering
left a comment
There was a problem hiding this comment.
Having the ability to define a value does seem neater, and nice to force the user to think about what is being filled in on absence rather than doing it automatically for them.
Interesting though that all missing are 0 in the examples. I would have expected 1 for efficiency / p_max_pu.
FYI: In Calliope, we were using this value option only and it caused us some issues. Namely, when applying a variant switching from capacity expansion to dispatch (see calliope-project/calliope#862 (comment)). I think this is a non-issue in mathspec as we can use missing: absent rather than a value.
Comments are about:
- switching some pypsa entries from having
0missing toabsent - adding
missinginformation elsewhere (e.g.p_max_pu).
Second point might not be worthwhile as pypsa always pre-fills them with defaults (I guess that's why most params don't have a missing at present).
| description: a given output schedule; a generator without one has no row here | ||
| dims: [scenario, snapshot, generator] | ||
| missing: neutral | ||
| missing: 0 |
There was a problem hiding this comment.
should these _set options always be absent? I.e., they only get applied if they are present so if they are missing that should be considered an absence rather than being filled in.
There was a problem hiding this comment.
Yes. But this result in a sparser model, which is probably not what pypsa does today.
Your pointing at what the role of these files is: Replicating pypsa 100%, or showing how pypsa would be implemented in mathspec. We settled on replicating 100% for now, so this should probably be 0, although absent is what we ultimately want.
There was a problem hiding this comment.
I dived in a bit and I think we should just change it to the correct one (absent here). Not sure what pypsa does exactly.
There was a problem hiding this comment.
OK, agreed that replicating 100% is our goal. I leave @FabianHofmann to review these entries based on what happens in pypsa currently.
There was a problem hiding this comment.
@brynpickering I move those pypsa changes into a stacked PR #912. We can fold them in here of you prefer.
| missing: neutral | ||
| missing: 0 | ||
| Generator_operational_limit_weight: | ||
| description: one where the generator is in the row's set — data prep; one outside it has no row | ||
| dims: [scenario, global_constraint, generator] | ||
| missing: neutral | ||
| missing: 0 | ||
| Generator_tech_capacity_weight: | ||
| description: >- | ||
| one where the generator is in the row's carrier-and-bus set — data | ||
| prep; one outside it, or one that does not stand in the row's | ||
| `investment_period`, has no row | ||
| dims: [global_constraint, generator] | ||
| missing: neutral | ||
| missing: 0 |
There was a problem hiding this comment.
This will include all generators in the global expression constraint, with many possibly included with a weight of 0. If missing: absent then they just wouldn't be included in the sum, right? Same result, but perhaps the latter is quicker to parse and add to the solver?
There was a problem hiding this comment.
Yes. But this results in a sparser model, which is probably not what pypsa does today.
Your pointing at what the role of these files is: Replicating pypsa 100%, or showing how pypsa would be implemented in mathspec. We settled on replicating 100% for now, so this should probably be 0, although absent is what we ultimately want.
There was a problem hiding this comment.
I dived in a bit and I think we should just change it to the correct one (absent here). Not sure what pypsa does exactly.
…ponent lacks is absent Every `*_set` parameter is read only under its own presence test, so absent reads it as missing: 0 did. Each global-constraint weight is a factor inside a sum, where absent leaves the summand out instead of adding zero times it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01D2S9sN1jLGGMCCwqwVyGD6
…at a component lacks is absent" This reverts commit 23e715e. The change moves to a pull request stacked on this one. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01D2S9sN1jLGGMCCwqwVyGD6
…ut is absent rather than zero, and a committable extendable unit is assumed to have its big-M (#912) * docs(pypsa): a PyPSA schedule, weight or limit that the data leaves out is absent rather than zero Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A2crTbivTGt2WzGpTebdmf * docs(changelog): link the line to its pull request Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A2crTbivTGt2WzGpTebdmf * docs(pypsa): regenerate the gallery pages on the merged base The big-M assumptions print their condition's indices sorted by name, as #911 prints every derived frame. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01D2S9sN1jLGGMCCwqwVyGD6 --------- Co-authored-by: Claude <noreply@anthropic.com>
…ards loads under missing: 0, and so does a variable number outside its bounds (#942) A guard spares a divisor of a missing: 0 parameter where it is false at every missing row: a conjunct of the constraint's where, of a case's when, or of the mask of a missing: absent variable that multiplies the quotient. A variable's missing: number no longer has to lie inside its literal bounds. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YZ68heVBrHxKUKsXxqJrXX
… builds The load check reads only the shape of a divisor, so a zero that numbers make loads: missing: 1 with 1 - c, constants that cancel around a missing: 0 parameter, a case that holds 1 - 1, or a present row that is 0. absence.md now states, as a rule every engine follows, that such a divisor is refused when the data is attached, and that a reported quotient stays absent instead. reading.md links the rule where an engine reads what the data must satisfy. The new section: 12 sentences, median 10 words, none over 25. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YZ68heVBrHxKUKsXxqJrXX
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015MPR9MKVJ9XwN68qHLrX5g
…used at load The check that refuses a divisor a missing row of a missing: 0 parameter makes 0 now also names an edge=0 shift as a cause, through the same walk. The tests and the rule in expressions.md come from #889. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01W7L7U7Av5v8wnKV6eDpXfi
Note
The following content was generated by AI.
What this changes
missing: neutralis gone. A file writes the value that a missing row reads as, such as0orfalse, and a variable writes the number it stands as outside its mask.refusedstill needs a row at every coordinate. A divisor that the file itself makes 0, through amissing: 0row or anedge=0shift, is refused at load.This PR supersedes #889, which stays open as a draft. #942 and #912 were squash-merged into this branch earlier, and each kept its own review.
How to review
src/missing:vocabulary, the divisor check at load in_divisors.py, and a variable's numberdocs/reference/edge=0rule in expressions.mdtests/, hand-writtenedge=0cases from #889, and theneutralcases that became0andfalseexamples/,docs/examples/neutralbecomes0orfalse, and #912 picks a reading for each PyPSA parametertests/expand/What a missing row reads as
refused,absentor a value.missing: 'neutral'gets the error for a closed vocabulary, which names the valid readings.offset=/window=amount,wherecomparison, bare flag, bare number or label), and one column each forabsentand for a value. Underrefused, the table has a row at every combination of labels, so no position reads a missing row.missing: 0parameter can make 0 is refused at load. The check follows a factor, the numerator of a quotient, the base of a power, and the operand of a negation, asum, a window, a shift, a lookup or a named expression. It also follows each region of a case. An addition can be 0 only where every term can, soc + 1loads. A guard that is false at every missing row spares the divisor (fix(language): a divisor that a where, a case or a masked variable guards loads under missing: 0, and so does a variable number outside its bounds #942): a conjunct of the constraint'swhere, of a case'swhen, or of the mask of amissing: absentvariable that multiplies the quotient.missing: 1withx / (1 - c),x / ((c + 1) - 1)undermissing: 0, a case that holds1 - 1, or a present row that is0. A reported quotient stays absent where its divisor is0. specsolve already refuses each of these, but names the wrong parameter for a case (a divisor that a case makes 0 is refused as a parameter with missing rows, though the parameter has every row fluxopt/specsolve#2064).missing:takesabsentor a finite number. The variable needs awhere:. The number must fit the domain of the variable:0or1forbinary, an integer forinteger. The bounds hold only inside the mask, so the number may lie outside them (fix(language): a divisor that a where, a case or a masked variable guards loads under missing: 0, and so does a variable number outside its bounds #942). A curve gate takes0for no curve, or1for an ungated curve, which is also whatabsentgives.A divisor that an edge=0 shift makes 0 (from #889)
p / shift(c, along=t, offset=1, edge=0)loaded and divided by 0 at the edge, whatever the data held. It is now refused at load, with the rewrite named:#889 added a second walk,
_zero_in, in_expression_resolver.py. Here the rule is one more case in this PR's walk:_zero_where_missingin_divisors.pyis now_zero_in, and it returns the cause of the zero, either amissing: 0parameter or anedge=0shift. So the shapes it follows are the same for both causes: a factor, a numerator, a base, the operand of a negation, a sum, a window, a shift, a lookup or a named expression, a region of a case, and a sum only where every term can be 0. No guard spares anedge=0zero. expressions.md states the rule beside the other rules for/, and links to absence.md for themissing: 0case.Reproduced first. #889's 15 tests were copied onto this branch before the fix. The 12 refusal cases failed with
DID NOT RAISE LanguageError, and the 3 cases that load passed.Mutation. With the
edge=0case deleted from_zero_in, the full suite gives 12 failures, all intest_a_divisor_a_shift_fills_with_zero_is_refused. The other branches of the walk are shared with themissing: 0check and are in the table below. The run used the same precautions as below.A table with gaps on purpose
absence.md has a new section for a table that has rows only where the model needs them. The file declares it
absent, and an assumption says where it must have a row:The tool that attaches the data checks the assumption, so a missing
p_max[gas]is an error, and a missingp_max[old]withcapacity[old] = 0is not.Migration
The migration writes
0for afloatorintparameter andfalsefor aboolparameter. It coversexamples/,tests/expand/and the golden fixture. The seven PyPSA*_nom_modparameters are declaredmissing: absentinstead: "no value means the build is continuous".Generator_p_nom_modis a divisor in*_maint_modstatus_*, and before #942missing: 0was refused there. Those rows are guarded by*_p_nom_mod > 0, somissing: 0loads now, and #912 decides which reading the files take. The two load refusals that need a row at every coordinate stay, and their rewrites no longer nameneutral:intorstrname in awhere, on arefusedparameter;refusedvalues table of a curve withpoints:.The schema, the golden output, and the gallery and notation pages are regenerated.
#912 judges each PyPSA
missing:parameter. It declares the*_setparameters and the global-constraint weightsmissing: absent, as asked in review, and more besides. #942 fixes two load checks above that refused valid files.Breaking changes, for the release notes
missing: neutralis refused, on a parameter and on a variable. Write0orfalseon a parameter, and0on a variable.edge=0shift can make 0 is refused at load. Writeedge='wrap', or anotheredge=value.neutral, a missing row there was refused when the data was attached. Undermissing: 0it reads as0, with no refusal:x <= capbecomesx <= 0. To keep a refusal, do one of these:where: cap, which has no row where the data has none. Every such use inexamples/pypsa.yamlalready does this;assumptions: {cap_given: {holds: "cap", where: <where it is read>}}, and the tool that attaches the data refuses a missing row there.wherecomparison. Underneutral, a comparison at a missing row was false. A value compares as itself, socost < 1is true at a missing row undermissing: 0. Every comparison on a migrated parameter inexamples/is> 0, which is false both ways. To keep the old reading, writewhere: cost AND cost < 1.missing:is named after the entry that declares it:RelationMissingisLiteral['refused', 'absent'],ParameterMissingisRelationMissing | bool | float, andVariableMissingisLiteral['absent'] | float.MissingReadingandMissingare gone. A given parameter is aGivenParameter(dims,dtype,description) underprogram.given.parameters, soParameter.missingis neverNone.Why
neutralread a missing row as0orfalse. That contributes nothing only in a sum. As a factor it removed the whole term, as an exponent it gave1, and as anoffset=it read the coordinate itself. A value says which number the file means.Verification
Gates on the head,
216660d, which adds theedge=0case from #889:pixi run lintpasses.pixi run testpasses: 3122 passed, 1 skipped.pixi run docs-buildpasses.compile-texdid not run, because the container cannot download the TeX bundle. CI runs it.Gates on
021e433, which mergedmain(0.4.0, #931, #946, #947) into this branch: lint and docs-build passed, and test gave 3107 passed, 1 skipped. The merge had two conflicts. InCHANGELOG.md, the lines that 0.4.0 released left "Upcoming version", and the #942, #912 and #894 lines stay. Inreading.md, this branch's paragraph on zero divisors and #947's paragraph on relation comparisons both stay.Earlier gates. On
9fb7dea, which mergedmainwith #898, #899, #900, #901, #904, #907, #909 and #911: lint and docs-build passed, and test gave 3077 passed, 1 skipped. #942 holds its own gates and mutation table. On547b034: test gave 3097 passed, 1 skipped.Mutation table on
2121ec7.b1869aamerges the two checks on a variable's number into one validator; itswhere:and domain branches were deleted again onb1869aaand are caught by the same tests. A script deleted each guard and ran the full suite. It restored the file withgit checkout --, dropped__pycache__before and after each run, and the tree came back clean. #942 later removed the bounds rows of the last line.test_a_divisor_that_reads_a_missing_row_as_zero_is_refusedmissing: 0parameter can make a divisor 0sum, a window or a shift[negated],[summed],[shifted][through-a-named-expression],[in-a-case],[looked-up-in-every-addend][a-factor]/[a-numerator]/[a-base]/[in-a-case]examples/pypsa.yamlno longer loads:CVaR_defdivides by1 - CVaR_alpha, and the mutated check refuses that divisorwhere:test_a_rule_decided_without_data[zero-without-a-mask]test_a_variable_missing_that_reads_data_is_refused[a-flag],[an-infinity]binary/integer/ belowlower/ aboveuppertest_a_variable_missing_outside_what_the_variable_takes_is_refused[binary]/[integer]/[below-lower]/[above-upper]edge=0case of_zero_in(on216660d)test_a_divisor_a_shift_fills_with_zero_is_refusedCoverage moved. The
neutralcases became0andfalsecases.an-int-under-neutral,neutral-on-a-labeland theneutralreading of the exclusivity tests are removed, and their value counterparts stay.Not done
neutral, and accept a number as themissing:of a variable. It already reads a value on a parameter. That is a separate PR in specsolve.sum_backstops short at the edge and sums fewer terms. That is not a 0 the file puts in, so it is not refused.missing: <parameter>on a variable is not added. A named expression withcases:already gives the variable inside the mask and the data outside it.neutral. They are history.🤖 Generated with Claude Code
https://claude.ai/code/session_01W7L7U7Av5v8wnKV6eDpXfi