Replies: 1 comment
-
|
Hello @fodzal! Interesting idea - we will review and get back to you. Please understand that this may take a while, as we'll need to look at this very carefully. |
Beta Was this translation helpful? Give feedback.
0 replies
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
-
Proposal: a dedicated
GraphStructurefor "chained entity + fixed-entity DAG" (multi-entity declarative shadow variables)Summary
I'd like to propose recognizing a new graph structure for declarative shadow variables: models with two entity classes, where one class forms chains via a single directional parent (
PREVIOUS/NEXT) and the other forms a fixed, acyclic DAG that consumes the chain's results and pushes them forward.Today this case is classified as
ARBITRARYand pays a full Tarjan SCC recompute on every move, even though the structure is fully known up front and contains no real cycles. I already have a working fork that handles it as an O(k) chain-walk + a pre-computed DAG order, and I'd like to know whether a cleaned-up version would be welcome upstream.My use case
In my VRP, I have successive
VehicleShifts. Propagation flows along the visits of one shift, and the end state of that shift (end time, end location, last visit, …) must be pushed to the next shift, which itself does not move — the shift-to-shift ordering is a problem fact.This pattern models several real needs:
I model it with declarative shadow variables (
@ShadowVariablesupplier methods) on two entity classes:Visit):arrivalTime/departureTime, computed from the previous element (aPREVIOUSsource). Visits are a@PlanningListVariableon each shift.VehicleShift):lastVisit/endTime/endLocation, plus a "previous shift"GROUPsource that carriesshiftA.end → shiftB.start.So the instance-level dependency graph is: chains within each shift + an acyclic DAG across shifts (
A → B → …). No cycles, fully determined by the model and the problem facts.The problem
As soon as there are 2+ entity classes with declarative shadow variables,
GraphStructure.determine()classifies the model asARBITRARY:That routes everything through
DefaultVariableReferenceGraph, whoseinnerUpdateChanged()runs, on every move:The propagation step (
AffectedEntitiesUpdater) is already incremental and short-circuits on no-change. The cost is thecommitChanges()call: a full Tarjan SCC recompute over the whole instance graph, O(V + E) per move.For my models (large number of visits) this dominates runtime — it's the difference between unusable and fine. And it's pure overhead here: there are no cycles to detect, and the structure never changes shape, only the dynamic
PREVIOUS/NEXTedges do.This is the same kind of situation the existing
SINGLE_DIRECTIONAL_PARENTstructure already optimizes — but that one only applies to a single entity class.What I've built (working fork)
A new structure
MULTI_ENTITY_SINGLE_DIRECTIONAL_PARENTand aMultiEntityChainedVariableReferenceGraphthat handles exactly this shape, with no generic instance graph and no Tarjan.Classification (once, at build time) — in
GraphStructure.determine(), the multi-entity case is accepted when:INDIRECTsources;PREVIOUS/NEXT) on one entity class (the "chained" entity);PREVIOUS/NEXT/NO_PARENT(+INVERSEallowed only if it targets the fixed entity, e.g.visit.vehicleShift);VARIABLE/GROUP/NO_PARENT.Anything outside these preconditions falls back to
ARBITRARYsafely.Fixed-entity DAG order — computed once via Kahn's algorithm from the
GROUPsources (A → Bedges), stored as a fixed topological order.Per move:
PriorityQueue(handles DAG merges correctly);GROUP-only, e.g.previousEndPosition) → chain-walk the visits in list order (O(k), with change-detection short-circuit) → post-chain updaters (lastVisit/endTime/endLocation);Result: no Tarjan, no per-node instance graph, no SCC/looped tracking. Complexity per move ≈ O(k + V_affected · log V), where
k= affected chain length andV_affected= number of shifts whose shadows changed.The fork also handles two correctness details that come with walking the chain directly: resetting shadow values on unassign (undo), and not terminating the walk early when there are non-contiguous dirty visits on the same shift after a swap.
Open questions before I clean this up for a PR
ARBITRARYfallback acceptable as a first step?I'm happy to open a draft PR, share the fork, and provide benchmarks on a representative model. Would a contribution along these lines be welcome?
Beta Was this translation helpful? Give feedback.
All reactions