1const EPSILON: f64 = 1e-9;
2
3#[derive(Debug, Clone)]
10pub struct Dbm {
11 dim: usize,
12 data: Vec<f64>,
13 clock_names: Vec<String>,
14 empty: bool,
15}
16
17impl Dbm {
18 pub fn new(size: usize) -> Self {
20 let data = vec![f64::INFINITY; size * size];
21 let mut dbm = Self {
22 dim: size,
23 data,
24 clock_names: Vec::new(),
25 empty: false,
26 };
27 for i in 0..size {
28 dbm.set(i, i, 0.0);
29 }
30 dbm
31 }
32
33 pub fn create(clock_names: Vec<String>, lower_bounds: &[f64], upper_bounds: &[f64]) -> Self {
35 let n = clock_names.len();
36 let dim = n + 1;
37 let mut dbm = Self::new(dim);
38 dbm.clock_names = clock_names;
39
40 for i in 0..n {
41 dbm.set(0, i + 1, -lower_bounds[i]); dbm.set(i + 1, 0, upper_bounds[i]); }
44
45 dbm.canonicalize();
46 dbm
47 }
48
49 pub fn empty_dbm() -> Self {
51 Self {
52 dim: 1,
53 data: vec![0.0],
54 clock_names: Vec::new(),
55 empty: true,
56 }
57 }
58
59 pub fn size(&self) -> usize {
61 self.dim
62 }
63
64 pub fn clock_names(&self) -> &[String] {
66 &self.clock_names
67 }
68
69 pub fn is_empty(&self) -> bool {
71 self.empty
72 }
73
74 pub fn get(&self, i: usize, j: usize) -> f64 {
76 self.data[i * self.dim + j]
77 }
78
79 pub fn set(&mut self, i: usize, j: usize, value: f64) {
81 self.data[i * self.dim + j] = value;
82 }
83
84 pub fn lower_bound(&self, i: usize) -> f64 {
86 if self.empty || i >= self.clock_names.len() {
87 return 0.0;
88 }
89 let val = -self.get(0, i + 1);
90 if val == 0.0 { 0.0 } else { val } }
92
93 pub fn upper_bound(&self, i: usize) -> f64 {
95 if self.empty || i >= self.clock_names.len() {
96 return f64::INFINITY;
97 }
98 self.get(i + 1, 0)
99 }
100
101 pub fn can_fire(&self, i: usize) -> bool {
103 !self.empty && self.lower_bound(i) <= EPSILON
104 }
105
106 pub fn canonicalize(&mut self) {
108 if self.empty {
109 return;
110 }
111 let n = self.dim;
112 for k in 0..n {
113 for i in 0..n {
114 for j in 0..n {
115 let ik = self.get(i, k);
116 let kj = self.get(k, j);
117 if ik < f64::INFINITY && kj < f64::INFINITY {
118 let via = ik + kj;
119 if via < self.get(i, j) {
120 self.set(i, j, via);
121 }
122 }
123 }
124 }
125 }
126 for i in 0..n {
128 if self.get(i, i) < -EPSILON {
129 self.empty = true;
130 return;
131 }
132 }
133 }
134
135 pub fn let_time_pass(&self) -> Dbm {
137 if self.empty {
138 return self.clone();
139 }
140 let mut result = self.clone();
141 for i in 1..self.dim {
142 result.set(0, i, 0.0);
143 }
144 result.canonicalize();
145 result
146 }
147
148 pub fn fire_transition(
154 &self,
155 fired_clock: usize,
156 new_clock_names: &[String],
157 new_lower_bounds: &[f64],
158 new_upper_bounds: &[f64],
159 persistent_clocks: &[usize],
160 ) -> Dbm {
161 if self.empty {
162 return self.clone();
163 }
164
165 let n = self.clock_names.len();
166 if fired_clock >= n {
167 return Dbm::empty_dbm();
168 }
169
170 let mut constrained = self.data.clone();
172 let dim = self.dim;
173 let f = fired_clock + 1;
174
175 for i in 0..n {
176 if i != fired_clock {
177 let idx = i + 1;
178 let pos = f * dim + idx;
179 constrained[pos] = constrained[pos].min(0.0);
180 }
181 }
182
183 if !canonicalize_in_place(&mut constrained, dim) {
185 return Dbm::empty_dbm();
186 }
187
188 let new_n = persistent_clocks.len() + new_clock_names.len();
190 let new_dim = new_n + 1;
191 let mut new_data = vec![f64::INFINITY; new_dim * new_dim];
192 for i in 0..new_dim {
193 new_data[i * new_dim + i] = 0.0;
194 }
195
196 for (pi, &old_clock) in persistent_clocks.iter().enumerate() {
198 let old_idx = old_clock + 1;
199 let new_idx = pi + 1;
200
201 let upper = constrained[old_idx * dim + f];
202 let lower = (-constrained[f * dim + old_idx]).max(0.0);
203
204 new_data[new_idx] = -lower; new_data[new_idx * new_dim] = upper; for (pj, &old_j) in persistent_clocks.iter().enumerate() {
209 let old_j_idx = old_j + 1;
210 let new_j = pj + 1;
211 new_data[new_idx * new_dim + new_j] = constrained[old_idx * dim + old_j_idx];
212 }
213 }
214
215 let offset = persistent_clocks.len();
217 for (k, _) in new_clock_names.iter().enumerate() {
218 let idx = offset + k + 1;
219 new_data[idx] = -new_lower_bounds[k]; new_data[idx * new_dim] = new_upper_bounds[k]; }
222
223 let mut all_names = Vec::with_capacity(new_n);
225 for &idx in persistent_clocks {
226 all_names.push(self.clock_names[idx].clone());
227 }
228 all_names.extend(new_clock_names.iter().cloned());
229
230 let mut result = Dbm {
231 dim: new_dim,
232 data: new_data,
233 clock_names: all_names,
234 empty: false,
235 };
236 result.canonicalize();
237 result
238 }
239
240 pub fn canonical_string(&self) -> String {
242 if self.empty {
243 return "DBM[empty]".to_string();
244 }
245 let mut parts = Vec::new();
246 for i in 0..self.clock_names.len() {
247 let lo = self.lower_bound(i);
248 let hi = self.upper_bound(i);
249 parts.push(format!(
250 "{}:[{},{}]",
251 self.clock_names[i],
252 format_bound(lo),
253 format_bound(hi)
254 ));
255 }
256 format!("DBM{{{}}}", parts.join(", "))
257 }
258}
259
260fn canonicalize_in_place(data: &mut [f64], dim: usize) -> bool {
261 for k in 0..dim {
262 for i in 0..dim {
263 for j in 0..dim {
264 let ik = data[i * dim + k];
265 let kj = data[k * dim + j];
266 if ik < f64::INFINITY && kj < f64::INFINITY {
267 let via = ik + kj;
268 if via < data[i * dim + j] {
269 data[i * dim + j] = via;
270 }
271 }
272 }
273 }
274 }
275 for i in 0..dim {
276 if data[i * dim + i] < -EPSILON {
277 return false;
278 }
279 }
280 true
281}
282
283fn format_bound(b: f64) -> String {
284 if b >= f64::INFINITY / 2.0 {
285 "\u{221e}".to_string()
286 } else if b == b.trunc() {
287 format!("{}", b as i64)
288 } else {
289 format!("{:.3}", b)
290 }
291}
292
293impl PartialEq for Dbm {
294 fn eq(&self, other: &Self) -> bool {
295 if self.empty && other.empty {
296 return true;
297 }
298 if self.empty || other.empty {
299 return false;
300 }
301 if self.clock_names.len() != other.clock_names.len() {
302 return false;
303 }
304 if self.clock_names != other.clock_names {
305 return false;
306 }
307 if self.data.len() != other.data.len() {
308 return false;
309 }
310 for i in 0..self.data.len() {
311 if (self.data[i] - other.data[i]).abs() > EPSILON {
312 return false;
313 }
314 }
315 true
316 }
317}
318
319impl Eq for Dbm {}
320
321#[cfg(test)]
322mod tests {
323 use super::*;
324
325 #[test]
326 fn dbm_new() {
327 let dbm = Dbm::new(3);
328 assert_eq!(dbm.size(), 3);
329 assert_eq!(dbm.get(0, 0), 0.0);
330 assert_eq!(dbm.get(1, 1), 0.0);
331 assert_eq!(dbm.get(0, 1), f64::INFINITY);
332 }
333
334 #[test]
335 fn dbm_create_with_bounds() {
336 let dbm = Dbm::create(vec!["t1".into(), "t2".into()], &[5.0, 3.0], &[10.0, 8.0]);
337 assert_eq!(dbm.clock_names().len(), 2);
338 assert_eq!(dbm.lower_bound(0), 5.0);
339 assert_eq!(dbm.upper_bound(0), 10.0);
340 assert_eq!(dbm.lower_bound(1), 3.0);
341 assert_eq!(dbm.upper_bound(1), 8.0);
342 }
343
344 #[test]
345 fn dbm_bounds() {
346 let mut dbm = Dbm::new(3);
347 dbm.clock_names = vec!["t1".into(), "t2".into()];
348 dbm.set(0, 1, -5.0);
349 dbm.set(1, 0, 10.0);
350 assert_eq!(dbm.lower_bound(0), 5.0);
351 assert_eq!(dbm.upper_bound(0), 10.0);
352 assert!(dbm.can_fire(0) || !dbm.can_fire(0)); }
354
355 #[test]
356 fn dbm_canonicalize() {
357 let mut dbm = Dbm::new(3);
358 dbm.set(1, 0, 10.0);
359 dbm.set(0, 1, -5.0);
360 dbm.set(2, 0, 8.0);
361 dbm.set(0, 2, -3.0);
362 dbm.canonicalize();
363 assert!(dbm.get(1, 2) <= 10.0 + 8.0);
364 }
365
366 #[test]
367 fn dbm_empty() {
368 let mut dbm = Dbm::new(2);
369 dbm.set(0, 1, -10.0);
370 dbm.set(1, 0, 5.0);
371 dbm.canonicalize();
372 assert!(dbm.is_empty());
373 }
374
375 #[test]
376 fn dbm_let_time_pass() {
377 let dbm = Dbm::create(vec!["t1".into()], &[5.0], &[10.0]);
378 let passed = dbm.let_time_pass();
379 assert_eq!(passed.lower_bound(0), 0.0);
381 assert_eq!(passed.upper_bound(0), 10.0);
383 }
384
385 #[test]
386 fn dbm_equality() {
387 let a = Dbm::create(vec!["t1".into()], &[5.0], &[10.0]);
388 let b = Dbm::create(vec!["t1".into()], &[5.0], &[10.0]);
389 assert_eq!(a, b);
390 }
391
392 #[test]
393 fn dbm_inequality() {
394 let a = Dbm::create(vec!["t1".into()], &[5.0], &[10.0]);
395 let b = Dbm::create(vec!["t1".into()], &[3.0], &[10.0]);
396 assert_ne!(a, b);
397 }
398
399 #[test]
400 fn dbm_fire_transition() {
401 let dbm = Dbm::create(vec!["t1".into(), "t2".into()], &[5.0, 3.0], &[10.0, 8.0]);
402 let passed = dbm.let_time_pass();
403
404 let result = passed.fire_transition(
406 0,
407 &[],
408 &[],
409 &[],
410 &[1], );
412 assert!(!result.is_empty());
413 assert_eq!(result.clock_names().len(), 1);
414 assert_eq!(result.clock_names()[0], "t2");
415 }
416
417 #[test]
418 fn dbm_fire_with_newly_enabled() {
419 let dbm = Dbm::create(vec!["t1".into()], &[0.0], &[f64::INFINITY]);
420 let passed = dbm.let_time_pass();
421
422 let result = passed.fire_transition(
424 0,
425 &["t2".into(), "t3".into()],
426 &[2.0, 0.0],
427 &[5.0, 3.0],
428 &[],
429 );
430 assert!(!result.is_empty());
431 assert_eq!(result.clock_names().len(), 2);
432 assert_eq!(result.clock_names()[0], "t2");
433 assert_eq!(result.clock_names()[1], "t3");
434 }
435
436 #[test]
437 fn dbm_can_fire() {
438 let dbm = Dbm::create(vec!["t1".into()], &[0.0], &[10.0]);
439 let passed = dbm.let_time_pass();
440 assert!(passed.can_fire(0));
441
442 let dbm2 = Dbm::create(vec!["t1".into()], &[5.0], &[10.0]);
444 assert!(!dbm2.can_fire(0)); }
446
447 #[test]
448 fn dbm_canonical_string() {
449 let dbm = Dbm::create(vec!["t1".into()], &[5.0], &[10.0]);
450 let s = dbm.canonical_string();
451 assert!(s.contains("t1"));
452 assert!(s.contains("5"));
453 assert!(s.contains("10"));
454 }
455
456 #[test]
457 fn dbm_empty_dbm() {
458 let dbm = Dbm::empty_dbm();
459 assert!(dbm.is_empty());
460 assert_eq!(dbm.canonical_string(), "DBM[empty]");
461 }
462
463 #[test]
464 fn dbm_fire_persistent_bounds_adjusted() {
465 let dbm = Dbm::create(vec!["t1".into(), "t2".into()], &[0.0, 0.0], &[5.0, 10.0]);
467 let passed = dbm.let_time_pass();
468
469 let result = passed.fire_transition(
470 0, &[],
472 &[],
473 &[],
474 &[1], );
476
477 assert!(!result.is_empty());
478 assert!(result.upper_bound(0) < f64::INFINITY);
480 }
481
482 #[test]
483 fn dbm_fire_replaces_fired_clock() {
484 let dbm = Dbm::create(vec!["t1".into()], &[0.0], &[5.0]);
485 let passed = dbm.let_time_pass();
486
487 let result = passed.fire_transition(0, &["t2".into()], &[1.0], &[3.0], &[]);
489
490 assert_eq!(result.clock_names().len(), 1);
491 assert_eq!(result.clock_names()[0], "t2");
492 assert_eq!(result.lower_bound(0), 1.0); assert_eq!(result.upper_bound(0), 3.0);
494
495 let passed = result.let_time_pass();
497 assert_eq!(passed.lower_bound(0), 0.0);
498 }
499
500 #[test]
501 fn dbm_infeasible_constraints_produce_empty() {
502 let dbm = Dbm::create(
504 vec!["t1".into()],
505 &[10.0],
506 &[5.0], );
508 assert!(dbm.is_empty());
509 }
510
511 #[test]
512 fn dbm_multiple_clocks_inter_constraints() {
513 let dbm = Dbm::create(vec!["t1".into(), "t2".into()], &[1.0, 2.0], &[3.0, 4.0]);
515 assert!(!dbm.is_empty());
516
517 assert!(dbm.get(1, 2) <= 1.0 + 1e-9);
520 }
521
522 #[test]
523 fn dbm_lower_bound_out_of_range() {
524 let dbm = Dbm::create(vec!["t1".into()], &[5.0], &[10.0]);
525 assert_eq!(dbm.lower_bound(10), 0.0);
527 assert_eq!(dbm.upper_bound(10), f64::INFINITY);
528 }
529
530 #[test]
531 fn dbm_equality_with_different_names() {
532 let a = Dbm::create(vec!["t1".into()], &[5.0], &[10.0]);
533 let b = Dbm::create(vec!["t2".into()], &[5.0], &[10.0]);
534 assert_ne!(a, b);
536 }
537
538 #[test]
539 fn dbm_empty_equality() {
540 let a = Dbm::empty_dbm();
541 let b = Dbm::empty_dbm();
542 assert_eq!(a, b);
543 }
544
545 #[test]
546 fn dbm_let_time_pass_preserves_upper() {
547 let dbm = Dbm::create(vec!["t1".into(), "t2".into()], &[3.0, 5.0], &[10.0, 15.0]);
548 let passed = dbm.let_time_pass();
549 assert_eq!(passed.lower_bound(0), 0.0);
550 assert_eq!(passed.lower_bound(1), 0.0);
551 assert_eq!(passed.upper_bound(0), 10.0);
552 assert_eq!(passed.upper_bound(1), 15.0);
553 }
554}