Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,47 @@ What changed in `packvium` on PyPI, release by release. The format follows
[Keep a Changelog](https://keepachangelog.com/1.1.0/) and this project adheres to
[Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [1.7.0]

The reference validator ships in the package, `quality` looks ahead in its block solver, and a
request built from objects is refused as its JSON would be. No request or result field changes.

### Added

- **`packvium.validate(request, result)`, `packvium.Issue` and `packvium.MalformedResult`.** The
reference validator over the JSON documents: a list of `Issue` (`code`, `detail`), empty when
the result is valid, and `MalformedResult` for a result of the wrong shape. It shares no code
with the engine it checks.

### Changed

- **A request built from objects is refused as JSON is.** A model constructor that
refuses a value raises `InvalidRequestError` (code, reason, field `""`), a subclass of the
`ValueError` it raised before, and a mistyped value is still a `TypeError` too. `Packer.pack`
checks the objects with the JSON rules before solving. `FixedPlacement` with
`container_instance` below 1 is now refused by `pack` as `invalid_fixed_placement`, not by its
constructor.
- **Every schema enum value is refused as `not_allowed`.** A value outside its list in
`allowed_rotations`, the output and dimensional-weight units, `shape_type`,
`ground_contact_rule` or `configuration.solvers` is `invalid_request` with reason `not_allowed`
at its JSON Pointer, where some raised a `KeyError`. **`ticks` as an output unit is now
refused**; the schema never listed it.
- **`quality` looks ahead in the block solver.** Each round completes its leading
blocks greedily and commits the best, so the answer is never worse than the greedy pass. On our
benchmark 87 of 89 answers improved and none got worse, at 6% more CPU. Under a tight effort
budget the block start can now report `truncated` / `effort_limit`. `fast` and `balanced` are
unchanged.
- **A `nesting_height` not below the item's height is refused as `invalid_value`** at its
pointer, and with no `output` block the result is written in `units.length`'s schema code.

### Fixed

- **A tall stack no longer raises `RecursionError`**: the load rules walk stacks iteratively.
- **The covered ground-contact rule counts only supporters with positive area**, so an item no
longer rests on a box it touches only at an edge.
- **A quantity past 2^53 − 1 on the object path is refused** instead of hanging the solver.
- **A route violation names its violators in sorted order.**

## [1.6.0]

Boxes cut to measure, flat bags, stacking groups and a volume-first objective. A request that
Expand Down
2 changes: 1 addition & 1 deletion CITATION.cff
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ type: software
authors:
- name: "Packvium contributors"
website: "https://packvium.com"
version: "1.6.0"
version: "1.7.0"
license: MIT
url: "https://packvium.com"
repository-code: "https://github.com/toxakara/packvium-python"
Expand Down
53 changes: 41 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ coordinates and rotations for each item, with a reason for anything that did not
Full documentation, the constraint reference and benchmarks live at
[packvium.com](https://packvium.com).

> **Version 1.6.0 — the public API is frozen.** Field names, status codes and the
> **Version 1.7.0 — the public API is frozen.** Field names, status codes and the
> objective vector do not change without a major version, so any `1.x` is a safe upgrade
> from any earlier `1.x`.
> Read [docs/GUARANTEES.md](https://github.com/toxakara/packvium-python/blob/main/docs/GUARANTEES.md) before relying on a result.
Expand Down Expand Up @@ -96,8 +96,9 @@ config = PackingConfig.balanced(

The result says what stopped the search: `result.termination.code` is `complete`,
`effort_limit` (reproducible), or `time_limit` (not reproducible).
`packvium.artifacts.replay_level(result.to_dict())` gives the same verdict as `exact` or
`not_guaranteed`. `reproducibility.py` shows four simulated machines giving four
`replay_level(result.to_dict())`, imported with `from packvium.artifacts import
replay_level` (a submodule `import packvium` does not load), gives the same verdict as
`exact` or `not_guaranteed`. `reproducibility.py` shows four simulated machines giving four
different answers under a clock, and one answer under a budget.

## Solver profiles
Expand All @@ -112,9 +113,14 @@ tries:
| `quality` | More solvers and orderings, a beam over container choices, and exact search for small orders. Slower. |
| `exact_small` | Bounded exact search when the order has at most `exact_item_limit` items (7, counting quantity); otherwise the same search as `balanced`. |

`PackingConfig.fast()`, `.balanced()`, `.quality()` and `.exact_small()` default to time
limits of 0.2, 1, 5 and 10 seconds; a JSON request defaults to 1 second whatever the
profile. An `effort_budget` makes any of them reproducible.
The JSON path and the `PackingConfig` constructors share a profile's solvers but not all of
its defaults. A JSON request defaults to 1 second, 8 item orderings (1 under `fast`) and
`alternatives: 3` (ranked answers, the winner included) whatever the profile.
`PackingConfig.fast()`, `.balanced()`, `.quality()` and `.exact_small()` default to 0.2, 1, 5
and 10 seconds; `.fast()` keeps 1 ranked answer, and `.quality()` runs 24 orderings and keeps
5, so `"solver_profile": "quality"` in JSON searches
less than `PackingConfig.quality()` unless you set `multi_start_orders`, `alternatives` and
`time_limit_ms` to match. An `effort_budget` makes any of them reproducible.

## Trucks, axles and delivery routes

Expand All @@ -130,8 +136,8 @@ A container can describe a vehicle, not only a box:

After the solve, `safe_route_removal_order` gives an unloading order stop by stop,
`replay_loading_order` checks the reverse as a loading order, and `placement_reachability`
says what can be reached when the doors open. An item the axle ratings cannot carry is
reported as `no_feasible_placement`, not as an axle problem. See `trucking.py`.
says what can be reached when the doors open. An item the axle ratings cannot carry even
alone in the vehicle is reported as `axle_overloaded`. See `trucking.py`.

## Errors

Expand Down Expand Up @@ -162,6 +168,27 @@ raise `UnknownObjectiveError` or `InvalidDirectionError`). A request that is
valid but does not fit completely is not an error: the result lists what was left out, and
why, in `unpacked_items`. `errors.py` turns all of these into HTTP responses.

## Checking a result

*New in 1.7.0.*

`packvium.validate` checks a result against the request it answers, from the JSON documents
alone: geometry, support and load, accounting, the result's facts and its score. It is the
reference validator every Packvium package exports a port of, so they all return the same
issues, code and detail:

```python
from packvium import MalformedResult, pack_from_dict, validate

issues = validate(request, pack_from_dict(request)) # [] -- valid
for issue in issues:
print(issue.code, issue.detail) # e.g. collision cube#1 with cube#2 in box#1
```

It shares no code with the engine, so it judges any engine's result, not only this package's. A
result without the documented shape raises `MalformedResult`, whose `kind` is `contract` or
`shape`.

## Examples

Runnable, in [`examples/`](https://github.com/toxakara/packvium-python/tree/main/examples),
Expand All @@ -177,7 +204,7 @@ in `constraints.py` and `limits.py` have failed you.
| File | What it shows |
| --- | --- |
| [`basic.py`](https://github.com/toxakara/packvium-python/blob/main/examples/basic.py) | The smallest useful call: items in, placements out — and the three details in it that are easy to miss. |
| [`objectives.py`](https://github.com/toxakara/packvium-python/blob/main/examples/objectives.py) | All six objectives on scenes where they genuinely disagree, including the rate card that makes the heavier shipment the cheaper one. |
| [`objectives.py`](https://github.com/toxakara/packvium-python/blob/main/examples/objectives.py) | Six of the seven objectives (all but `maximum_volume`) on scenes where they genuinely disagree, including the rate card that makes the heavier shipment the cheaper one. |
| [`constraints.py`](https://github.com/toxakara/packvium-python/blob/main/examples/constraints.py) | Upright-only, floor-only, non-stackable, top-load limits, and tags that keep two items out of the same box — plus how to read the reason an item was refused. |
| [`limits.py`](https://github.com/toxakara/packvium-python/blob/main/examples/limits.py) | A courier fleet: a van with a fridge unit and wheel arches as obstacles, an item cap per bike, one dangerous-goods item per van, frozen goods only in a refrigerated vehicle, and a cap on vehicles. |
| [`units.py`](https://github.com/toxakara/packvium-python/blob/main/examples/units.py) | Why there are no floats anywhere: fractional inches, exact ticks, and the one-tick difference between a fit and a refusal. |
Expand Down Expand Up @@ -261,7 +288,9 @@ Python 3.9 or newer. No dependencies.
## The Packvium family

One request and result contract, implemented independently in four engines (Rust,
Python, PHP, JavaScript) and held to identical placements on a shared fixture set.
Python, PHP, JavaScript) and checked against each other on a shared fixture set. Python and
PHP are held to byte-identical canonical results; Rust and the JavaScript fallback are held
to independent validity and a per-fixture objective floor.
Pick the package for your stack; mixing them in one system is safe.

Documentation, the constraint reference and the benchmarks are at
Expand All @@ -271,7 +300,7 @@ Documentation, the constraint reference and the benchmarks are at
| --- | --- | --- |
| Python — [`packvium`](https://pypi.org/project/packvium/) | `pip install packvium` | [packvium-python](https://github.com/toxakara/packvium-python) |
| PHP — [`packvium/packvium`](https://packagist.org/packages/packvium/packvium) | `composer require packvium/packvium` | [packvium-php](https://github.com/toxakara/packvium-php) |
| Rust — [`packvium`](https://crates.io/crates/packvium) | `packvium = "1.0"` | [packvium-rust](https://github.com/toxakara/packvium-rust) |
| Rust — [`packvium`](https://crates.io/crates/packvium) | `packvium = "1.7"` | [packvium-rust](https://github.com/toxakara/packvium-rust) |
| Node.js — [`@packvium/engine`](https://www.npmjs.com/package/@packvium/engine) | `npm install @packvium/engine` | [packvium-node](https://github.com/toxakara/packvium-node) |
| Browser / WebAssembly — [`@packvium/browser`](https://www.npmjs.com/package/@packvium/browser) | `npm install @packvium/browser` | [packvium-wasm](https://github.com/toxakara/packvium-wasm) |
| PHP FFI bridge — [`packvium/native-bridge`](https://packagist.org/packages/packvium/native-bridge) | `composer require packvium/native-bridge` | [packvium-php-bridge](https://github.com/toxakara/packvium-php-bridge) |
Expand All @@ -293,7 +322,7 @@ CodeMeta form.
@software{packvium_python,
author = {{Packvium contributors}},
title = {Packvium for Python},
version = {1.6.0},
version = {1.7.0},
license = {MIT},
url = {https://packvium.com}
}
Expand Down
2 changes: 1 addition & 1 deletion codemeta.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"@type": "SoftwareSourceCode",
"name": "Packvium for Python",
"description": "Deterministic 3D bin-packing and cartonization engine for pallets, containers, trucks and shipping cartons. Exact integer geometry, reproducible results.",
"version": "1.6.0",
"version": "1.7.0",
"license": "https://spdx.org/licenses/MIT",
"author": [
{
Expand Down
25 changes: 13 additions & 12 deletions docs/COMMERCE-API.md
Original file line number Diff line number Diff line change
Expand Up @@ -160,9 +160,11 @@ may carry only `tariffs`. Field-level rules:
than leaving it to be inferred from which fields the payload happens to carry.
- An omitted optional field and an explicit JSON `null` mean the same thing: absent.
Writing `"minimum_charge_minor": null` is exactly writing nothing.
- Every integer is exact and, except where a model explicitly allows zero, positive.
The per-field bounds are the ones the models already enforce — see
CATALOG-VERSIONING.md and POLICY-RULES.md.
- Every integer is exact. Dimensions, an item's `weight_g`, `max_payload_g`,
`max_stack_height_mm` and `dimensional_weight_divisor` must be positive; `effective_at`,
`published_at`, `cost_minor`, `minimum_charge_minor`, `fuel_surcharge_permille`,
`flat_charge_minor`, `permille_of_base` and each zone rate in
`cost_per_dimensional_kg_minor` may be zero but not negative.

### Input errors versus rejections

Expand Down Expand Up @@ -322,13 +324,12 @@ original paths and re-export the relocated definitions, so every workspace test,
against the exact same objects. There is one definition of `rate_tariff` in the Python
tree, and the installed wheel contains it. `commerce/rating/objective.py` — the solver
adapter — is untouched and stays a workspace module: it registers in-process scorer and
selector objects, which EXTENDING.md explains are deliberately not
cross-language features.
selector objects, which are deliberately not cross-language features: an in-process
object cannot cross the JSON boundary every other engine is driven through.

## Conformance standard

Held to the standard every other capability in this project is held to, per
TESTING-AND-RELEASE.md:
Held to the standard every other capability in this project is held to:

- **Python and PHP: byte-identical.** Both are ports of one contract; canonical JSON
(sorted keys, `,`/`:` separators, no trailing whitespace) of the result document must
Expand Down Expand Up @@ -364,15 +365,15 @@ TESTING-AND-RELEASE.md:
| `catalog_version_info` | `O(h + e log e)` | `O(e)` |

The `e log e` term is sorting the three id lists; every other bound is a linear scan of
the relevant history. These match the bounds already published for the underlying
models in ALGORITHMS-AND-COMPLEXITY.md — the wrapper
adds parsing and serialization, both linear in the payload, and nothing else.
the relevant history. These are the bounds of the underlying models; the wrapper adds
parsing and serialization, both linear in the payload, and nothing else.

## Limitations

- The document is supplied by the caller. Nothing here fetches, scrapes or embeds any
real carrier's published rates; live rate-card ingestion is still out of scope and
still tracked in LIMITATIONS-AND-ROADMAP.md.
real carrier's published rates. Live rate-card ingestion exists, but in the carrier
connectors, not here: CARRIER-RATE-INGESTION.md builds a
table from a feed, and this API is handed the result like any other document.
- `catalog_version_info` returns metadata and id lists, not the resolved master-data
records. Exporting the full snapshot is a larger surface with its own wire-format
question and is not part of this epic.
Expand Down
4 changes: 2 additions & 2 deletions docs/GUARANTEES.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ silently — if you need them, they belong in your own layer above this library.

## Status of this release

Version `1.6.0` freezes the public API. Field names, status codes, the objective
Version `1.7.0` freezes the public API. Field names, status codes, the objective
vector, the numeric policy and the validation rules do not change without a major
version, so any `1.x` is a safe upgrade from any earlier `1.x`. A caret or tilde
constraint on `1.0` is enough; an exact pin is no longer required.
Expand All @@ -61,7 +61,7 @@ What the freeze does not cover: which of several equally valid packings a solver
returns. That is bounded by the objective vector, not by the placement list, and a minor
release may return a different arrangement with the same or a better score.

The algorithm complexities documented in `ALGORITHMS-AND-COMPLEXITY.md` are asymptotic
The algorithm complexities Packvium states, in these docs and in the source, are asymptotic
design bounds held by review, not per-release measurements. They are not a wall-clock
performance contract either: constants, input shape and host all move the real number.
Use them to choose a solver profile, not to predict a duration.
Loading
Loading