Utilities¶
Small helpers shared by the solvers, the dataset loaders and the test-suite:
Bunch (the return type of the loaders),
check_random_state and
check_is_fitted (the two estimator helpers of the base
class), initial_tour (the warm-start helper behind every
init= parameter) and check_router, the
structural test battery a new solver must pass before it is merged
(tests/test_common.py runs it over skroute.all_solvers()). Its multi-trip check (8) also
fits one instance with a per-node service_time (the depot's included) and requires the result
to equal a fit on the time matrix with the services folded in by hand — the definition of D32 —
so a solver must read problem.time_or_cost, never problem.time, when it prices durations
during its search; check 6 exercises the service_time error messages, so a fit override
must forward it.
utils ¶
Shared helpers: Bunch, input validation, the warm-start helper and the estimator checks.
Bunch— dict with attribute access (the return type of the dataset loaders).check_random_state—None/int/Generator->numpy.random.Generator.check_is_fitted— raisesNotFittedErrorbeforefit.initial_tour— builds a solver's starting tour from itsinitparameter.check_router— the structural test battery (inskroute.utils.estimator_checks; also exported asskroute.check_router).
Bunch ¶
Bases: dict
Container object exposing keys as attributes.
Bunch extends dict so that values can be accessed either with
b["key"] or b.key. Setting an attribute stores a key.
Examples:
>>> from skroute.utils import Bunch
>>> b = Bunch(a=1, b="two")
>>> b.a, b["b"]
(1, 'two')
>>> b.c = [3]
>>> sorted(b)
['a', 'b', 'c']
initial_tour ¶
initial_tour(problem: RoutingProblem, init: Any, rng: Generator | None) -> ndarray
Build the starting index tour of a solver from its init parameter.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
problem
|
RoutingProblem
|
The instance being solved. |
required |
init
|
('nearest_neighbour', 'random')
|
|
"nearest_neighbour"
|
rng
|
Generator or None
|
The solver's generator; |
required |
Returns:
| Name | Type | Description |
|---|---|---|
tour |
ndarray of shape (n,), int64
|
A permutation of |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
Examples:
>>> import numpy as np
>>> from skroute import RoutingProblem
>>> from skroute.utils import initial_tour
>>> C = np.array([[0, 5, 9, 10], [5, 0, 4, 8], [9, 4, 0, 3], [10, 8, 3, 0]], dtype=float)
>>> p = RoutingProblem(C, depot=3)
>>> initial_tour(p, "nearest_neighbour", None).tolist()
[3, 2, 1, 0]
>>> initial_tour(p, [3, 0, 1, 2, 3], None).tolist()
[3, 0, 1, 2]
>>> t = initial_tour(p, "random", np.random.default_rng(0))
>>> int(t[0]) == 3 and sorted(t.tolist()) == [0, 1, 2, 3]
True
check_is_fitted ¶
Raise NotFittedError unless estimator has been fitted.
An estimator is fitted when it carries the cost_ attribute, which
fit sets last.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
estimator
|
BaseRouter
|
The estimator to check. |
required |
Raises:
| Type | Description |
|---|---|
NotFittedError
|
With the message |
Examples:
>>> from skroute.base import BaseRouter
>>> from skroute.utils import check_is_fitted
>>> class Identity(BaseRouter):
... def _solve(self, problem, rng):
... return problem.to_index_tour(problem.labels)
>>> check_is_fitted(Identity())
Traceback (most recent call last):
...
skroute.exceptions.NotFittedError: This Identity instance is not fitted yet. Call 'fit' first.
>>> check_is_fitted(Identity().fit([[0, 1, 2], [1, 0, 1], [2, 1, 0]]))
check_random_state ¶
Turn seed into a numpy.random.Generator.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
seed
|
(None, int or Generator)
|
|
required |
Returns:
| Name | Type | Description |
|---|---|---|
rng |
Generator
|
|
Raises:
| Type | Description |
|---|---|
TypeError
|
If |
Examples:
check_router ¶
check_router(estimator: BaseRouter) -> None
Run the structural test battery of SPEC §6 on an unfitted solver instance.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
estimator
|
BaseRouter
|
An unfitted instance (so |
required |
Raises:
| Type | Description |
|---|---|
AssertionError
|
Prefixed with the number of the failing check ( |
TypeError
|
If |
ValueError
|
If |
Warns:
| Type | Description |
|---|---|
UserWarning
|
For every check that had to be skipped ( |
Notes
The checks: 1 parameter protocol, 2 not fitted, 3 fitted attributes, 4 recomputed cost,
5 input kinds, 6 invalid inputs, 7 tags, 8 multi-trip, 9 no printing, 10 iterative
contract, 11 reproducibility, 13 smallest sizes, 14 callback protocol (D30).
check_router.checks lists them as (name, fn) pairs; the tolerance checks (12) live
in tests/test_common.py.
Examples:
>>> from skroute import check_router
>>> from skroute.base import BaseRouter, RouterTags
>>> import numpy as np
>>> class Identity(BaseRouter):
... def __init__(self, verbose=0):
... self.verbose = verbose
...
... def _get_tags(self):
... return RouterTags(kind="construction")
...
... def _solve(self, problem, rng):
... return np.roll(np.arange(problem.n), -problem.depot)
>>> check_router(Identity())
>>> [name for name, fn in check_router.checks][:3]
['1_init_and_params', '2_not_fitted', '3_fit_results']