From c0bc320c52a8f12a839574efd9fec4f7b9163c84 Mon Sep 17 00:00:00 2001 From: Nick Petalas Date: Fri, 23 Oct 2026 10:36:24 +0100 Subject: [PATCH] feat(research): prove the joint T29 bin-count rectangle --- research/t29-joint-bin-spectrum/README.md | 163 +++++++++++++++++ research/t29-joint-bin-spectrum/SOURCES.json | 30 ++++ research/t29-joint-bin-spectrum/TASK.json | 108 ++++++++++++ research/t29-joint-bin-spectrum/VERIFY.json | 32 ++++ research/t29-joint-bin-spectrum/verify.py | 176 +++++++++++++++++++ 5 files changed, 509 insertions(+) create mode 100644 research/t29-joint-bin-spectrum/README.md create mode 100644 research/t29-joint-bin-spectrum/SOURCES.json create mode 100644 research/t29-joint-bin-spectrum/TASK.json create mode 100644 research/t29-joint-bin-spectrum/VERIFY.json create mode 100644 research/t29-joint-bin-spectrum/verify.py diff --git a/research/t29-joint-bin-spectrum/README.md b/research/t29-joint-bin-spectrum/README.md new file mode 100644 index 0000000000..7a5d311d08 --- /dev/null +++ b/research/t29-joint-bin-spectrum/README.md @@ -0,0 +1,163 @@ +# Exact joint T29 bin-count spectrum + +This contribution was produced by Nick Petalas. Apache-2.0. + +Claim type: scoped constructive proof with exact finite corroboration. In the +historical fixed U8 whole-helper packing model at T=29, let x count +capacity-16 `{9,7}` bins and y count capacity-8 `{4,4}` bins. The exact joint +projection is the full integer rectangle + +```text +4824 <= x <= 12992, 0 <= y <= 8286. +``` + +Every pair in this rectangle occurs. There is no additional coupling or +parity restriction between these two counts. This answers the explicit Open +next question in woklerx's [PR1844](https://github.com/CrocSwap/integer-mult-bounds/pull/1844). +The new result is the joint projection and its inventory-preserving exchange. +The predecessor's one-dimensional x range and starting construction are cited +premises, not new results or audit targets. No kappa improvement is claimed. + +## Model and cited premises + +The capacity quotas at `(8,16,32,48)` are `(19488,12992,12992,12992)`. +The helper demands at widths `(4,5,7,8,9)` are +`(20880,8352,14094,31842,56376)`. The required remainder profile is +`{0:49770,1:4,2:1,48:8689}`. Helpers remain whole. No chronological, +geometric, chart or physical owner constraint is imposed. + +Hanhong Zhao's [PR1160 proof](../t29-capacity-audit/PROOF.md), Lemmas 1-2, +shows that every realization uses tight patterns. Its four remainder-one +bins each contain one four, its remainder-two bin contains at least one +four, and each of its 4,303 full capacity-48 bins contains at least one four. +It also proves the necessary x range. We use these statements as published +premises, without rerunning their pattern census. + +Woklerx's [PR1844 construction](../t29-capacity-projection-spectrum/README.md) +gives a realization for every integer x in that range with y=8286. Write +`x=4824+2t+e`, where e is zero or one. Its groups outside capacity eight +that contain eights are exactly: + +| Capacity | Pattern | Number of bins | Eights per bin | +|---:|---|---:|---:| +| 16 | `{8,8}` | `8168-2t-e` | 2 | +| 32 | `{9,9,8,4}` | 1 | 1 | +| 32 | `{9,8,8,7}` | e | 2 | +| 32 | `{8,8,8,8}` | t | 4 | +| 48 | `{9,9,9,9,8,4}` | 4303 | 1 | + +All other groups are retained unchanged. Exact proof and construction bytes +are pinned as historical provenance in [SOURCES.json](SOURCES.json). + +## Proof of the joint range + +Necessity of the x interval is PR1160's result. Nonnegative counts give +`y>=0`. The forced bins cited above consume at least `4+1+4303=4308` +fours outside capacity eight. The y distinguished bins consume another 2y, +so `2y+4308<=20880`, giving `y<=8286`. This is a new necessary bound on y. + +For sufficiency, start with PR1844's realization for the desired x. The table +contains exactly + +```text +2(8168-2t-e) + 1 + 2e + 4t + 4303 = 20640 +``` + +eights outside capacity eight, independently of x. Replacing an eight in any +one bin by two fours preserves its total width, capacity tag and remainder. +It also preserves its signed price since an eight and two fours both cost +-2 in the inherited price system. Any subset of these 20,640 occurrences +can be replaced, including several occurrences within a bin. + +Choose any desired integer `0<=y<=8286` and put `k=8286-y`. In exactly k +of the starting capacity-8 `{4,4}` bins, replace the two fours by one eight. +In exactly k of the listed external eight occurrences, make the reverse +replacement. There are enough occurrences because `k<=8286<20640`. +The two changes cancel in the four and eight inventories. They leave all +other helper demands, all bin quotas and the entire remainder profile fixed. + +The external move cannot change the number of capacity-16 `{9,7}` bins: +those bins contain no eights, and replacing eights creates only fours. +The capacity-eight move reduces y by exactly k. Thus the result has the +prescribed pair `(x,y)`. This proves sufficiency for every integer pair, +without interpolation or an integral-relaxation assumption. + +The verifier implements the construction using counts of equal patterns. +For each group with n bins and s eights per bin, spending a replacements +converts `floor(a/s)` complete bins, at most one partial bin, and leaves the +rest unchanged. This realizes every integer budget a between zero and ns. +No individual-helper splitting is used. + +## Verification and reproduction + +From the repository root, with CPython 3.12 and the standard library: + +```sh +/Users/nick/git/integer-mult-bounds/.venv/bin/python3.12 -W error -B research/t29-joint-bin-spectrum/verify.py +/Users/nick/git/integer-mult-bounds/.venv/bin/python3.12 -W error -O -B research/t29-joint-bin-spectrum/verify.py +/Users/nick/git/integer-mult-bounds/.venv/bin/python3.12 -B scripts/research_package.py --package research/t29-joint-bin-spectrum +/Users/nick/git/integer-mult-bounds/.venv/bin/python3.12 -B scripts/check_research_commit.py --package research/t29-joint-bin-spectrum --check-source-history +``` + +The direct commands emit `PASS_T29_JOINT_BIN_SPECTRUM` in a generated JSON +receipt bound to every package file's SHA256. They write no files and import +no predecessor executable. The finite checks construct new joint witnesses +and check every capacity, every helper width, every remainder and both named +projections. They test the external pool at every x, every y on four x slices, +and six boundary/interior y values at each remaining x. Local exchange checks +cover every convertible starting shape. Malformed coordinates, missing +compensation, an extra bin and a false projection must reject under normal +and optimized Python. Universal coverage follows from the written exchange +proof; the sampled interior grid is corroboration, not exhaustive pair coverage. + +Local normal and optimized runs each pass 82,138 joint constructions, 8,169 +pool identities, 15 local exchange checks and 11 rejection controls. The two +registered commands took 1.65 and 1.65 seconds on CPython 3.12.15/macOS arm64. +These are check durations, not performance improvement measurements. + +The selected source-binding orientation also passes with +`scripts/research_selected_baseline.py`. No selected operator replay, +predecessor census, Lean or Docker build is run. Independent human and formal +proof-kernel review are unclaimed. + +## Scope and implementation bounds + +The selected-packing card changes to this historical arithmetic side question. +The joint range describes assembly freedom within a known sharp remainder +profile. It supplies no additional recursive saving and makes no claim about +the changed selected #4470 supplier. Physical matching, owner geometry, +chronology, charts, storage, precision and a complete caller remain unpriced. +No further construction expansion follows from this result. + +The verifier has no recursion, subprocesses, network access or generated code. +It retains one construction at a time, with at most 36 pattern groups and +12 widths per group. Its outer loops range over 8,169 fixed x coordinates and +at most 8,287 y coordinates. Each replacement loop has at most four steps. +Functions remain below 60 physical lines; explicit exceptions retain all +contracts under optimization. Warning-strict execution supplies diagnostics. +AST syntax and function-size inspection pass. No standalone linter or static +type checker is installed in the execution environment. +CPython's dynamic objects and integer arithmetic are a managed-runtime +deviation from Power of Ten allocation constraints, bounded here by these +fixed domains. No speed or memory benchmark claim is made. + +## Open next questions + +1. What is the joint projection after also fixing the number of full + capacity-48 `{9,9,9,9,8,4}` bins? This would distinguish the remaining + exchange freedom from a genuinely coupled constraint. Start by retaining + that coordinate in the same four/eight ledger. The present move changes it, + so the rectangle theorem does not settle the three-coordinate problem. +2. Can any realization in this rectangle satisfy one literal historical owner + and chronological constraint? Start with one named owner fixture and its + legal windows. These constraints and their physical prices are absent here. + +## Credit + +Woklerx supplies PR1844's affine family and the explicit joint-projection +question. Hanhong Zhao supplies PR1160's capacity model, tight-pattern theorem +and capacity-tagged realizations. Chafik Boukhalfa supplies the PR795/PR890 +prices and family; Jonathan Capalbo supplies PR942's all-replica relaxation; +woklerx supplies PR992's sharp T29 profile; Hao Yan supplies the PR858 owner +lineage. The new joint-range proof and verifier were produced by Nick Petalas. +All predecessor files and credit lines remain unchanged. diff --git a/research/t29-joint-bin-spectrum/SOURCES.json b/research/t29-joint-bin-spectrum/SOURCES.json new file mode 100644 index 0000000000..453ea3b23f --- /dev/null +++ b/research/t29-joint-bin-spectrum/SOURCES.json @@ -0,0 +1,30 @@ +{ + "schema_version": 1, + "base_commit": "81e9bd69e4a5b00ba9c713a1620e36d576d5522e", + "files": [ + { + "path": "certificates/selected-result.json", + "usage": "provenance", + "git_blob": "c35482cd9035843b15a634cc8276a7e1e40e8395", + "sha256": "6fe0a099b89aa2aed796a13c3a21dafc63f3388ebf397464e44e0451893b9375" + }, + { + "path": "docs/research/agent-task-catalog.json", + "usage": "provenance", + "git_blob": "f126dd2e2dad2b669e75d1d0d0bd05c600725602", + "sha256": "f7a48bfa2659b88378282a8eee7d9fe52cdf505594124c4d1686b58510fffd72" + }, + { + "path": "research/t29-capacity-audit/PROOF.md", + "usage": "provenance", + "git_blob": "9013468c524b9c740de01669d2a246abd8bc763e", + "sha256": "9fc548c21d2d7ee8b23dcb67d9001b8bc604f1e2a7bea32f3303b12e5053a02e" + }, + { + "path": "research/t29-capacity-projection-spectrum/README.md", + "usage": "provenance", + "git_blob": "9e888d164ec097d1b618bfc5329e4f2d3ad6b625", + "sha256": "32ed9e6379d5fe905b9e65f5be3044e3a1f6d2b62d264e3852842faf716528e2" + } + ] +} diff --git a/research/t29-joint-bin-spectrum/TASK.json b/research/t29-joint-bin-spectrum/TASK.json new file mode 100644 index 0000000000..074a8cbc74 --- /dev/null +++ b/research/t29-joint-bin-spectrum/TASK.json @@ -0,0 +1,108 @@ +{ + "schema_version": 1, + "task_id": "selected-packing", + "priority_revision": 7, + "base_commit": "81e9bd69e4a5b00ba9c713a1620e36d576d5522e", + "original_card": { + "id": "selected-packing", + "question": "Does a named residual packing task have meaningful uncovered headroom after its exact fixed-inventory ceiling?", + "starting_points": [ + "docs/research/research-family-status.md", + "certificates/selected-result.json", + "research/staged-fourth-mixture-ceiling/README.md", + "research/staged544-mixed-fourth-entry/README.md" + ], + "methods": [ + "Check a fixed-inventory ceiling and source equivalence before any explicitly bounded residual search.", + "Price a genuinely changed word/interface under a newly applicable model." + ], + "acceptance_gate": "Check live overlap and exact model/source binding first. Distinguish a chronological construction from a pooled optimum. State the applicable fixed-inventory ceiling and remaining headroom; changed words or interfaces require a new fully charged model. Do not launch a residual search without an explicit scope and time cap.", + "first_step": { + "command": [ + "python3", + "-B", + "scripts/research_baseline.py" + ], + "expected": "PASS_RESEARCH_BASELINE_BINDINGS_ONLY", + "inspect": "Read #4091 fixed-word pooled ceiling and #4088 later 1090-call literal fill. Bind the unchanged local invoices/capacities/stock before transferring the ceiling; its earlier 1080-call residual-bin recipe is historical.", + "scope": "Source-binding orientation only; no scientific replay, theorem acceptance or supplier admission. Budget any heavy replay separately." + }, + "first_experiment": "Default to a bounded headroom/source-equivalence check. Only start residual search with an explicit unclaimed domain, time cap and reason the tiny remaining gain matters. Otherwise hand off or select a changed-word mechanism. A changed word or interface needs a fresh cost model, not the old ceiling. Stop at the fixed-inventory cap; do not extend by new random seeds.", + "bottleneck_link": "An actual paid-profile gain can improve the frontier up to the other supplier cap.", + "allowed_variations": [ + "One bounded residual packing instance or exact relaxation.", + "If the coverage law, template or guard changes, derive an applicable fully charged replacement bound instead of applying the incumbent ceiling outside its scope." + ], + "scope_change_triggers": [ + "Rerunning an existing search with only a different seed as a separate mathematical contribution." + ], + "overlap_terms": [ + "T16", + "owner exchange", + "packing", + "4091", + "4088", + "staged-fourth-mixture-ceiling", + "staged544-mixed-fourth-entry" + ], + "existing_checks": [ + { + "scope": "PR #4091: pooled optimum at one-third staging for pinned fixed words; not a chronological fill.", + "evidence": "research/staged-fourth-mixture-ceiling/README.md", + "remaining": "Independent scope/source check; any residual optimization is explicitly bounded and optional.", + "status": "author-checked", + "source_commit": "a29bdd8f56cd18b805917775cdec09ff935eddc4", + "selected_record_sha256": "bb2eaa58647b353f88d0aec2eafb9d30df1be53589231b23e12ceef3e4bad129" + }, + { + "scope": "PR #4088: later 1090-call literal fill improves the earlier 1080-call witness.", + "evidence": "research/staged544-mixed-fourth-entry/README.md", + "remaining": "Complete admission remains separate; old residual-bin instructions must not be copied blindly.", + "status": "author-checked", + "source_commit": "72d7efc181edb21806a878f174847c96dd410ab0", + "selected_record_sha256": "bb2eaa58647b353f88d0aec2eafb9d30df1be53589231b23e12ceef3e4bad129" + } + ] + }, + "scope_status": "changed", + "actual_scope": "Exact joint projection of capacity-16 {9,7} and capacity-8 {4,4} counts in the historical fixed U8 T=29 packing model.", + "scope_change_reason": "Takes the explicit open question in woklerx PR1844 rather than auditing fixed selected invoices or searching a residual packing. The result concerns arithmetic assembly freedom and has no selected-profile gain.", + "bottleneck_link": "Side result: identifies exactly which two inventory coordinates remain independent below physical scheduling. It does not improve the selected construction or its recursive bill.", + "budget_minutes": 60, + "stop_rule": "Stop at the first verified joint-range theorem. Leave extra coordinates and physical owner constraints open.", + "outcome": "completed", + "evidence": "README proof plus standalone exact new joint constructions, complete inventory/remainder/projection invoices and rejecting controls; predecessor tight-pattern theorem and affine family are cited premises.", + "author": "Nick Petalas", + "ai_assistance": "Independent human and formal proof-kernel review are unclaimed.", + "open_questions": [ + { + "question": "Determine the joint projection with the full capacity-48 {9,9,9,9,8,4} count also fixed.", + "why_it_matters": "Distinguishes remaining exchange freedom from a genuinely coupled constraint.", + "first_step": "Retain this third coordinate in the same four/eight exchange ledger.", + "blocked_by": "The present exchange can change that count; no necessity or construction with it fixed is supplied." + }, + { + "question": "Realize one pair under a literal historical owner and chronological constraint.", + "why_it_matters": "The arithmetic rectangle alone cannot establish physical feasibility.", + "first_step": "Name one owner fixture and encode its legal windows before pricing it.", + "blocked_by": "Geometry, charts, windows, restoration and physical costs are absent from this model." + } + ], + "coordination": { + "claim": "https://github.com/CrocSwap/integer-mult-bounds/discussions/1166#discussioncomment-19142736", + "upstream_check_utc": "2026-10-23T09:34:00Z", + "predecessors": [ + { + "pr": 1844, + "author": "woklerx", + "scope": "Affine x family and explicit joint-projection question" + }, + { + "pr": 1160, + "author": "Hanhong Zhao", + "scope": "Tight-pattern theorem and necessary x interval" + } + ] + }, + "claim_type": "scoped constructive proof with finite corroboration" +} diff --git a/research/t29-joint-bin-spectrum/VERIFY.json b/research/t29-joint-bin-spectrum/VERIFY.json new file mode 100644 index 0000000000..73002b75d5 --- /dev/null +++ b/research/t29-joint-bin-spectrum/VERIFY.json @@ -0,0 +1,32 @@ +{ + "schema_version": 1, + "status": "ready", + "python": "3.12", + "requirements": [], + "inputs": [], + "commands": [ + { + "argv": [ + "{python}", + "-W", + "error", + "-B", + "{package}/verify.py" + ], + "timeout_seconds": 120, + "checks": "New joint constructions, full capacity/helper/remainder/projection invoices, all-x pool identities, local exchange invariants and rejecting controls." + }, + { + "argv": [ + "{python}", + "-W", + "error", + "-O", + "-B", + "{package}/verify.py" + ], + "timeout_seconds": 120, + "checks": "New joint constructions, full capacity/helper/remainder/projection invoices, all-x pool identities, local exchange invariants and rejecting controls." + } + ] +} diff --git a/research/t29-joint-bin-spectrum/verify.py b/research/t29-joint-bin-spectrum/verify.py new file mode 100644 index 0000000000..2acdcd58b6 --- /dev/null +++ b/research/t29-joint-bin-spectrum/verify.py @@ -0,0 +1,176 @@ +"""Exact T29 joint-bin constructions. Produced by Nick Petalas. Apache-2.0.""" + +import hashlib +import json +from collections import Counter +from pathlib import Path + + +QUOTAS = {8: 19488, 16: 12992, 32: 12992, 48: 12992} +DEMAND = {4: 20880, 5: 8352, 7: 14094, 8: 31842, 9: 56376} +PROFILE = {0: 49770, 1: 4, 2: 1, 48: 8689} +PRICES = {4: -1, 5: -20, 7: -8, 8: -2, 9: 4} +BONUS = {8: 2, 16: 4, 32: 8, 48: -13} + + +def require(condition, message): + if not condition: + raise ValueError(message) + + +def starting_rows(x): + """The cited affine family in woklerx PR1844, displayed in its README.""" + require(type(x) is int and 4824 <= x <= 12992, "x domain") + t, e = divmod(x - 4824, 2) + return [ + (8, (8,), 11202), (8, (4, 4), 8286), + (16, (7, 9), x), (16, (8, 8), 8168 - 2*t - e), + (32, (5, 9, 9, 9), 8352), (32, (4, 9, 9, 9), 4), + (32, (4, 8, 9, 9), 1), + (32, (7, 7, 9, 9), 4635 - t - e), + (32, (7, 8, 8, 9), e), (32, (8, 8, 8, 8), t), + (48, (), 8689), (48, (4, 8, 9, 9, 9, 9), 4303), + ] + + +def replace_eights(widths, number): + require(0 <= number <= widths.count(8), "replacement capacity") + result = list(widths) + for _ in range(number): # At most four eights in a starting pattern. + result.remove(8) + result.extend((4, 4)) + return tuple(sorted(result)) + + +def witness(x, y): + require(type(y) is int and 0 <= y <= 8286, "y domain") + rows = starting_rows(x) + remaining = 8286 - y + out = Counter({(8, (8,)): 19488 - y, (8, (4, 4)): y}) + for cap, widths, count in rows: # Exactly twelve starting groups. + if cap == 8: + continue + slots = widths.count(8) + spent = min(remaining, count * slots) + remaining -= spent + full, partial = divmod(spent, slots) if slots else (0, 0) + if full: + out[cap, replace_eights(widths, slots)] += full + if partial: + out[cap, replace_eights(widths, partial)] += 1 + out[cap, widths] += count - full - bool(partial) + require(remaining == 0, "conversion pool exhausted") + return {key: count for key, count in out.items() if count} + + +def invoice(entries, x, y): + require(len(entries) <= 36, "pattern capacity") + caps, widths, remainders = Counter(), Counter(), Counter() + score = 0 + for (cap, ws), count in entries.items(): + require(cap in QUOTAS and type(count) is int and count > 0, + "pattern tag or count") + require(len(ws) <= 12 and all(w in DEMAND for w in ws), "width domain") + require(sum(ws) <= cap, "bin overflow") + caps[cap] += count + remainders[cap - sum(ws)] += count + score += count * (BONUS[cap] + sum(PRICES[w] for w in ws)) + for w in ws: + widths[w] += count + require(dict(caps) == QUOTAS, "capacity invoice") + require(dict(widths) == DEMAND, "helper invoice") + require(dict(remainders) == PROFILE, "remainder invoice") + require(score == -112868, "price invoice") + require(entries.get((16, (7, 9)), 0) == x, "x projection") + require(entries.get((8, (4, 4)), 0) == y, "y projection") + + +def check_exchange_types(): + """Check the new local move on every shape used by the construction.""" + shapes = {(cap, ws) for x in (4824, 4825, 12992) + for cap, ws, _ in starting_rows(x) if cap != 8 and 8 in ws} + checks = 0 + for cap, ws in sorted(shapes): # Five shapes, each with at most four eights. + for k in range(ws.count(8) + 1): + changed = replace_eights(ws, k) + require(sum(changed) == sum(ws), "local remainder invariance") + require(sum(PRICES[w] for w in changed) == sum(PRICES[w] for w in ws), + "local price invariance") + before = Counter(ws) + Counter({4: 2*k}) + after = Counter(changed) + Counter({8: k}) + require(before == after, "paired helper invariance") + require(sum(changed) <= cap, "local legality") + checks += 1 + return checks + + +def rejection_checks(): + controls = 0 + for x, y in ((4823, 0), (12993, 0), (4824, -1), (4824, 8287), + (True, 0), (4824, True), (4824.0, 0), (4824, 0.0)): + try: + witness(x, y) + except ValueError: + controls += 1 + else: + raise ValueError("invalid projection accepted") + for variant in range(3): + bad = witness(4825, 0) + if variant == 0: # A missing compensation violates the helper invoice. + key = next(key for key in bad if key[0] == 16 and 4 in key[1]) + cap, ws = key + bad[key] -= 1 + changed = list(ws) + changed.remove(4) + changed.remove(4) + changed.append(8) + bad[cap, tuple(sorted(changed))] = 1 + elif variant == 1: + bad[8, (8,)] += 1 + else: # The invoice holds, but the claimed projection is false. + bad = witness(4825, 1) + bad = {key: n for key, n in bad.items() if n} + try: + invoice(bad, 4825, 0) + except ValueError: + controls += 1 + else: + raise ValueError("corrupted construction accepted") + return controls + + +def run(): + digest = hashlib.sha256() + constructions = 0 + pool_checks = 0 + for x in range(4824, 12993): + pool = sum(n * ws.count(8) for cap, ws, n in starting_rows(x) if cap != 8) + require(pool == 20640 and pool >= 8286, "uniform conversion pool") + pool_checks += 1 + ys = (range(8287) if x in (4824, 4825, 12991, 12992) + else (0, 1, 3982, 3983, 8285, 8286)) + for y in ys: + entries = witness(x, y) + invoice(entries, x, y) + encoded = [[cap, list(ws), n] for (cap, ws), n in sorted(entries.items())] + digest.update(json.dumps([x, y, encoded], separators=(",", ":")).encode()) + constructions += 1 + require(2*8286 + 4 + 1 + 4303 == DEMAND[4], "sharp four budget") + return { + "status": "PASS_T29_JOINT_BIN_SPECTRUM", + "x_range": [4824, 12992], "y_range": [0, 8286], + "pool_checks": pool_checks, "joint_constructions": constructions, + "local_exchange_checks": check_exchange_types(), + "rejection_controls": rejection_checks(), + "constructions_sha256": digest.hexdigest(), "new_kappa": False, + } + + +if __name__ == "__main__": + result = run() + here = Path(__file__).resolve().parent + result["source_sha256"] = { + name: hashlib.sha256((here / name).read_bytes()).hexdigest() + for name in ("README.md", "SOURCES.json", "TASK.json", "VERIFY.json", "verify.py") + } + print(json.dumps(result, sort_keys=True))