Skip to main content

pudo-sequences

pudo-sequences is a small Python package for PU/DO constrained sequence combinatorics: counting, generating, and indexing event orders where every pickup must occur before its paired drop-off.

pickup_i before dropoff_i

Use it when a routing, ridesharing, dispatching, simulation, optimization, or learning system needs to reason about feasible local service orders before evaluating distance, time windows, capacity, cost, or reward.

Why It Matters

A PU/DO route is not just a permutation of events. For two new requests, the four events are:

0 = pickup request 0
1 = pickup request 1
2 = drop-off request 0
3 = drop-off request 1

itertools.permutations returns all 24 event orders, including impossible orders such as (2, 0, 1, 3), where request 0 is dropped off before it is picked up.

from itertools import permutations

from pudo_sequences import pudo_sequences

all_orders = list(permutations([0, 1, 2, 3]))
valid_orders = pudo_sequences([0, 1])

print(len(all_orders))
print(len(valid_orders))
24
6

The valid orders are:

[
    (0, 1, 2, 3),
    (0, 1, 3, 2),
    (0, 2, 1, 3),
    (1, 0, 2, 3),
    (1, 0, 3, 2),
    (1, 3, 0, 2),
]

You can filter normal permutations yourself:

def is_valid(route):
    return route.index(0) < route.index(2) and route.index(1) < route.index(3)


filtered = [route for route in all_orders if is_valid(route)]

assert filtered == valid_orders

For small teaching examples that is fine. In real local-search or learning loops, generating impossible actions first adds noise and waste. The gap grows quickly:

Requests Unconstrained permutations Valid PU/DO sequences Valid share
1 2 1 50.0%
2 24 6 25.0%
3 720 90 12.5%
4 40,320 2,520 6.25%
5 3,628,800 113,400 3.125%

The package focuses on this first layer:

PU/DO combinatorics: which event orders are logically possible?
Domain evaluation: which of those orders satisfy time, capacity, or cost rules?
Selection: which feasible order should the system choose?

It is not a routing solver. It gives you the constrained local sequence space that a solver, simulator, heuristic, or policy can evaluate.

Install

pip install pudo-sequences

For local development:

pip install -e ".[dev]"
pytest

The core package has no runtime dependency beyond Python. The optional topological strategy requires NetworkX:

pip install "pudo-sequences[networkx]"

Quick Start

from pudo_sequences import count_pudo_sequences, pudo_sequences

print(count_pudo_sequences(2))
print(pudo_sequences([0, 1]))

With the default integer convention, pickup i maps to drop-off i + n, where n is the number of requests. For requests=[0, 1], drop-offs are 2 and 3.

To stream instead of materializing every route:

from pudo_sequences import iter_pudo_sequences

best_route = None
best_score = float("inf")

for route in iter_pudo_sequences([0, 1, 2]):
    score = sum(route)  # Replace with distance, time, reward, or feasibility logic.
    if score < best_score:
        best_route = route
        best_score = score

To check whether a candidate from a heuristic is PU/DO-valid, compare it with the generated local action set for small neighborhoods:

from pudo_sequences import pudo_sequences

valid_routes = set(pudo_sequences([0, 1, 2]))

assert (0, 3, 1, 4, 2, 5) in valid_routes
assert (3, 0, 1, 4, 2, 5) not in valid_routes

Custom Labels

Use dropoff_for when events are not integers:

from pudo_sequences import pudo_sequences

routes = pudo_sequences(
    ["alice", "bob"],
    dropoff_for=lambda pickup: ("dropoff", pickup),
)

assert (
    "alice",
    ("dropoff", "alice"),
    "bob",
    ("dropoff", "bob"),
) in routes

By requiring a mapping from pickup labels to drop-off labels, the package can use the same combinatorics for rider IDs, job IDs, task names, tuples, or other hashable labels.

Already-Open Drop-Offs

Sometimes a planning horizon starts with requests already onboard. Their drop-offs have no pickup inside the local sequence, so they may appear anywhere.

from pudo_sequences import count_pudo_sequences, pudo_sequences

