ratio_graph/
metadata.rs

1//! # Metadata 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 uuid::Uuid;
14
15#[cfg(feature = "serde")]
16use crate::serde_utils::*;
17
18/// Metadata struct that contains common metadata for any instance.
19#[derive(Clone, Debug, PartialEq)]
20#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
21pub struct Metadata {
22    /// Unique instance ID.
23    #[cfg_attr(feature = "serde", serde(rename = "id"))]
24    _id: Uuid,
25    /// Instance name.
26    pub name: String,
27    /// Main category of this instance.
28    pub kind: String,
29    /// Instance labels.
30    #[cfg_attr(
31        feature = "serde",
32        serde(
33            default,
34            serialize_with = "sort_alphabetically",
35            skip_serializing_if = "is_empty_set"
36        )
37    )]
38    pub labels: BTreeSet<String>,
39    /// Numeric instance weights.
40    #[cfg_attr(
41        feature = "serde",
42        serde(
43            default,
44            serialize_with = "sort_alphabetically",
45            skip_serializing_if = "is_empty_map"
46        )
47    )]
48    pub weights: BTreeMap<String, f64>,
49    /// Miscellaneous data fields for this instance.
50    #[cfg_attr(
51        feature = "serde",
52        serde(
53            default,
54            serialize_with = "sort_alphabetically",
55            skip_serializing_if = "is_empty_map"
56        )
57    )]
58    pub annotations: BTreeMap<String, serde_json::Value>,
59}
60impl Default for Metadata {
61    fn default() -> Self {
62        let _id = Uuid::new_v4();
63        Self {
64            _id,
65            name: _id.to_string(),
66            kind: "default".to_string(),
67            labels: BTreeSet::<String>::default(),
68            weights: BTreeMap::<String, f64>::default(),
69            annotations: BTreeMap::<String, serde_json::Value>::default(),
70        }
71    }
72}
73impl Metadata {
74    /// Create a new Meta struct with arguments.
75    pub fn new(
76        id: Option<Uuid>,
77        name: Option<String>,
78        kind: Option<String>,
79        labels: Option<BTreeSet<String>>,
80        weights: Option<BTreeMap<String, f64>>,
81        annotations: Option<BTreeMap<String, serde_json::Value>>,
82    ) -> Self {
83        let _id = match id {
84            None => Uuid::new_v4(),
85            Some(x) => x,
86        };
87        Self {
88            _id,
89            name: name.unwrap_or_else(|| _id.to_string()),
90            kind: kind.unwrap_or_else(|| "default".to_string()),
91            labels: labels.unwrap_or_default(),
92            weights: weights.unwrap_or_default(),
93            annotations: annotations.unwrap_or_default(),
94        }
95    }
96    /// The unique ID of this instance.
97    pub fn id(&self) -> &Uuid {
98        &self._id
99    }
100    /// Set the unique ID to a custom value (or generate a new one with None).
101    /// Use with care! References in node and edge stores are NOT updated automatically.
102    pub fn set_id(&mut self, value: Option<Uuid>) {
103        self._id = value.unwrap_or_else(Uuid::new_v4);
104    }
105}
106
107pub trait Meta {
108    fn get_meta(&self) -> &Metadata;
109    fn id(&self) -> &Uuid {
110        &self.get_meta()._id
111    }
112    fn name(&self) -> &str {
113        self.get_meta().name.as_str()
114    }
115    fn kind(&self) -> &str {
116        self.get_meta().kind.as_str()
117    }
118    fn labels(&self) -> &BTreeSet<String> {
119        &self.get_meta().labels
120    }
121    fn weights(&self) -> &BTreeMap<String, f64> {
122        &self.get_meta().weights
123    }
124    fn annotations(&self) -> &BTreeMap<String, serde_json::Value> {
125        &self.get_meta().annotations
126    }
127}
128
129#[derive(Clone, Debug, Default, PartialEq)]
130#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
131pub enum Aggregator {
132    #[default]
133    Binary,
134    Kinds,
135    Labels,
136    Weights,
137    Annotations,
138}
139
140#[derive(Clone, Debug, Default, PartialEq)]
141#[cfg_attr(
142    feature = "serde",
143    derive(serde::Serialize, serde::Deserialize),
144    serde(default)
145)]
146pub struct Aggregate {
147    /// Over how many items the aggregate has been taken.
148    pub items: isize,
149    /// Kind occurrence.
150    pub kinds: BTreeMap<String, isize>,
151    /// Label occurrence.
152    pub labels: BTreeMap<String, isize>,
153    /// Weight sum value.
154    pub weights: BTreeMap<String, f64>,
155    /// Annotation key occurrence.
156    pub annotations: BTreeMap<String, isize>,
157}
158impl Aggregate {
159    pub fn new(data: &[&Metadata]) -> Self {
160        let mut agg = Self::default();
161        for &d in data.iter() {
162            agg.add(d)
163        }
164        agg
165    }
166    /// Add metadata to the aggregate.
167    pub fn add(&mut self, item: &Metadata) {
168        self.items += 1;
169        *self.kinds.entry(item.kind.clone()).or_insert(0) += 1;
170        for label in item.labels.iter() {
171            *self.labels.entry(label.clone()).or_insert(0) += 1;
172        }
173        for (key, value) in item.weights.iter() {
174            *self.weights.entry(key.clone()).or_insert(0.0) += value;
175        }
176        for key in item.annotations.keys() {
177            *self.annotations.entry(key.clone()).or_insert(0) += 1;
178        }
179    }
180    /// Subtract metadata from the aggregate.
181    pub fn subtract(&mut self, item: &Metadata) {
182        self.items -= 1;
183        if let Some(&num) = self.kinds.get(&item.kind) {
184            if num <= 1 {
185                self.kinds.remove(&item.kind);
186            } else {
187                *self.kinds.entry(item.kind.clone()).or_insert(1) -= 1;
188            }
189        }
190        for label in item.labels.iter() {
191            if let Some(&num) = self.labels.get(label) {
192                if num <= 1 {
193                    self.labels.remove(label);
194                } else {
195                    *self.labels.entry(label.clone()).or_insert(1) -= 1;
196                }
197            }
198        }
199        for (key, value) in item.weights.iter() {
200            *self.weights.entry(key.clone()).or_insert(0.0) -= value;
201        }
202        for key in item.annotations.keys() {
203            if let Some(&num) = self.annotations.get(key) {
204                if num <= 1 {
205                    self.annotations.remove(key);
206                } else {
207                    *self.annotations.entry(key.clone()).or_insert(1) -= 1;
208                }
209            }
210        }
211    }
212    /// Add another Aggregate's values to this.
213    pub fn extend(&mut self, other: Self) -> &Self {
214        self.items += other.items;
215        for (key, value) in other.kinds {
216            *self.kinds.entry(key).or_insert(0) += value;
217        }
218        for (key, value) in other.labels {
219            *self.labels.entry(key).or_insert(0) += value;
220        }
221        for (key, value) in other.weights {
222            *self.weights.entry(key).or_insert(0.0) += value;
223        }
224        for (key, value) in other.annotations {
225            *self.annotations.entry(key).or_insert(0) += value;
226        }
227        self
228    }
229    /// Get the sum for all given fields' values for a given aggregator.
230    pub fn sum(
231        &self,
232        aggregator: &Aggregator,
233        fields: Option<&BTreeSet<String>>,
234        absolute: bool,
235    ) -> f64 {
236        match aggregator {
237            Aggregator::Binary => {
238                if self.items > 0 {
239                    1.0
240                } else {
241                    0.0
242                }
243            }
244            Aggregator::Kinds => match fields {
245                None => self.kinds.values().sum::<isize>() as f64,
246                Some(fields) => fields
247                    .iter()
248                    .map(|field| self.value(aggregator, field, absolute))
249                    .sum(),
250            },
251            Aggregator::Labels => match fields {
252                None => self.labels.values().sum::<isize>() as f64,
253                Some(fields) => fields
254                    .iter()
255                    .map(|field| self.value(aggregator, field, absolute))
256                    .sum(),
257            },
258            Aggregator::Weights => match fields {
259                None => self.weights.values().sum(),
260                Some(fields) => fields
261                    .iter()
262                    .map(|field| self.value(aggregator, field, absolute))
263                    .sum(),
264            },
265            Aggregator::Annotations => match fields {
266                None => self.annotations.values().sum::<isize>() as f64,
267                Some(fields) => fields
268                    .iter()
269                    .map(|field| self.value(aggregator, field, absolute))
270                    .sum(),
271            },
272        }
273    }
274    /// Get the value that a field represents for a certain aggregator.
275    pub fn value<S: AsRef<str>>(&self, aggregator: &Aggregator, field: &S, absolute: bool) -> f64 {
276        let field = field.as_ref();
277        let value = match aggregator {
278            Aggregator::Binary => 1.0,
279            Aggregator::Kinds => self.kinds.get(field).map(|&x| x as f64).unwrap_or(0.0),
280            Aggregator::Labels => self.labels.get(field).map(|&x| x as f64).unwrap_or(0.0),
281            Aggregator::Weights => self.weights.get(field).copied().unwrap_or(0.0),
282            Aggregator::Annotations => self
283                .annotations
284                .get(field)
285                .map(|&x| x as f64)
286                .unwrap_or(0.0),
287        };
288        if absolute { value.abs() } else { value }
289    }
290    /// Get a fraction that a field represents for a certain aggregator.
291    pub fn fraction<S: AsRef<str>>(
292        &self,
293        aggregator: &Aggregator,
294        field: &S,
295        fields: Option<&BTreeSet<String>>,
296        absolute: bool,
297    ) -> f64 {
298        let sum = self.sum(aggregator, fields, absolute);
299        let value = self.value(aggregator, field, absolute);
300        if sum == 0.0 { 0.0 } else { value / sum }
301    }
302    /// Get all fractions for the given fields with respect to each other.
303    pub fn fractions<S: AsRef<str>>(
304        &self,
305        aggregator: &Aggregator,
306        fields: &[S],
307        factor: f64,
308        absolute: bool,
309    ) -> Vec<f64> {
310        let all: BTreeSet<String> = fields.iter().map(|s| s.as_ref().to_owned()).collect();
311        let sum = self.sum(aggregator, Some(&all), absolute);
312        let factor = { if sum == 0.0 { 1.0 } else { factor / sum } };
313        fields
314            .iter()
315            .map(|field| factor * self.value(aggregator, field, absolute))
316            .collect()
317    }
318}
319
320/// Track lower and upper bounds for float BTreeMaps.
321#[derive(Clone, Debug, Default, PartialEq)]
322#[cfg_attr(
323    feature = "serde",
324    derive(serde::Serialize, serde::Deserialize),
325    serde(default)
326)]
327pub struct Domains {
328    pub bounds: BTreeMap<String, (f64, f64)>,
329}
330impl Domains {
331    /// Update the lower and upper bounds according to
332    pub fn update(&mut self, values: &BTreeMap<String, f64>) {
333        for (key, &value) in values.iter() {
334            if let Some(entry) = self.bounds.get_mut(key) {
335                entry.0 = entry.0.min(value);
336                entry.1 = entry.1.max(value);
337            } else {
338                self.bounds.insert(key.to_owned(), (value, value));
339            }
340        }
341    }
342    /// Get the domains for a specified key.
343    pub fn get<S: AsRef<str>>(&self, key: &S) -> Option<&(f64, f64)> {
344        self.bounds.get(key.as_ref())
345    }
346    /// Interpolate a value for a specified key's domain. Returns 1.0 when the domain has no size
347    /// (lower and upper bound are equal).
348    pub fn interpolate<S: AsRef<str>>(&self, key: &S, value: f64) -> Option<f64> {
349        self.get(key).map(|&(lower, upper)| {
350            if lower == upper {
351                1.0
352            } else {
353                (value - lower) / (upper - lower)
354            }
355        })
356    }
357}
358
359/// Filtering options on metadata.
360#[derive(Clone, Default, Debug, PartialEq)]
361#[cfg_attr(
362    feature = "serde",
363    derive(serde::Serialize, serde::Deserialize),
364    serde(default)
365)]
366pub struct MetadataFilter {
367    pub kinds: Option<Vec<String>>,
368    pub labels: Option<BTreeSet<String>>,
369}
370impl MetadataFilter {
371    /// Whether some metadata holding object satisfies this filter.
372    pub fn satisfies<T: Meta>(&self, instance: &T) -> bool {
373        self.satisfies_kinds(instance) && self.satisfies_labels(instance)
374    }
375
376    /// Whether some metadata holding object satisfies the set kinds.
377    pub fn satisfies_kinds<T: Meta>(&self, instance: &T) -> bool {
378        if let Some(kinds) = &self.kinds {
379            kinds.contains(&instance.kind().to_owned())
380        } else {
381            true
382        }
383    }
384    /// Whether some metadata holding object satisfies the set labels.
385    pub fn satisfies_labels<T: Meta>(&self, instance: &T) -> bool {
386        {
387            if let Some(labels) = &self.labels {
388                !labels.is_disjoint(instance.labels())
389            } else {
390                true
391            }
392        }
393    }
394}