Sketch: mosp (0.2)#
Status: reviewed sketch (M3, against the frozen 0.1 contract, ADR 0023); implemented and frozen
in M7 with the changes of ADR 0027 (options::num_objectives, delta of the K updates only, host
path costs over the K objectives, affected; the open questions below are decided there). The
algorithm page is docs/algorithms/mosp.md.
Computes: multi-objective shortest paths from one source on a graph with K weight columns:
the K single-objective shortest-path trees, the preference-weighted combined graph, its
shortest-path tree (the MOSP tree) and the K path costs of every vertex along it.
Paper: DynaMOSP (IPDPS 2025, IEEE TPDS 2025). Origin: MOSP-CUDA src/mospUpdate.cu,
combinedGraphGpu.cu; MOSP-OpenMP combinedGraphCpu.cpp (PLAN Section 6.4.4).
The header#
// cpp/include/dyng/mosp.hpp
namespace dyng::mosp {
struct options { ///< an aggregate; fields are only appended
std::vector<std::int32_t> preferences; ///< one per objective, >= 1; empty = all ones. Fixed at
///< compute(); lcm(preferences) <= 2^20
std::int64_t delta = 0; ///< near-far width of the K updates and of the combined
///< solve; 0 = automatic (sssp's rule). A tunable
engine cuda_engine = engine::automatic; ///< the engine of the K sssp updates (sssp::options). A tunable
bool compute_path_costs = true; ///< Step 3's last line (the K costs along the MOSP tree). A tunable
bool validate_inputs = true; ///< O(K n) checks of imported trees (from_arrays)
};
struct stats : update_stats { ///< update_stats: affected (vertices whose MOSP parent
apply_summary batch; ///< or a cost changed), fallback_used, engine_used, ...
std::vector<sssp::stats> objectives; ///< one per objective, deterministic fields as in sssp
std::int64_t combined_edges = 0; ///< deterministic: edges of the combined graph
std::int64_t preference_scale = 0; ///< deterministic: L = lcm(preferences)
};
template <typename vertex_t, typename distance_t = std::int64_t>
class result { ///< opaque, move-only (pimpl)
public:
[[nodiscard]] vertex_t source() const noexcept;
[[nodiscard]] int num_objectives() const noexcept;
[[nodiscard]] array_view<const distance_t> distances(int objective) const; ///< the K sssp trees
[[nodiscard]] array_view<const vertex_t> parents(int objective) const;
[[nodiscard]] array_view<const distance_t> combined_distances() const; ///< in units of 1/L
[[nodiscard]] array_view<const vertex_t> combined_parents() const; ///< the MOSP tree
[[nodiscard]] array_view<const distance_t> path_costs() const; ///< n * K, vertex-major
[[nodiscard]] std::int64_t preference_scale() const noexcept;
[[nodiscard]] const options& get_options() const;
void set_options(const options& opt); ///< tunables only
[[nodiscard]] std::uint64_t graph_version() const noexcept;
[[nodiscard]] memory_space space() const noexcept;
[[nodiscard]] result clone(const resources& res) const;
template <typename edge_t, typename weight_t> ///< adopt K trees (MOSP's obj<k>/ files)
[[nodiscard]] static result from_arrays(const resources& res, const graph<vertex_t, edge_t, weight_t>& g,
vertex_t source,
array_view<const array_view<const distance_t>> distances,
array_view<const array_view<const vertex_t>> parents,
bool canonicalize = true, const options& opt = {});
};
template <typename vertex_t, typename edge_t, typename weight_t>
[[nodiscard]] result<vertex_t> compute(const resources& res, const graph<vertex_t, edge_t, weight_t>& g,
vertex_t source, const options& opt = {});
template <typename vertex_t, typename edge_t, typename weight_t>
stats update(const resources& res, graph<vertex_t, edge_t, weight_t>& g,
const edge_batch_view<vertex_t, weight_t>& batch, result<vertex_t>& r);
} // namespace dyng::mosp
Objectives are the weight columns of the graph (
g.num_weights(), 1 tomax_objectives = 64);graph_properties::num_weightssets K and the batch carries K weights per insertion (edge_batch_view::num_weights). No new container or batch type is needed.Composition, not a new engine.
update()runs Kssspproblems over objective views of one graph through onerun_update()(one commit, one Step 0), sharing the handle’s workspace (ADR 0015), thenfinalizebuilds the combined graph (weightsL(K+1) - sum_i L / pref_iper edge: count, scan, fill), solves it statically with thessspengine, and computes the path costs. This is the framework’s composition of PLAN 4.5.1;mosp_problemholds the Ksssp_problems.Stages:
mosp.normalize,mosp.commit,mosp.objectivearound each sssp update (one stage for all K objectives, called K times; whose ownsssp.*stages nest inside; the frozen profiler rule wants every dotted part to start with a letter, so the objective’s index is not a name part:mosp.objective.0would be rejected, and the per-objective times are the stage’s samples in order),mosp.combine,mosp.combined_sssp,mosp.path_costs,mosp.finalize. The paper’s regions: “(a) compute” = the K updates + combine + combined solve, “(b) end to end”.
Contract and registry#
Item |
Value |
|---|---|
Family |
fixed point (K times |
Oracle |
|
Determinism |
|
Graph requirements |
|
Backends |
sequential, openmp, cuda (fused sssp engine; operators engine from M7) |
|
strong before the commit; basic after it (result poisoned) |
Review against the 0.1 contract#
Check |
Result |
|---|---|
Uniform skeleton |
yes; |
Frozen headers needed |
none changed: K weight columns, |
Differences from PLAN 5.1-5.3 |
|
Python |
|
Open questions (decided in M7)#
Whether
path_costs()is computed on the device in 0.2 (the original computes it on the host) or stays a host step behindcompute_path_costs.preferencesasstd::vectormakesoptionsnon-trivially-copyable; an inline array ofmax_objectivesis the alternative if the Python binding or the ABI prefers it.The combined graph’s storage: a second
graphowned by the result (simplest, reusesgraph::from_csr) or a workspace-leased CSR (no per-update allocation, invariant I9). The latter is the target; the former is the straight port.