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}