Skip to main content

somatize_core/
placement.rs

1//! Where each node runs: the fourth fact, beside [`Graph`](crate::Graph) (what
2//! exists), [`Catalog`](crate::Catalog) (who executes it) and
3//! [`Plan`](crate::Plan) (when).
4//!
5//! It does not fit in the graph — topology only — nor in the catalog, which is
6//! the half that is **not** data: when a subgraph travels, the placement travels
7//! with it and the implementations do not.
8//!
9//! Two maps and not a pair, because the two halves are obeyed by different
10//! people: [`distribute`](crate::distribute) reads the [`Host`] when deciding
11//! the shape, and the node reads the [`Device`] through `ctx.device` when
12//! executing. A node can have either, both or neither. Hence
13//! [`compile`](crate::compile) sees neither: a device is inert for the traversal,
14//! and crossing a wire is a separate, named step.
15//!
16//! A bare map, without checking that the ids exist: that is checked where there
17//! is a graph in front of you.
18
19use crate::{Device, Host, NodeId};
20use std::collections::{HashMap, HashSet};
21
22/// Where each node runs. The ones not listed run wherever they land.
23#[derive(Debug, Default, Clone, PartialEq, Eq)]
24#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
25pub struct Placement {
26    devices: HashMap<NodeId, Device>,
27    hosts: HashMap<NodeId, Host>,
28}
29
30impl Placement {
31    /// Nothing placed.
32    pub fn new() -> Self {
33        Self::default()
34    }
35
36    /// Places a node on a device, returning where it was before.
37    pub fn place(&mut self, id: impl Into<NodeId>, device: Device) -> Option<Device> {
38        self.devices.insert(id.into(), device)
39    }
40
41    /// Sends a node to a host, returning which one it was on before.
42    /// Independent of [`place`](Self::place).
43    pub fn place_at(&mut self, id: impl Into<NodeId>, host: Host) -> Option<Host> {
44        self.hosts.insert(id.into(), host)
45    }
46
47    /// Which device this node runs on, if it was said. `None` is "wherever it
48    /// already is", not `cpu`.
49    pub fn of(&self, id: &NodeId) -> Option<&Device> {
50        self.devices.get(id)
51    }
52
53    /// Which host this node runs on, if it was said. `None` is here.
54    pub fn host_of(&self, id: &NodeId) -> Option<&Host> {
55        self.hosts.get(id)
56    }
57
58    /// Every host this placement names, once each, in a fixed order.
59    ///
60    /// The half of [`host_of`](Self::host_of) that reads the other way, for the
61    /// client that talks to a broker and so does not already know the names.
62    /// Once each, because a host named by ten nodes is one rendezvous. **Sorted**,
63    /// because these come out of a `HashMap` and an irreproducible order would
64    /// make the order failures happen in irreproducible too.
65    pub fn hosts(&self) -> Vec<&Host> {
66        let mut named: Vec<&Host> = self.hosts.values().collect();
67        named.sort();
68        named.dedup();
69        named
70    }
71
72    /// How many nodes have something said about them: device, host or both.
73    pub fn len(&self) -> usize {
74        self.devices
75            .keys()
76            .chain(self.hosts.keys())
77            .collect::<HashSet<_>>()
78            .len()
79    }
80
81    /// Whether nothing has been said about any node.
82    pub fn is_empty(&self) -> bool {
83        self.devices.is_empty() && self.hosts.is_empty()
84    }
85
86    /// Whether no node has been sent to any host, which is what allows skipping
87    /// [`distribute`](crate::distribute).
88    pub fn is_local(&self) -> bool {
89        self.hosts.is_empty()
90    }
91}