1use std::cmp::Ordering;
30use std::collections::VecDeque;
31
32pub const LIMIT: i64 = 1 << 24;
34
35pub const NODES: usize = 1 << 31;
37
38#[derive(Clone, Copy, Debug, PartialEq, Eq)]
40pub struct Edge {
41 pub from: usize,
42 pub to: usize,
43 pub delay: i64,
44 pub registers: i64,
45}
46
47#[derive(Clone, Debug, PartialEq, Eq)]
49pub enum Bound {
50 Acyclic,
52 CombinationalLoop(Vec<usize>),
54 Ratio { delay: i64, registers: i64, cycle: Vec<usize>, potential: Vec<i128> },
57}
58
59impl Bound {
60 pub fn period(&self) -> Option<(i64, i64)> {
62 match self {
63 Bound::Ratio { delay, registers, .. } => {
64 let g = gcd(*delay, *registers).max(1);
65 Some((delay / g, registers / g))
66 }
67 _ => None,
68 }
69 }
70}
71
72fn gcd(mut a: i64, mut b: i64) -> i64 {
73 while b != 0 {
74 (a, b) = (b, a % b);
75 }
76 a.abs()
77}
78
79#[derive(Clone, Copy, Debug, PartialEq, Eq)]
81pub enum GraphError {
82 OutOfRange { edge: usize },
84 Negative { edge: usize },
86 TooLarge { edge: usize },
88 TooManyNodes,
90}
91
92fn validate(nodes: usize, edges: &[Edge]) -> Result<(), GraphError> {
93 if nodes > NODES {
94 return Err(GraphError::TooManyNodes);
95 }
96 for (k, e) in edges.iter().enumerate() {
97 if e.from >= nodes || e.to >= nodes {
98 return Err(GraphError::OutOfRange { edge: k });
99 }
100 if e.delay < 0 || e.registers < 0 {
101 return Err(GraphError::Negative { edge: k });
102 }
103 if e.delay >= LIMIT || e.registers >= LIMIT {
104 return Err(GraphError::TooLarge { edge: k });
105 }
106 }
107 Ok(())
108}
109
110struct Out {
112 start: Vec<usize>,
113 list: Vec<usize>,
114}
115
116impl Out {
117 fn new(nodes: usize, edges: &[Edge]) -> Out {
118 let mut start = vec![0usize; nodes + 1];
119 for e in edges {
120 start[e.from + 1] += 1;
121 }
122 for n in 0..nodes {
123 start[n + 1] += start[n];
124 }
125 let mut fill = start.clone();
126 let mut list = vec![0usize; edges.len()];
127 for (k, e) in edges.iter().enumerate() {
128 list[fill[e.from]] = k;
129 fill[e.from] += 1;
130 }
131 Out { start, list }
132 }
133
134 fn of(&self, n: usize) -> &[usize] {
135 &self.list[self.start[n]..self.start[n + 1]]
136 }
137}
138
139fn find_cycle(nodes: usize, edges: &[Edge], keep: impl Fn(&Edge) -> bool) -> Option<Vec<usize>> {
141 let out = Out::new(nodes, edges);
142 let mut state = vec![0u8; nodes];
144 let mut via: Vec<usize> = vec![usize::MAX; nodes];
145 for root in 0..nodes {
146 if state[root] != 0 {
147 continue;
148 }
149 let mut stack: Vec<(usize, usize)> = vec![(root, 0)];
150 state[root] = 1;
151 while let Some(top) = stack.last_mut() {
152 let (n, i) = *top;
153 let Some(&k) = out.of(n).get(i) else {
154 state[n] = 2;
155 stack.pop();
156 continue;
157 };
158 top.1 += 1;
159 if !keep(&edges[k]) {
160 continue;
161 }
162 let t = edges[k].to;
163 match state[t] {
164 0 => {
165 state[t] = 1;
166 via[t] = k;
167 stack.push((t, 0));
168 }
169 1 => {
170 let mut cycle = vec![k];
171 let mut at = n;
172 while at != t {
173 let e = via[at];
174 cycle.push(e);
175 at = edges[e].from;
176 }
177 cycle.reverse();
178 return Some(cycle);
179 }
180 _ => {}
181 }
182 }
183 }
184 None
185}
186
187fn components(nodes: usize, edges: &[Edge], out: &Out) -> (Vec<Vec<usize>>, Vec<usize>) {
190 let mut index = vec![usize::MAX; nodes];
191 let mut low = vec![0usize; nodes];
192 let mut on = vec![false; nodes];
193 let mut stack: Vec<usize> = Vec::new();
194 let mut comps: Vec<Vec<usize>> = Vec::new();
195 let mut next = 0;
196 for root in 0..nodes {
197 if index[root] != usize::MAX {
198 continue;
199 }
200 let mut call: Vec<(usize, usize)> = vec![(root, 0)];
201 index[root] = next;
202 low[root] = next;
203 next += 1;
204 stack.push(root);
205 on[root] = true;
206 while let Some(top) = call.last_mut() {
207 let (v, i) = *top;
208 if let Some(&k) = out.of(v).get(i) {
209 top.1 += 1;
210 let w = edges[k].to;
211 if index[w] == usize::MAX {
212 index[w] = next;
213 low[w] = next;
214 next += 1;
215 stack.push(w);
216 on[w] = true;
217 call.push((w, 0));
218 } else if on[w] {
219 low[v] = low[v].min(index[w]);
220 }
221 continue;
222 }
223 call.pop();
224 if let Some(&(u, _)) = call.last() {
225 low[u] = low[u].min(low[v]);
226 }
227 if low[v] == index[v] {
228 let mut members = Vec::new();
229 loop {
230 let w = stack.pop().expect("v is on the stack");
231 on[w] = false;
232 members.push(w);
233 if w == v {
234 break;
235 }
236 }
237 comps.push(members);
238 }
239 }
240 }
241 comps.reverse();
242 let mut comp = vec![0usize; nodes];
243 for (c, members) in comps.iter().enumerate() {
244 for &v in members {
245 comp[v] = c;
246 }
247 }
248 (comps, comp)
249}
250
251#[derive(Clone, Copy, Debug, PartialEq, Eq)]
253struct Ratio {
254 p: i64,
255 q: i64,
256}
257
258impl Ratio {
259 fn new(p: i64, q: i64) -> Ratio {
260 let g = gcd(p, q).max(1);
261 Ratio { p: p / g, q: q / g }
262 }
263
264 fn weight(self, e: &Edge) -> i128 {
265 self.q as i128 * e.delay as i128 - self.p as i128 * e.registers as i128
266 }
267}
268
269impl Ord for Ratio {
270 fn cmp(&self, other: &Ratio) -> Ordering {
271 (self.p as i128 * other.q as i128).cmp(&(other.p as i128 * self.q as i128))
272 }
273}
274
275impl PartialOrd for Ratio {
276 fn partial_cmp(&self, other: &Ratio) -> Option<Ordering> {
277 Some(self.cmp(other))
278 }
279}
280
281fn sums(edges: &[Edge], cycle: &[usize]) -> (i64, i64) {
282 cycle.iter().fold((0, 0), |(d, r), &k| (d + edges[k].delay, r + edges[k].registers))
283}
284
285fn howard(edges: &[Edge], out: &Out, members: &[usize], comp: &[usize], c: usize) -> (Ratio, Vec<usize>) {
288 let inside = |k: &&usize| comp[edges[**k].to] == c;
289 let mut policy: Vec<usize> = vec![usize::MAX; comp.len()];
290 for &v in members {
291 policy[v] = *out.of(v).iter().filter(inside).max_by_key(|&&k| edges[k].delay).expect("a component's node has an edge in it");
292 }
293 let mut ratio: Vec<Ratio> = vec![Ratio { p: 0, q: 1 }; comp.len()];
294 let mut bias: Vec<i128> = vec![0; comp.len()];
295 let mut state: Vec<u8> = vec![0; comp.len()];
296 let mut path: Vec<usize> = Vec::new();
297 loop {
298 for &v in members {
300 state[v] = 0;
301 }
302 let mut found: Option<(Ratio, Vec<usize>)> = None;
303 for &start in members {
304 if state[start] != 0 {
305 continue;
306 }
307 path.clear();
308 let mut at = start;
309 while state[at] == 0 {
310 state[at] = 1;
311 path.push(at);
312 at = edges[policy[at]].to;
313 }
314 let mut tail = path.len();
315 if state[at] == 1 {
316 let pos = path.iter().position(|&n| n == at).expect("the walk met itself");
317 let cycle = &path[pos..];
318 let edges_of: Vec<usize> = cycle.iter().map(|&n| policy[n]).collect();
319 let (d, r) = sums(edges, &edges_of);
320 let lambda = Ratio::new(d, r);
321 let root = (0..cycle.len()).min_by_key(|&i| cycle[i]).expect("a cycle has a node");
322 bias[cycle[root]] = 0;
323 ratio[cycle[root]] = lambda;
324 state[cycle[root]] = 2;
325 for k in 1..cycle.len() {
326 let i = (root + cycle.len() - k) % cycle.len();
327 let u = cycle[i];
328 let t = cycle[(i + 1) % cycle.len()];
329 bias[u] = lambda.weight(&edges[policy[u]]) + bias[t];
330 ratio[u] = lambda;
331 state[u] = 2;
332 }
333 if found.as_ref().map_or(true, |(best, _)| lambda > *best) {
334 found = Some((lambda, edges_of));
335 }
336 tail = pos;
337 }
338 for &u in path[..tail].iter().rev() {
339 let t = edges[policy[u]].to;
340 ratio[u] = ratio[t];
341 bias[u] = ratio[u].weight(&edges[policy[u]]) + bias[t];
342 state[u] = 2;
343 }
344 }
345 let mut changed = false;
347 for &u in members {
348 let mut best = ratio[u];
349 for &k in out.of(u).iter().filter(inside) {
350 let r = ratio[edges[k].to];
351 if r > best {
352 best = r;
353 policy[u] = k;
354 changed = true;
355 }
356 }
357 }
358 if !changed {
359 for &u in members {
360 let mut best = bias[u];
361 for &k in out.of(u).iter().filter(inside) {
362 let t = edges[k].to;
363 if ratio[t] != ratio[u] {
364 continue;
365 }
366 let value = ratio[u].weight(&edges[k]) + bias[t];
367 if value > best {
368 best = value;
369 policy[u] = k;
370 changed = true;
371 }
372 }
373 }
374 }
375 if !changed {
376 return found.expect("a component with an edge has a policy cycle");
377 }
378 }
379}
380
381fn potential(nodes: usize, edges: &[Edge], out: &Out, comps: &[Vec<usize>], comp: &[usize], lambda: Ratio) -> Vec<i128> {
384 let mut dist = vec![0i128; nodes];
385 let mut queued = vec![false; nodes];
386 let mut queue: VecDeque<usize> = VecDeque::new();
387 for (c, members) in comps.iter().enumerate() {
388 queue.extend(members.iter().copied());
389 for &v in members {
390 queued[v] = true;
391 }
392 while let Some(u) = queue.pop_front() {
393 queued[u] = false;
394 for &k in out.of(u) {
395 let e = &edges[k];
396 if comp[e.to] != c {
397 continue;
398 }
399 let d = dist[u] + lambda.weight(e);
400 if d > dist[e.to] {
401 dist[e.to] = d;
402 if !queued[e.to] {
403 queued[e.to] = true;
404 queue.push_back(e.to);
405 }
406 }
407 }
408 }
409 for &u in members {
410 for &k in out.of(u) {
411 let e = &edges[k];
412 if comp[e.to] != c {
413 dist[e.to] = dist[e.to].max(dist[u] + lambda.weight(e));
414 }
415 }
416 }
417 }
418 dist
419}
420
421pub fn max_cycle_ratio(nodes: usize, edges: &[Edge]) -> Result<Bound, GraphError> {
423 validate(nodes, edges)?;
424 if let Some(cycle) = find_cycle(nodes, edges, |e| e.registers == 0) {
425 return Ok(Bound::CombinationalLoop(cycle));
426 }
427 let out = Out::new(nodes, edges);
428 let (comps, comp) = components(nodes, edges, &out);
429 let mut best: Option<(Ratio, Vec<usize>)> = None;
430 for (c, members) in comps.iter().enumerate() {
431 let cyclic = members.len() > 1 || out.of(members[0]).iter().any(|&k| edges[k].to == members[0]);
432 if !cyclic {
433 continue;
434 }
435 let (lambda, cycle) = howard(edges, &out, members, &comp, c);
436 if best.as_ref().map_or(true, |(b, _)| lambda > *b) {
437 best = Some((lambda, cycle));
438 }
439 }
440 let Some((lambda, cycle)) = best else {
441 return Ok(Bound::Acyclic);
442 };
443 let (delay, registers) = sums(edges, &cycle);
444 let scale = (registers / lambda.q) as i128;
445 let potential = potential(nodes, edges, &out, &comps, &comp, lambda).into_iter().map(|d| d * scale).collect();
446 Ok(Bound::Ratio { delay, registers, cycle, potential })
447}
448
449pub fn check(nodes: usize, edges: &[Edge], bound: &Bound) -> bool {
453 if validate(nodes, edges).is_err() {
454 return false;
455 }
456 let is_cycle = |cycle: &[usize]| {
457 let mut seen = vec![false; nodes];
458 !cycle.is_empty()
459 && cycle.iter().all(|&k| k < edges.len() && !std::mem::replace(&mut seen[edges[k].from], true))
460 && cycle.iter().enumerate().all(|(i, &k)| edges[k].to == edges[cycle[(i + 1) % cycle.len()]].from)
461 };
462 match bound {
463 Bound::Acyclic => find_cycle(nodes, edges, |_| true).is_none(),
464 Bound::CombinationalLoop(cycle) => is_cycle(cycle) && cycle.iter().all(|&k| edges[k].registers == 0),
465 Bound::Ratio { delay, registers, cycle, potential } => {
466 if !is_cycle(cycle) || sums(edges, cycle) != (*delay, *registers) || *registers <= 0 || potential.len() != nodes {
467 return false;
468 }
469 edges.iter().all(|e| {
470 let w = *registers as i128 * e.delay as i128 - *delay as i128 * e.registers as i128;
471 match potential[e.from].checked_add(w) {
472 Some(reach) => potential[e.to] >= reach,
473 None => w < 0,
474 }
475 })
476 }
477 }
478}
479
480#[cfg(test)]
481#[path = "../../logicaffeine_tests/unit/logicaffeine_proof/cycle_ratio/tests.rs"]
482mod tests;