ratio_graph/
mdm.rs

1//! # Multi-domain matrix module
2//!
3//! ## License
4//!
5//! This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
6//! If a copy of the MPL was not distributed with this file,
7//! You can obtain one at <https://mozilla.org/MPL/2.0/>.
8//!
9//! **Code examples both in the docstrings and rendered documentation are free to use.**
10
11use std::collections::{BTreeMap, BTreeSet};
12
13use snafu::{OptionExt, ResultExt, Snafu};
14use uuid::Uuid;
15
16use crate::{EdgeStore, Graph, Meta, MetadataFilter, Node, NodeStore};
17
18/// Multi-domain matrix error.
19#[derive(Clone, Debug, Snafu)]
20pub enum MdmError {
21    /// No node on MDM with this id: {id}.
22    NodeNotOnAxis { id: Uuid },
23    /// NodeStore error.
24    NodeStore { source: crate::node::NodeStoreError },
25    /// EdgeStore error.
26    EdgeStore { source: crate::edge::EdgeStoreError },
27}
28
29/// Whether any node of any kind should be selected independently or they should at least share one
30/// edge with a supplied node kind.
31#[derive(Clone, Debug, Default, PartialEq)]
32#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
33pub enum NodeSelectionMode {
34    #[default]
35    Independent,
36    Dependent(BTreeSet<String>),
37}
38
39/// What to display for edges.
40#[derive(Copy, Clone, Default, Debug, PartialEq)]
41#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
42pub enum EdgeDisplayMode {
43    #[default]
44    Binary,
45    Kinds,
46    Labels,
47    Weights,
48}
49
50/// How to divide the piecharts per edge.
51#[derive(Copy, Clone, Default, Debug, PartialEq)]
52#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
53pub enum EdgeDivisionMode {
54    Absolute,
55    #[default]
56    Equal,
57    Relative,
58}
59
60/// Node IDs as they should be displayed on the axis of an MDM for the given filters and settings.
61pub fn mdm_axis_nodes(
62    graph: &Graph,
63    node_filter: &MetadataFilter,
64    edge_filter: &MetadataFilter,
65    root_ids: Option<&Vec<Uuid>>,
66    node_depth: Option<usize>,
67    node_selection_mode: &NodeSelectionMode,
68) -> Result<Vec<Uuid>, MdmError> {
69    let root_ids = root_ids
70        .cloned()
71        .unwrap_or_else(|| graph.root_ids().map(|id| id.to_owned()).collect());
72
73    let node_kinds_vec = node_filter.kinds.clone().unwrap_or_else(|| {
74        graph
75            .node_aggregate()
76            .kinds
77            .keys()
78            .map(|x| x.to_owned())
79            .collect::<Vec<_>>()
80    });
81    let node_kind_order = node_kinds_vec
82        .into_iter()
83        .enumerate()
84        .map(|(i, kind)| (kind, i))
85        .collect::<BTreeMap<String, usize>>();
86
87    // Split independent roots from dependent roots and sort them.
88    let (mut ind_roots, mut dep_roots) =
89        split_dependent_roots(graph, root_ids, node_selection_mode)?;
90
91    ind_roots.sort_by_key(|id| {
92        graph
93            .get_node(id)
94            .map(|n| node_kind_order.get(&n.get_meta().kind))
95    });
96    dep_roots.sort_by_key(|id| {
97        graph
98            .get_node(id)
99            .map(|n| node_kind_order.get(&n.get_meta().kind))
100    });
101
102    // Get all leaf IDs for the independent roots.
103    let ind_leaf_vec: Vec<Uuid> =
104        independent_leafs(graph, &ind_roots, node_filter, node_depth).collect();
105
106    // Early return if there are only independent roots.
107    if node_selection_mode == &NodeSelectionMode::Independent {
108        return Ok(ind_leaf_vec);
109    }
110    let mut result = ind_leaf_vec.clone();
111
112    // Aggregate them in a set for filtering and sorting functions.
113    let ind_leaf_set: BTreeSet<Uuid> = ind_leaf_vec.iter().copied().collect();
114
115    // Get dependent leafs by further filtering after the independent leafs logic.
116    let dep_leaf_iter =
117        independent_leafs(graph, &dep_roots, node_filter, node_depth).filter(|&leaf_id| {
118            let this = BTreeSet::from([leaf_id]);
119            graph
120                .edges_between_all(&this, &ind_leaf_set)
121                .any(|e| edge_filter.satisfies(e))
122                || graph
123                    .edges_between_all(&ind_leaf_set, &this)
124                    .any(|e| edge_filter.satisfies(e))
125        });
126
127    result.extend(dep_leaf_iter);
128
129    Ok(result)
130}
131
132/// Expand a node in a MDM axis.
133pub fn mdm_axis_expand(
134    axis: &mut Vec<Uuid>,
135    node_id: &Uuid,
136    graph: &Graph,
137    node_filter: &MetadataFilter,
138    edge_filter: &MetadataFilter,
139    node_selection_mode: &NodeSelectionMode,
140) -> Result<(), MdmError> {
141    let position = axis
142        .iter()
143        .position(|id| id == node_id)
144        .with_context(|| NodeNotOnAxisSnafu { id: *node_id })?;
145
146    let children = mdm_axis_children(
147        axis,
148        node_id,
149        graph,
150        node_filter,
151        edge_filter,
152        node_selection_mode,
153    )?;
154
155    axis.splice(position..position + 1, children);
156
157    Ok(())
158}
159
160pub fn mdm_axis_children<'a>(
161    axis: &[Uuid],
162    node_id: &Uuid,
163    graph: &'a Graph,
164    node_filter: &'a MetadataFilter,
165    edge_filter: &'a MetadataFilter,
166    node_selection_mode: &NodeSelectionMode,
167) -> Result<impl Iterator<Item = Uuid> + 'a, MdmError> {
168    let children = graph
169        .get_node_err(node_id)
170        .with_context(|_| NodeStoreSnafu)?
171        .children
172        .iter()
173        .filter(|&child_id| {
174            graph
175                .get_node(child_id)
176                .map(|c| node_filter.satisfies(c))
177                .unwrap_or_default()
178        });
179    let children: Box<dyn Iterator<Item = &Uuid>> = match node_selection_mode {
180        NodeSelectionMode::Independent => Box::new(children),
181        NodeSelectionMode::Dependent(kinds) => {
182            let ind_leaf_set = axis
183                .iter()
184                .filter(|&id| {
185                    graph
186                        .get_node(id)
187                        .map(|n| kinds.contains(&n.get_meta().kind))
188                        .unwrap_or_default()
189                })
190                .copied()
191                .collect::<BTreeSet<_>>();
192            Box::new(children.filter(move |&child_id| {
193                let this = BTreeSet::from([*child_id]);
194                graph
195                    .edges_between_all(&this, &ind_leaf_set)
196                    .any(|e| edge_filter.satisfies(e))
197                    || graph
198                        .edges_between_all(&ind_leaf_set, &this)
199                        .any(|e| edge_filter.satisfies(e))
200            }))
201        }
202    };
203    Ok(children.copied())
204}
205
206/// Collapse a node's children in an MDM axis.
207pub fn mdm_axis_collapse(
208    axis: &mut Vec<Uuid>,
209    node_id: &Uuid,
210    graph: &Graph,
211) -> Result<(), MdmError> {
212    let leaf_set = axis.iter().copied().collect::<BTreeSet<_>>();
213
214    let mut to_replace = graph
215        .descendant_ids(node_id, true, true, Some(&leaf_set), None)
216        .filter_map(|id| id.ok())
217        .filter(|&id| leaf_set.contains(id))
218        .filter_map(|id| axis.iter().position(|e| e == id));
219
220    if let Some(first) = to_replace.next() {
221        let (start, end) =
222            to_replace.fold((first, first), |(start, end), c| (start.min(c), end.max(c)));
223        axis.splice(start..end + 1, [*node_id]);
224    }
225
226    Ok(())
227}
228
229/// Split dependent roots and independent roots into two separate vectors of IDs.
230fn split_dependent_roots(
231    graph: &Graph,
232    root_ids: Vec<Uuid>,
233    mode: &NodeSelectionMode,
234) -> Result<(Vec<Uuid>, Vec<Uuid>), MdmError> {
235    Ok(match mode {
236        NodeSelectionMode::Independent => (root_ids, vec![]),
237        NodeSelectionMode::Dependent(lead_kinds) => {
238            let mut ind_roots = vec![];
239            let mut dep_roots = vec![];
240            for root_id in root_ids.into_iter() {
241                if lead_kinds.contains(
242                    graph
243                        .get_node_err(&root_id)
244                        .with_context(|_| NodeStoreSnafu)?
245                        .kind(),
246                ) {
247                    ind_roots.push(root_id);
248                } else {
249                    dep_roots.push(root_id);
250                }
251            }
252            (ind_roots, dep_roots)
253        }
254    })
255}
256
257/// Get relevant leaf node IDs in an independent fashion.
258fn independent_leafs<'a>(
259    graph: &'a Graph,
260    root_ids: &'a [Uuid],
261    node_filter: &'a MetadataFilter,
262    node_depth: Option<usize>,
263) -> impl Iterator<Item = Uuid> + 'a {
264    root_ids.iter().flat_map(move |root_id| {
265        let descendants: Box<dyn Iterator<Item = &Uuid>> = if node_depth == Some(0)
266            || graph
267                .get_node(root_id)
268                .map(|r| r.is_leaf())
269                .unwrap_or_default()
270        {
271            Box::new(Some(root_id).into_iter())
272        } else {
273            Box::new(
274                graph
275                    .descendant_ids(root_id, true, true, None, node_depth)
276                    .filter_map(|id| id.ok()),
277            )
278        };
279        descendants.filter_map(|id| {
280            graph.get_node(id).and_then(|node| {
281                if node_filter.satisfies(node) {
282                    Some(*id)
283                } else {
284                    None
285                }
286            })
287        })
288    })
289}
290
291/// Do a typical display sorting of the given input node IDs.
292pub fn display_sort(
293    graph: &Graph,
294    node_ids: &[Uuid],
295    node_kind_order: Option<&BTreeMap<String, usize>>,
296    leaf_ids: Option<&BTreeSet<Uuid>>,
297) -> Vec<usize> {
298    let mut nodes: Vec<(usize, &Node)> = node_ids
299        .iter()
300        .filter_map(|x| graph.get_node(x))
301        .enumerate()
302        .collect();
303    nodes.sort_unstable_by_key(|&(_, node)| {
304        (
305            node_kind_order.map(|x| x.get(node.kind())),
306            graph.node_width(node.id(), false, leaf_ids, None).unwrap(),
307            node.name(),
308            node.id(),
309        )
310    });
311    nodes.into_iter().map(|x| x.0).collect()
312}