Skip to main content

graphrecords_query/optimizer/
phase.rs

1use super::stats::Stats;
2pub use graphrecords_macros::PhaseLabel;
3use std::{
4    any::Any,
5    fmt::{self, Debug, Display, Formatter},
6    hash::{Hash, Hasher},
7    sync::Arc,
8};
9
10pub trait PhaseLabel: Any + Send + Sync {
11    fn dyn_eq(&self, other: &dyn PhaseLabel) -> bool;
12
13    fn dyn_hash(&self, state: &mut dyn Hasher);
14
15    fn dyn_debug(&self, formatter: &mut Formatter<'_>) -> fmt::Result;
16
17    fn as_any(&self) -> &dyn Any;
18}
19
20pub struct PhaseId(Arc<dyn PhaseLabel>);
21
22impl PhaseId {
23    #[must_use]
24    pub fn new(label: impl PhaseLabel) -> Self {
25        Self(Arc::new(label))
26    }
27}
28
29impl Clone for PhaseId {
30    fn clone(&self) -> Self {
31        Self(Arc::clone(&self.0))
32    }
33}
34
35impl PartialEq for PhaseId {
36    fn eq(&self, other: &Self) -> bool {
37        self.0.dyn_eq(other.0.as_ref())
38    }
39}
40
41impl Eq for PhaseId {}
42
43impl Hash for PhaseId {
44    fn hash<H: Hasher>(&self, state: &mut H) {
45        self.0.dyn_hash(state);
46    }
47}
48
49impl Debug for PhaseId {
50    fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
51        self.0.dyn_debug(formatter)
52    }
53}
54
55pub const DEFAULT_MAX_ITERATIONS: usize = 100;
56
57#[derive(Clone, Copy, PartialEq, Eq, Debug)]
58pub enum FixpointPolicy {
59    Once,
60    Fixpoint { max_iterations: usize },
61}
62
63impl FixpointPolicy {
64    #[must_use]
65    pub const fn once() -> Self {
66        Self::Once
67    }
68
69    #[must_use]
70    pub const fn fixpoint() -> Self {
71        Self::Fixpoint {
72            max_iterations: DEFAULT_MAX_ITERATIONS,
73        }
74    }
75
76    #[must_use]
77    pub const fn fixpoint_with(max_iterations: usize) -> Self {
78        Self::Fixpoint { max_iterations }
79    }
80}
81
82#[derive(Clone, PartialEq, Eq, Debug)]
83pub enum StopReason {
84    CompletedOnce,
85    Converged { iterations: usize },
86    Oscillation { iterations: usize },
87    IterationLimit { iterations: usize },
88    Skipped,
89    Empty,
90}
91
92#[derive(Clone, Debug)]
93pub struct PhaseOutcome {
94    pub label: PhaseId,
95    pub stop: StopReason,
96}
97
98#[derive(Clone, Debug, Default)]
99pub struct OptimizationReport {
100    pub phases: Vec<PhaseOutcome>,
101}
102
103impl OptimizationReport {
104    #[must_use]
105    pub const fn display(&self) -> ReportDisplay<'_> {
106        ReportDisplay(self)
107    }
108}
109
110pub struct ReportDisplay<'a>(&'a OptimizationReport);
111
112impl Display for ReportDisplay<'_> {
113    fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
114        for (index, outcome) in self.0.phases.iter().enumerate() {
115            if index > 0 {
116                writeln!(formatter)?;
117            }
118
119            write!(formatter, "{:?}: ", outcome.label)?;
120
121            match &outcome.stop {
122                StopReason::Converged { iterations } => {
123                    write!(formatter, "converged ({iterations} iterations)")?;
124                }
125                StopReason::CompletedOnce => formatter.write_str("completed once")?,
126                StopReason::Oscillation { iterations } => {
127                    write!(formatter, "oscillation after {iterations} iterations")?;
128                }
129                StopReason::IterationLimit { iterations } => {
130                    write!(formatter, "did not converge within {iterations} iterations")?;
131                }
132                StopReason::Skipped => formatter.write_str("skipped (run condition false)")?,
133                StopReason::Empty => formatter.write_str("skipped (no rules)")?,
134            }
135        }
136
137        Ok(())
138    }
139}
140
141pub(super) type RunCondition = Box<dyn Fn(&Stats<'_>) -> bool + Send + Sync>;