sim_lib_discrete_graph/
control.rs1use std::time::{Duration, Instant};
4
5use crate::GraphError;
6
7#[derive(Clone, Copy, Debug, PartialEq, Eq)]
9pub struct AlgorithmWorkCosts {
10 pub cell: u64,
12 pub edge: u64,
14}
15
16impl Default for AlgorithmWorkCosts {
17 fn default() -> Self {
18 Self { cell: 1, edge: 1 }
19 }
20}
21
22#[derive(Clone, Debug, Default, PartialEq, Eq)]
24pub struct AlgorithmControl {
25 pub max_work: Option<u64>,
27 pub max_memory_cells: Option<usize>,
29 pub max_time: Option<Duration>,
31 pub costs: AlgorithmWorkCosts,
33}
34
35impl AlgorithmControl {
36 pub fn with_max_work(mut self, max_work: u64) -> Self {
38 self.max_work = Some(max_work);
39 self
40 }
41
42 pub fn with_max_memory_cells(mut self, max_memory_cells: usize) -> Self {
44 self.max_memory_cells = Some(max_memory_cells);
45 self
46 }
47
48 pub fn with_max_time(mut self, max_time: Duration) -> Self {
50 self.max_time = Some(max_time);
51 self
52 }
53
54 pub fn with_costs(mut self, costs: AlgorithmWorkCosts) -> Self {
56 self.costs = costs;
57 self
58 }
59}
60
61pub trait AlgorithmInterrupt {
63 fn is_cancelled(&self) -> bool;
65}
66
67#[derive(Clone, Copy, Debug, Default)]
69pub struct NeverInterrupt;
70
71impl AlgorithmInterrupt for NeverInterrupt {
72 fn is_cancelled(&self) -> bool {
73 false
74 }
75}
76
77#[derive(Clone, Debug, PartialEq, Eq)]
79pub struct AlgorithmReceipt {
80 pub work_used: u64,
82 pub cells: u64,
84 pub edges: u64,
86 pub peak_memory_cells: usize,
88 pub cell_work: u64,
90 pub edge_work: u64,
92}
93
94impl AlgorithmReceipt {
95 pub(crate) fn validate(&self) -> Result<(), GraphError> {
96 let cell_work = self
97 .cells
98 .checked_mul(self.cell_work)
99 .ok_or_else(|| GraphError::CertificateInvalid("cell work overflowed".to_owned()))?;
100 let edge_work = self
101 .edges
102 .checked_mul(self.edge_work)
103 .ok_or_else(|| GraphError::CertificateInvalid("edge work overflowed".to_owned()))?;
104 let expected = cell_work
105 .checked_add(edge_work)
106 .ok_or_else(|| GraphError::CertificateInvalid("total work overflowed".to_owned()))?;
107 if self.cell_work == 0 || self.edge_work == 0 {
108 return Err(GraphError::CertificateInvalid(
109 "algorithm work charges must be positive".to_owned(),
110 ));
111 }
112 if self.work_used != expected {
113 return Err(GraphError::CertificateInvalid(
114 "algorithm receipt work total is inconsistent".to_owned(),
115 ));
116 }
117 Ok(())
118 }
119}
120
121pub(crate) struct WorkMeter<'a> {
122 control: &'a AlgorithmControl,
123 interrupt: &'a dyn AlgorithmInterrupt,
124 started: Instant,
125 receipt: AlgorithmReceipt,
126}
127
128impl<'a> WorkMeter<'a> {
129 pub(crate) fn new(
130 control: &'a AlgorithmControl,
131 interrupt: &'a dyn AlgorithmInterrupt,
132 peak_memory_cells: usize,
133 ) -> Result<Self, GraphError> {
134 if control.costs.cell == 0 || control.costs.edge == 0 {
135 return Err(GraphError::InvalidControl(
136 "cell and edge work costs must be positive".to_owned(),
137 ));
138 }
139 if control
140 .max_memory_cells
141 .is_some_and(|limit| peak_memory_cells > limit)
142 {
143 return Err(GraphError::ControlStopped(format!(
144 "memory-cell bound reached: required {peak_memory_cells}"
145 )));
146 }
147 Ok(Self {
148 control,
149 interrupt,
150 started: Instant::now(),
151 receipt: AlgorithmReceipt {
152 work_used: 0,
153 cells: 0,
154 edges: 0,
155 peak_memory_cells,
156 cell_work: control.costs.cell,
157 edge_work: control.costs.edge,
158 },
159 })
160 }
161
162 pub(crate) fn cell(&mut self) -> Result<(), GraphError> {
163 self.charge(self.control.costs.cell)?;
164 self.receipt.cells = self
165 .receipt
166 .cells
167 .checked_add(1)
168 .ok_or_else(|| GraphError::WeightOverflow("cell counter".to_owned()))?;
169 Ok(())
170 }
171
172 pub(crate) fn edge(&mut self) -> Result<(), GraphError> {
173 self.charge(self.control.costs.edge)?;
174 self.receipt.edges = self
175 .receipt
176 .edges
177 .checked_add(1)
178 .ok_or_else(|| GraphError::WeightOverflow("edge counter".to_owned()))?;
179 Ok(())
180 }
181
182 pub(crate) fn finish(self) -> AlgorithmReceipt {
183 self.receipt
184 }
185
186 fn charge(&mut self, work: u64) -> Result<(), GraphError> {
187 if self.interrupt.is_cancelled() {
188 return Err(GraphError::ControlStopped(
189 "algorithm interrupt cancelled the run".to_owned(),
190 ));
191 }
192 if self
193 .control
194 .max_time
195 .is_some_and(|limit| self.started.elapsed() >= limit)
196 {
197 return Err(GraphError::ControlStopped(
198 "algorithm time bound reached".to_owned(),
199 ));
200 }
201 let next = self
202 .receipt
203 .work_used
204 .checked_add(work)
205 .ok_or_else(|| GraphError::WeightOverflow("work counter".to_owned()))?;
206 if self.control.max_work.is_some_and(|limit| next > limit) {
207 return Err(GraphError::ControlStopped(
208 "algorithm work bound reached".to_owned(),
209 ));
210 }
211 self.receipt.work_used = next;
212 Ok(())
213 }
214}