routes = pudo_sequences([0, 1], open_dropoffs=[4])

assert len(routes) == count_pudo_sequences(2, n_open_dropoffs=1)
assert len(routes) == 30
assert (4, 0, 1, 2, 3) in routes
assert (0, 1, 2, 3, 4) in routes

This represents two new requests plus one already-open request whose drop-off is 4.

Counts

For n labeled pickup/drop-off pairs, the number of valid sequences is:

(2n)! / 2^n

Equivalently:

n! * (1 * 3 * 5 * ... * (2n - 1))

If there are also m already-open drop-offs, the count becomes:

(2n + m)! / 2^n

API:

from pudo_sequences import (
    count_fixed_pickup_order_dropoff_insertions,
    count_open_dropoff_insertions,
    count_pudo_sequences,
)

assert count_pudo_sequences(3) == 90
assert count_pudo_sequences(3, n_open_dropoffs=2) == 5040
assert count_fixed_pickup_order_dropoff_insertions(3) == 15
assert count_open_dropoff_insertions(3, 2) == 56

The constructive count is useful for explaining the structure:

1. choose a pickup order;
2. insert each paired drop-off only after its pickup;
3. optionally insert already-open drop-offs anywhere.

Generation Strategies

The default strategy is dependency-free DFS over the PU/DO state space:

from pudo_sequences import iter_pudo_sequences

for route in iter_pudo_sequences([0, 1, 2], strategy="dfs"):
    pass

Other strategies are available for comparison:

  • strategy="insertion": generate pickup orders, then insert each paired drop-off only after its pickup.
  • strategy="topological": use NetworkX to enumerate all topological orders of a precedence graph with edges pickup_i -> dropoff_i.

For n=3, all strategies represent the same constrained sequence set:

from pudo_sequences import pudo_sequences

dfs_routes = set(pudo_sequences([0, 1, 2], strategy="dfs"))
insertion_routes = set(pudo_sequences([0, 1, 2], strategy="insertion"))

assert dfs_routes == insertion_routes

The generator is intended for small local neighborhoods. PU/DO sequence counts grow factorially, so large request sets should usually be handled by heuristics, optimization models, or sampling rather than full enumeration.

Prefix Indexing

When a caller repeatedly asks for valid continuations after a partial route, build a prefix index:

from pudo_sequences import build_pudo_index

index = build_pudo_index([0, 1])

assert index.continuations([0, 1]) == {(2, 3), (3, 2)}
assert index.continuations([0, 2]) == {(1, 3)}

The index is a secondary feature. Its purpose is fast continuation lookup, not the definition of the package.

Development

pip install -e ".[dev]"
pytest -q
python -m build --sdist --wheel
python -m twine check dist/*

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

pudo_sequences-0.1.0.tar.gz (9.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

pudo_sequences-0.1.0-py3-none-any.whl (9.8 kB view details)

Uploaded Python 3

File details

Details for the file pudo_sequences-0.1.0.tar.gz.

File metadata

  • Download URL: pudo_sequences-0.1.0.tar.gz
  • Upload date:
  • Size: 9.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.0

File hashes

Hashes for pudo_sequences-0.1.0.tar.gz
Algorithm Hash digest
SHA256 77ce73e4f3f572428546b23eb3dbbf7de2a1d46ee83b651f62080df708a5f0b0
MD5 4210d0fc6f25d4c8f96c0c6331918595
BLAKE2b-256 6a949cbbb71f7ceae4b9bac1abd43cb996bcd6e5e9ac14d3e0cb9b58287a68e1

See more details on using hashes here.

File details

Details for the file pudo_sequences-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: pudo_sequences-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 9.8 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.0

File hashes

Hashes for pudo_sequences-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 2250bc907333568850f2b3c3e59a03c934ab95e941fced2123fbbb30a1628d51
MD5 fb32492f360ddd4715df486b6d207043
BLAKE2b-256 ea54c5eb32bdfa8ff5c6f395da10f191429b395bb68ff976aeaad63e0b61e189

See more details on using hashes here.

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 files

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page