Bound each integer symbol to the range of its type - #98
Draft
HowardvanRooijen wants to merge 1 commit into
Draft
HowardvanRooijen wants to merge 1 commit into
HowardvanRooijen wants to merge 1 commit into
Conversation
A short, int, long or DateTime symbol travels through Z3 as an unbounded integer, and nothing told Z3 the range. A constraint no value of the type could satisfy - a short equal to 40000, a DateTime after MaxValue - still had a model, and the failure came on the way out, as an OverflowException from the checked read. And optimising such a symbol with no other bound returned an arbitrary value, measured as zero, rather than the extreme of the type. AssertConstraints now walks the environment and asserts low <= symbol <= high for every scalar whose type has a range, descending into nested objects. So the impossible constraint is unsatisfiable, which is the true answer, and the optimisation returns short.MaxValue, int.MaxValue and so on. This also closes the int route #87 called unreachable, which #76 had since opened. Only scalars are bounded. A collection's length is not known when the constraints are asserted, and bounding every element would take a quantifier that can cost Z3 its completeness, so an element keeps the checked read and the loud OverflowException. Enum membership is a separate rule, raised as #97. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #87.
The defect
A
short,int,longorDateTimesymbol travels through Z3 as an unbounded integer, andnothing told Z3 the range of the type. A constraint no value of the type could satisfy still had a
model, and the failure came on the way out:
There is no
shortequal to 40000. The theorem is unsatisfiable, and that is what should come back.#87's own table needs one correction. It recorded the
introute as unreachable, because acomparison against a
longvariable fell into the conversion catch-all. #76 made that widening ano-op and the route opened: measured before this change,
t.X1 == 3_000_000_000Lon anintsymbol solves and the read throws
Z3Exception: Numeral is not an int. Recorded on the issue.Measured before, twelve shapes. The other finding worth stating: optimising a symbol with no
other bound returned an arbitrary value - zero, every time - because Z3 reports an unbounded
objective and supplies whatever model it likes. Maximising a
shortgave 0.The change
The bounds are asserted alongside the constraints.
AssertConstraintsnow walks theenvironment and, for every scalar symbol whose type has a range -
short,int,long,DateTimein ticks - assertslow <= symbol <= high, descending into nested objects. An enumis its underlying type and is bounded as one.
short == 40000(via anintvariable)OverflowExceptionon the readint == 3_000_000_000LZ3Exception: Numeral is not an intDateTime > DateTime.MaxValue,< DateTime.MinValueOverflowExceptionfrom the #83 guardshortwith no other bound032767int/ minimise along02147483647/-9223372036854775808DateTime0001-01-019999-12-31T23:59:59.9999999Z/0001-01-01short == short.MaxValue,== short.MinValueDateTime[]element> DateTime.MaxValueOverflowExceptionnaming the symbolOnly scalars are bounded, deliberately. A collection is an array from
Intto the elementsort; its length is not known when the constraints are asserted - it comes from the instance when
the solution is read - and bounding every element would take a quantifier, which can cost Z3 its
completeness. So an element keeps the checked read from #63 and the guard from #83, and a value
outside its type is loud rather than wrong. The last row of the table, and the two element tests,
hold that in place; the comments on both reads now say which case each is for.
The bounds are not logged.
TheoremCompositionTestscounts logged lines, and the log is forthe constraints the caller wrote.
No existing test changed its answer. Sudoku, the shipping and oil problems, every
optimisation test - all pass unchanged, because every one of them bounds its symbols itself, as a
caller normally does. The bounds only matter at the edges, which is where they were missing.
Tests
270 → 281. Three pins rewritten from "throws" to "unsatisfiable", and a new
SymbolBoundsTests.Optimize_ShortSymbolMaximised_ReturnsShortMaxValue/..Minimised..,..Int..,..Long..,..DateTime..×2Solve_IntSymbolConstrainedBeyondIntRange_IsUnsatisfiableSolve_ShortSymbolAtBothEndsOfItsRange_IsSatisfiableOptimize_ShortSymbolInANestedObjectMaximised_ReturnsShortMaxValueTryOptimize_ShortSymbolConstrainedOutsideItsRange_ReturnsFalseSolve_ShortSymbolConstrainedOutsideShortRange_IsUnsatisfiable,Solve_DateTimeSymbolConstrainedBeyondMaxValue_IsUnsatisfiable,..BelowMinValue..Solve_DateTimeArrayElementConstrainedBeyondMaxValue_ThrowsOverflowExceptionNamingIt(inCollectionSymbolTests)Mutation results
DateTimereturns tick 0, which isMinValue, and the inclusive-ends test is satisfiable with or without the boundsshortDateTimeNew issue
#97 - an enum symbol is bounded as its underlying integer, not to the members of the
enum: maximising a
DayOfWeeksymbol now returns(DayOfWeek)2147483647. Before this change itreturned
Sundayonly because Z3 supplied zero for an unbounded objective; nothing was respectingthe enum either way. A different rule - membership, not range - with a
[Flags]question attached,so raised rather than folded in.
Verification
dotnet build solutions/Z3.Linq.slnx -c Release- clean,TreatWarningsAsErrorsanddocumentation generation on
./build.ps1 -Configuration Release- 46 tasks, 0 errors, 0 warnings_ => nullnever runs, and SymbolType only ever sees a property or a field. Both are there for exhaustiveness, and both count as uncoveredRelease note
Releases remain on hold under #60 until Microsoft.Z3 5.x reaches nuget.org, so this reaches
mainbut not consumers. Nothing about the hold changes.