-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathobst.cpp
executable file
·6605 lines (5773 loc) · 263 KB
/
obst.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
// Written by Philipp Czerner, 2018. Public Domain.
// See LICENSE.md for license information.
// The README contains some high-level remarks about the code, you might want to take a look.
// Uncomment this to show the force-based layout algorithm. Debug functionality, obviously.
//#define OBST_DBG_SHOW_FORCE_LAYOUT 1
// Display an error somewhere, somehow. This is not for critical errors where we exit the program,
// just to tell the user that they did something we do not like. ui_error_report is a printf-like
// function.
Array_dyn<u8> ui_error_buf;
template <typename... Args>
void ui_error_report(char const* msg, Args... args) {
u64 len = snprintf(nullptr, 0, msg, args...);
ui_error_buf.size = 0;
array_reserve(&ui_error_buf, len+1);
snprintf(
(char*)ui_error_buf.end(),
ui_error_buf.capacity - ui_error_buf.size,
msg, args...
);
ui_error_buf.size += len+1;
platform_fmt_store_simple(Text_fmt::PARAGRAPH | Text_fmt::RED, ui_error_buf, Text_fmt::SLOT_ERRORINFO);
}
void ui_error_report(char const* msg) {
platform_fmt_store_simple(Text_fmt::PARAGRAPH | Text_fmt::RED, msg, Text_fmt::SLOT_ERRORINFO);
}
// These are used for the parser.
namespace errorcodes {
enum errorcode : u8 {
SUCCESS = 0,
STRING_EMPTY = 1,
INVALID_CHARACTER = 2,
OUT_OF_RANGE_TOO_LOW = 3,
OUT_OF_RANGE_TOO_HIGH = 4,
ERROR = 5
};
}
// Parse an integer in str into val using base base. Is pretty defensive about what kind of strings
// it accepts.
u8 jup_stoi(Array_t<u8> str, s32* val, s64 base) {
assert(val);
assert(2 <= base and base <= 16);
if (str.size == 0) return errorcodes::STRING_EMPTY;
bool negate = false;
if (str[0] == '+') {
str = array_subarray(str, 1, str.size-1);
} else if (str[0] == '-') {
negate = true;
str = array_subarray(str, 1, str.size-1);
}
if (str.size == 0) return errorcodes::STRING_EMPTY;
u32 tmp = 0;
for (char c: str) {
u32 c_val;
if ('0' <= c and c <= '9') {
c_val = c - '0';
} else if ('a' <= c and c <= 'z') {
c_val = c - 'a' + 10;
} else if ('A' <= c and c <= 'Z') {
c_val = c - 'A' + 10;
} else {
return errorcodes::INVALID_CHARACTER;
}
if (c_val >= base) {
return errorcodes::INVALID_CHARACTER;
}
u32 tmp_old = tmp;
tmp = tmp * base + c_val;
if (tmp_old > tmp) {
return negate ? errorcodes::OUT_OF_RANGE_TOO_LOW
: errorcodes::OUT_OF_RANGE_TOO_HIGH;
}
}
if (negate) {
tmp = -tmp;
if (not (tmp >> 31) and tmp) {
return errorcodes::OUT_OF_RANGE_TOO_LOW;
}
*val = (s32)tmp;
return errorcodes::SUCCESS;
} else {
if (tmp >> 31) {
return errorcodes::OUT_OF_RANGE_TOO_HIGH;
}
*val = (s32)tmp;
return errorcodes::SUCCESS;
}
}
// Reads a list of integers from str and parses it into into. In this context, list means
// alphanumerical separated by non-alphanumerical.
u8 parse_int_list(Array_dyn<s64>* into, Array_t<u8> str, s64 base) {
s64 last = 0;
for (s64 i = 0; i <= str.size; ++i) {
bool alnum = false;
if (i < str.size) {
if ('0' <= str[i] and str[i] <= '9') alnum = true;
if ('a' <= str[i] and str[i] <= 'z') alnum = true;
if ('A' <= str[i] and str[i] <= 'Z') alnum = true;
}
if (not alnum and last < i) {
s32 number;
u8 code = jup_stoi(array_subarray(str, last, i), &number, base);
if (code) return code;
last = i+1;
array_push_back(into, (s64)number);
} else if (not alnum) {
last = i+1;
}
}
return 0;
}
// This represents the nodes of the graph. (In the graph sense, so no positions and such.)
struct Bdd {
// Which flags we allow
enum Bdd_constants: u16 {
NONE = 0,
TEMPORARY = 1, // These nodes are created during the execution of a stepwise algorithm to illustrate the current state to the puny humans watching it. They are not subject to deduplication and are either deleted or finalised using bdd_finalize.
CURRENT = 2, // The active node the algorithm is considering. Drawn in red.
MARKED = 4, // Some nodes are relevant to the current operation and will be marked by the algorithm. Drawn in green.
MARKED_E0 = 8, // Sometimes, we need even more colours. This corresponds to whether the edge to child0 is marked.
MARKED_E1 = 16, // Same, but for child1.
FAINT_BDD = 32, // Draw node semi-transparent. Only set during layout_frame_draw due to highlighting.
FAINT_E0 = 64, // Draw edge to child0 semi-transparent. Only set during layout_frame_draw due to highlighting.
FAINT_E1 = 128, // Same, but for child1.
FAINT = FAINT_BDD | FAINT_E0 | FAINT_E1, // Used to reset the faint flags
COSMETIC = CURRENT | MARKED | MARKED_E0 | MARKED_E1 | FAINT, // Used to reset the cosmetic flags which are cleared after the snapshot is taken.
INTERMEDIATE = 256 // These nodes are used by the layout algorithm to deal with edges spanning multiple layers. They exist just in the imagination of the layout algorithm and are not drawn anywhere.
};
u32 child0 = 0, child1 = 0; // child0 is the false-edge, child1 the true-edge.
u8 level = 0;
u16 flags = 0;
u32 id = 0; // 0 is F, the false-node, and 1 is T, the true-node
u32 name = 0; // Index into Bdd_store::names
u32 creation = 0; // Index into Bdd_store::creations
};
struct Snapshot {
u64 offset_bdd;
u64 offset_context;
u64 offset_levels;
};
struct Bdd_lookup {
u64 hash; u32 id;
};
// This stores the current state of the BDD graph, as well as a copy of the graph and context for
// each snapshot.
struct Bdd_store {
Array_t<Bdd_lookup> bdd_lookup; // Hashtable for deduplication
s64 bdd_lookup_count; // How full the hashtable is
Array_dyn<Bdd> bdd_data; // Maps id -> Bdd. The ids are assigned only once, so this can contain a lot of temporary nodes.
// Data describing the current state of the algorithm. This is used to _generate_ the snapshot.
Array_dyn<u32> snapshot_parents; // What nodes to start the reachability traversal with
Array_dyn<u32> snapshot_cur_node; // A stack of nodes, the last of which is flagged as current
Array_dyn<u32> snapshot_marked; // A list of nodes flagged as marked. Resets when taking a snapshot!
Array_dyn<u32> snapshot_marked_e; // A list of edges (!) flagged as marked. Resets when taking a snapshot!
Array_dyn<u8> snapshot_context; // A stack of textual descriptions of the current state of the algorithm. Separated by 0.
Array_dyn<u8> snapshot_levels; // A list of level names, terminated by 0.
// This stores the snapshots.
Array_dyn<Snapshot> snapshots; // This just contains offsets into the other two arrays. There is always a dummy element at the end.
Array_dyn<Bdd> snapshot_data_bdd; // Nodes contained in the snapshot
Array_dyn<u8> snapshot_data_context; // Context strings in the snapshot
Array_dyn<u8> snapshot_data_levels; // Level name strings in the snapshot
// Data for generating the names of the Bdds
Array_dyn<s64> names; // Offsets into name_data, with a dummy element at the end. First name has length 0
Array_dyn<u8> name_data; // utf-8 encoded names
s64 name_next; // The final names for nodes are given in sequential order
s64 name_next_temp; // Final names for temporary nodes
// Data describing how a node was created
Array_dyn<s64> creations; // Offsets into creation_data, with a dummy element at the end. First name has length 0
Array_dyn<u8> creation_data; // utf-8 encoded creation data
// If the hashtable overflows, we need to graciously abort the procedure
bool error_flag;
};
// Initialises the store. Can be used for re-initialisation.
void bdd_store_init(Bdd_store* store) {
// The size of the hashmap is fixed at the constant below.
constexpr s64 bdd_lookup_size = 4096;
// Either allocate memory or just initialise it.
if (store->bdd_lookup.size == 0) {
store->bdd_lookup = array_create<Bdd_lookup>(bdd_lookup_size);
} else {
memset(store->bdd_lookup.data, 0, store->bdd_lookup.size * sizeof(Bdd_lookup));
}
assert(store->bdd_lookup.size == bdd_lookup_size);
store->bdd_lookup_count = 0;
store->bdd_data.size = 0;
store->snapshot_parents.size = 0;
store->snapshot_cur_node.size = 0;
store->snapshot_marked.size = 0;
store->snapshot_marked_e.size = 0;
store->snapshot_context.size = 0;
store->snapshot_levels.size = 0;
store->snapshots.size = 0;
store->snapshot_data_bdd.size = 0;
store->snapshot_data_context.size = 0;
store->snapshot_data_levels.size = 0;
store->names.size = 0;
store->name_data.size = 0;
store->name_next = 0;
store->name_next_temp = 0;
store->creations.size = 0;
store->creation_data.size = 0;
// The F and T nodes.
array_push_back(&store->bdd_data, {0, 0, 0, 0, 0, 1, 0});
array_push_back(&store->bdd_data, {1, 1, 0, 0, 1, 2, 0});
// Dummy element
array_push_back(&store->snapshots, {0, 0, 0});
// Initial names for T and F as well as dummy element
array_append(&store->names, {0ll, 0ll, 1ll, 2ll});
array_append(&store->name_data, {(u8*)"FT", 2});
// 0 is the empty creation
array_append(&store->creations, {0ll, 0ll});
store->error_flag = false;
}
u64 bdd_hash(Bdd bdd) {
// splitmix64, see http://xorshift.di.unimi.it/splitmix64.c
// This is unnecessarily expensive, but not a bottleneck.
u64 val = bdd.child0;
val = (val ^ (val >> 30)) * 0xbf58476d1ce4e5b9ull;
val = (val ^ (val >> 27)) * 0x94d049bb133111ebull;
val = val ^ (val >> 31);
val ^= bdd.child1;
val = (val ^ (val >> 30)) * 0xbf58476d1ce4e5b9ull;
val = (val ^ (val >> 27)) * 0x94d049bb133111ebull;
val = val ^ (val >> 31);
val ^= bdd.level | (bdd.flags & ~Bdd::COSMETIC) << 8;
val = (val ^ (val >> 30)) * 0xbf58476d1ce4e5b9ull;
val = (val ^ (val >> 27)) * 0x94d049bb133111ebull;
val = val ^ (val >> 31);
return val;
}
// Insert the bdd into the store, doing deduplication. Returns the id of the deduplicated bdd.
u32 bdd_insert(Bdd_store* store, Bdd bdd) {
u64 hash = bdd_hash(bdd);
if (store->bdd_lookup_count*4 >= store->bdd_lookup.size*3) {
// If the hash table is full, the whole operation needs to abort.
if (not store->error_flag) {
ui_error_report("Error: size of BDD hash table exceeded, too many nodes");
store->error_flag = true;
}
return bdd.id;
}
u64 slot = hash % store->bdd_lookup.size;
while (store->bdd_lookup[slot].hash != 0 and store->bdd_lookup[slot].hash != hash) {
slot = (slot + 1) % store->bdd_lookup.size;
}
if (store->bdd_lookup[slot].hash == 0) {
store->bdd_lookup[slot].hash = hash;
store->bdd_lookup[slot].id = bdd.id;
++store->bdd_lookup_count;
}
return store->bdd_lookup[slot].id;
}
// Append a number to the next BDD name. The next time a name is assigned, it will consume this
// data.
void bdd_name_amend_number_base(Bdd_store* store, u64 number, s64 base = 10, bool small = false) {
s64 digit_count = 0;
for (u64 ii = number; ii or digit_count == 0; ii /= base) ++digit_count;
array_resize(&store->name_data, store->name_data.size + digit_count);
auto buf = array_subarray(store->name_data, store->name_data.size - digit_count, store->name_data.size);
s64 j = buf.size;
for (s64 j_it = 0; j_it < digit_count; ++j_it) {
s64 digit = number % base;
number /= base;
buf[--j] = (small << 7) | (u8)(digit < 10 ? '0' + digit : 'a' + digit - 10);
}
}
// Append a list of numbers to the next BDD name. The next time a name is assigned, it will consume
// this data.
void bdd_name_amend_list_base(Bdd_store* store, Array_t<u64> list, s64 base) {
bool first = true;
for (u64 i: list) {
if (not first) array_push_back(&store->name_data, (u8)(128 | ','));
first = false;
bdd_name_amend_number_base(store, i, base, true);
}
}
// Append an id based on the number to the next BDD name. These ids are of the form 'a', 'b', ...,
// 'z', 'aa', 'ab', ... and so on. The next time a name is assigned, it will consume this data.
void bdd_name_amend_id(Bdd_store* store, u32 number) {
s64 base = 26;
s64 digit_count = 1;
for (u64 ii = number; ii >= base; ii = ii/base-1) ++digit_count;
array_resize(&store->name_data, store->name_data.size + digit_count);
auto buf = array_subarray(store->name_data, store->name_data.size - digit_count, store->name_data.size);
s64 j = buf.size;
for (s64 j_it = 0; j_it < digit_count; ++j_it) {
buf[--j] = (u8)('a' + number % base);
number = number / base - 1;
}
}
// Append a temporary id based on the number to the next BDD name. These temporary ids are of the
// form '%0', '%1', ... and so on. The next time a name is assigned, it will consume this data.
void bdd_name_amend_tempid(Bdd_store* store, u32 number) {
array_printf(&store->name_data, "%");
bdd_name_amend_number_base(store, number);
}
// Append an existing bdd name to the next BDD name. The next time a name is assigned, it will
// consume this data.
void bdd_name_amend_name(Bdd_store* store, u32 bdd) {
u32 name = store->bdd_data[bdd].name;
auto arr = array_subarray(store->name_data, store->names[name], store->names[name+1]);
array_append(&store->name_data, arr);
}
// Append a name representing a set operation to the bdd name. The next time a name is assigned, it
// will consume this data.
void bdd_name_amend_setop(Bdd_store* store, u32 a, u32 b, char op) {
assert(store->names.size > 0);
if (op == '~') {
array_push_back(&store->name_data, (u8)op);
bdd_name_amend_name(store, a);
} else if (op == '&' or op == '|') {
bdd_name_amend_name(store, a);
array_push_back(&store->name_data, (u8)op);
bdd_name_amend_name(store, b);
} else {
assert(false);
}
}
#if 0
void bdd_name_amend_quant(Bdd_store* store, u32 a, char quant, Array_t<u8> levels) {
assert(store->names.size > 0);
array_push_back(&store->name_data, (u8)(quant ^ 128));
{s64 level = 0;
s64 i = 0;
bool first = true;
while (true) {
bool do_print = false;
for (u8 j: levels) {
if (level == j) do_print = true;
}
if (do_print) {
for (; store->snapshot_levels[i]; ++i)
array_push_back(&store->name_data, (u8)(store->snapshot_levels[i] ^ 128));
if (not first)
array_push_back(&store->name_data, (u8)(',' ^ 128));
first = false;
} else {
while (store->snapshot_levels[i]) ++i;
}
++i;
}}
}
#endif
// Assign the next bdd name. This will consume the data given by the bdd_name_amend function.
void bdd_name_assign(Bdd_store* store, Bdd* bdd) {
assert(store->names.size > 0);
if (store->name_data.size != store->names[store->names.size-1]) {
bdd->name = store->names.size-1;
store->bdd_data[bdd->id].name = bdd->name;
array_push_back(&store->names, store->name_data.size);
}
}
// Create a new bdd, with a new id. Unless the node is temporary, this does both deduplication and
// removal of unnecessary nodes (both children point to the same node).
// Additionally, if there is any data remaining in store->name_data, assign it as the new name of
// the node.
void bdd_create_inplace(Bdd_store* store, Bdd* bdd) {
bdd->id = store->bdd_data.size;
if (bdd->flags & Bdd::TEMPORARY) {
array_push_back(&store->bdd_data, *bdd);
} else {
if (bdd->child0 == bdd->child1) {
*bdd = store->bdd_data[bdd->child0];
} else {
bdd->id = bdd_insert(store, *bdd);
if (bdd->id == store->bdd_data.size) {
array_push_back(&store->bdd_data, *bdd);
bdd_name_amend_id(store, store->name_next++);
bdd_name_assign(store, bdd);
} else {
*bdd = store->bdd_data[bdd->id];
}
}
}
bdd_name_assign(store, bdd);
}
u32 bdd_create(Bdd_store* store, Bdd bdd) {
bdd_create_inplace(store, &bdd);
return bdd.id;
}
// Partition numbers by using the bit with index level. (The least-significant bit has index 0.)
// Returns the index of the first element of the second set, so that the partition is given by [0,
// index), [index, numbers->size).
s64 _partition_along_bit(Array_t<u64>* numbers, u8 level) {
u64 l = 0;
for (u64 r = 0; r < (u64)numbers->size; ++r) {
if (not ((*numbers)[r] >> level & 1)) {
u64 tmp = (*numbers)[l];
(*numbers)[l] = (*numbers)[r];
(*numbers)[r] = tmp;
++l;
}
}
return l;
}
// This pushed a new messages onto the context stack. printf-like function.
template <typename... Args>
void context_append_(Bdd_store* store, char const* msg, Args... args) {
u64 len = snprintf(nullptr, 0, msg, args...);
array_reserve(&store->snapshot_context, store->snapshot_context.size + len+1);
snprintf(
(char*)store->snapshot_context.end(),
store->snapshot_context.capacity - store->snapshot_context.size,
msg, args...
);
store->snapshot_context.size += len+1;
}
void context_append_(Bdd_store* store, char const* msg) {
array_append(&store->snapshot_context, {(u8*)msg, (s64)(strlen(msg) + 1)});
}
// Append the string to the last message currently on the context stack.
template <typename... Args>
void context_amend_(Bdd_store* store, char const* msg, Args... args) {
--store->snapshot_context.size;
context_append_(store, msg, args...);
}
// Use this to get the compiler to check that the printf arguments are passed correctly. (I tried to get the built-in annotations to work, but did not.) Debug functionality.
#if 0
#define context_append(a, ...) context_append_(a, __VA_ARGS__); snprintf(0, 0, __VA_ARGS__)
#define context_amend(a, ...) context_amend_(a, __VA_ARGS__); snprintf(0, 0, __VA_ARGS__)
#else
#define context_append context_append_
#define context_amend context_amend_
#endif
void context_amend(Bdd_store* store, Array_t<u8> msg) {
--store->snapshot_context.size;
array_append(&store->snapshot_context, msg);
array_push_back(&store->snapshot_context, (u8)0);
}
// Like context_amend, but generate a list of numbers in square braces, separated by commas. fmt
// specifies the format to use for each number. If you notice anything strange here, it may pay of
// to use the above macro to make sure you got the printf specifiers right.
template <typename T>
void context_amend_list(Bdd_store* store, Array_t<T> list, char const* fmt) {
context_amend(store, "[");
bool first = true;
for (T i: list) {
if (not first) context_amend(store, ", ");
first = false;
context_amend(store, fmt, i);
}
context_amend(store, "]");
}
// Like context_amend, but write number in base base with digit_count digits. If digit_count is -1,
// then it will be determined automatically. marked contains digits that should be shown in bold.
void context_amend_number_base(Bdd_store* store, u64 number, s64 base, s64 digit_count = -1, Array_t<u8> marked = {}) {
// If there is no fixed count, determine the actual amount here
if (digit_count == -1) {
digit_count = 0;
for (u64 ii = number; ii or digit_count == 0; ii /= base) ++digit_count;
for (u8 pos: marked) {
digit_count = std::max(digit_count, (s64)pos+1);
}
}
s64 buf_size = digit_count + 7 * marked.size + 1;
Array_t<char> buf = {(char*)alloca(buf_size), buf_size};
s64 j = buf.size;
buf[--j] = 0;
bool marking = false;
for (s64 j_it = 0; j_it < digit_count; ++j_it) {
s64 digit = number % base;
number /= base;
char c = (char)(digit < 10 ? '0' + digit : 'a' + digit - 10);
bool flag = false;
for (u8 pos: marked) flag |= pos == j_it;
if (not flag and marking) {
j -= 3;
memcpy(&buf[j], "<b>", 3);
}
if (flag and not marking) {
j -= 4;
memcpy(&buf[j], "</b>", 4);
}
marking = flag;
buf[--j] = c;
}
if (marking) {
j -= 3;
memcpy(&buf[j], "<b>", 3);
}
context_amend(store, &buf[j]);
}
void context_amend_number_base(Bdd_store* store, u64 number, s64 base, s64 digit_count, s64 digit_mark_) {
if (digit_mark_ == -1) {
context_amend_number_base(store, number, base, digit_count);
} else {
u8 digit_mark = (u8)digit_mark_;
context_amend_number_base(store, number, base, digit_count, {&digit_mark, 1});
}
}
// Like context_amend_list, but prints the numbers ith context_amend_number_base instead of printf.
void context_amend_list_base(Bdd_store* store, Array_t<u64> list, s64 base, s64 digit_count = -1, s64 digit_mark = -1) {
context_amend(store, "[");
bool first = true;
for (u64 i: list) {
if (not first) context_amend(store, ", ");
first = false;
context_amend_number_base(store, i, base, digit_count, digit_mark);
}
context_amend(store, "]");
}
// Pop the last element of the context stack. This will assert if it is empty.
void context_pop(Bdd_store* store) {
--store->snapshot_context.size;
while (store->snapshot_context.size and store->snapshot_context[store->snapshot_context.size-1])
--store->snapshot_context.size;
}
// Note on bdd labels: The names are stored with one byte per character. Why not use the existing
// system for formatted text you ask? Two reasons: The labels need to be rendered by GL, even in the
// browser. Hence I cannot use the same backend I use for the other formatted text, which emit
// HTML. Additionally, the names are stored in the Bdd_store, and the formatted text API would need
// additional interfaces to enable outside storage. So it is not really a good fit here.
// Instead I store the labels as simple sequences of bytes with an ASCII-like encoding. The range
// [32,127] is mapped to itself and contains the printable characters. While drawing, a few of these
// will be mapped to other unicode glyphs, e.g. & becomes set union. [22,32) are then mapped to
// subscripts. Finally, some of the labels are written in a smaller font, so that they can be
// wrapped into two lines. The small characters are mapped in exactly the same fashion, but with
// their high-bit set.
// Finally, the bytes are then mapped to indices into the texture in a simple manner, we map
// [22,127) to [0,105), and then 128 | [22,127) to 105 + [0,105).
constexpr s64 opengl_bddlabel_index_size = 210; // Total number of slots in the texture
// Return the index of bytes c according to the above mapping and the other way around.
s64 opengl_bddlabel_char_index(u8 c) {
if (22 <= (c&127) and (c&127) < 127) {
return (c&127) - 22 + (c>>7) * 105;
} else {
return -1;
}
}
u8 opengl_bddlabel_index_char(s64 index) {
assert(0 <= index and index < 210);
return (u8)(index < 105 ? index + 22 : index + 45);
}
// Convert an index into the texture into a unicode codepoint represented in UTF-8 as well as
// information on how to draw it.
Array_t<u8> opengl_bddlabel_index_utf8(s64 index, bool* draw_light_ = nullptr, bool* draw_italics_=nullptr) {
assert(0 <= index and index < opengl_bddlabel_index_size);
static char c[2];
c[0] = (char)(index < 105 ? index + 22 : index - 83);
char const* s;
bool draw_light = false;
bool draw_italics = 'a' <= c[0] and c[0] <= 'z';
switch (c[0]) {
case '|': s = u8"∪"; draw_light = true; break;
case '&': s = u8"∩"; draw_light = true; break;
case '~': s = u8"¬"; draw_light = true; break;
case '+': s = u8"∨"; draw_light = true; break;
case '-': s = u8"∧"; draw_light = true; break;
case '<': s = u8"←"; draw_light = true; break;
case '>': s = u8"→"; draw_light = true; break;
case '=': s = u8"↔"; draw_light = true; break;
case '^': s = u8"⊕"; draw_light = true; break;
case '?': s = u8"∀"; break;
case '!': s = u8"∃"; break;
case 22+0: s = u8"₀"; break;
case 22+1: s = u8"₁"; break;
case 22+2: s = u8"₂"; break;
case 22+3: s = u8"₃"; break;
case 22+4: s = u8"₄"; break;
case 22+5: s = u8"₅"; break;
case 22+6: s = u8"₆"; break;
case 22+7: s = u8"₇"; break;
case 22+8: s = u8"₈"; break;
case 22+9: s = u8"₉"; break;
default: s = &c[0]; break;
};
if (draw_light_) *draw_light_ = draw_light;
if (draw_italics_) *draw_italics_ = draw_italics;
return {(u8*)s, (s64)strlen(s)};
}
Array_t<u8> opengl_bddlabel_char_utf8(u8 c) {
return opengl_bddlabel_index_utf8(opengl_bddlabel_char_index(c));
}
// Like context_amend, but formats the name of a bdd.
void context_amend_bdd(Bdd_store* store, u32 id) {
u32 name = store->bdd_data[id].name;
auto arr = array_subarray(store->name_data, store->names[name], store->names[name+1]);
if (id <= 1) context_amend(store, "<s>");
bool italics_on = false;
for (u8 c: arr) {
bool italicized;
auto c_arr = opengl_bddlabel_index_utf8(opengl_bddlabel_char_index(c), nullptr, &italicized);
if (not italics_on and italicized) context_amend(store, "<i>");
if (italics_on and not italicized) context_amend(store, "</i>");
italics_on = italicized;
context_amend(store, c_arr);
}
if (italics_on) context_amend(store, "</i>");
if (id <= 1) context_amend(store, "</s>");
}
void bdd_creation_amend_number_base(Bdd_store* store, u64 number, s64 base) {
s64 digit_count = 0;
for (u64 ii = number; ii or digit_count == 0; ii /= base) ++digit_count;
array_resize(&store->creation_data, store->creation_data.size + digit_count);
auto buf = array_subarray(store->creation_data,
store->creation_data.size - digit_count, store->creation_data.size);
s64 j = buf.size;
for (s64 j_it = 0; j_it < digit_count; ++j_it) {
s64 digit = number % base;
number /= base;
buf[--j] = (u8)(digit < 10 ? '0' + digit : 'a' + digit - 10);
}
}
void bdd_creation_amend_list_base(Bdd_store* store, Array_t<u64> list, s64 base) {
bool first = true;
array_printf(&store->creation_data, "[");
for (u64 i: list) {
if (not first) array_printf(&store->creation_data, ", ");
first = false;
bdd_creation_amend_number_base(store, i, base);
}
array_printf(&store->creation_data, "]");
}
void bdd_creation_amend_list_base(Bdd_store* store, Array_t<u8> list, s64 base) {
bool first = true;
array_printf(&store->creation_data, "[");
for (u64 i: list) {
if (not first) array_printf(&store->creation_data, ", ");
first = false;
bdd_creation_amend_number_base(store, i, base);
}
array_printf(&store->creation_data, "]");
}
void bdd_creation_amend_bdd(Bdd_store* store, u32 id) {
u32 name = store->bdd_data[id].name;
auto arr = array_subarray(store->name_data, store->names[name], store->names[name+1]);
if (id <= 1) array_printf(&store->creation_data, "<s>");
bool italics_on = false;
for (u8 c: arr) {
bool italicized;
auto c_arr = opengl_bddlabel_index_utf8(opengl_bddlabel_char_index(c), nullptr, &italicized);
if (not italics_on and italicized) array_printf(&store->creation_data, "<i>");
if (italics_on and not italicized) array_printf(&store->creation_data, "</i>");
italics_on = italicized;
array_append(&store->creation_data, c_arr);
}
if (italics_on) array_printf(&store->creation_data, "</i>");
if (id <= 1) array_printf(&store->creation_data, "</s>");
}
#if 0
void bdd_creation_amend_levels(Bdd_store* store, u32 a, Array_t<u8> levels) {
assert(store->names.size > 0);
{s64 level = 0;
s64 i = 0;
bool first = true;
while (true) {
bool do_print = false;
for (u8 j: levels) {
if (level == j) do_print = true;
}
if (do_print) {
for (; store->snapshot_levels[i]; ++i)
array_push_back(&store->name_data, store->snapshot_levels[i]);
if (not first)
array_printf(&store->name_data, ", ");
first = false;
} else {
while (store->snapshot_levels[i]) ++i;
}
++i;
}}
}
#endif
// Take a temporary bdd and finalise it, i.e. do deduplication and removal of unnecessary
// nodes. This will push a description of the operation performed onto the context stack. (If
// nothing was done, an empty entry is pushed.)
// If the bdd had a name, it will be preserved.
u32 bdd_finalize(Bdd_store* store, Bdd bdd) {
assert(bdd.flags & Bdd::TEMPORARY);
bdd.flags &= ~Bdd::TEMPORARY;
if (bdd.child0 == bdd.child1) {
bdd_name_amend_tempid(store, store->name_next_temp++);
bdd_name_assign(store, &bdd);
context_append(store, "Node ");
context_amend_bdd(store, bdd.id);
context_amend(store, " is superflous, remove");
return bdd.child0;
} else {
u32 bdd_id = bdd_insert(store, bdd);
if (bdd_id == bdd.id) {
store->bdd_data[bdd.id] = bdd;
bdd_name_amend_id(store, store->name_next++);
bdd_name_assign(store, &bdd);
context_append(store, "");
} else {
bdd_name_amend_tempid(store, store->name_next_temp++);
bdd_name_assign(store, &bdd);
context_append(store, "Merging node ");
context_amend_bdd(store, bdd.id);
context_amend(store, " with ");
context_amend_bdd(store, bdd_id);
}
return bdd_id;
}
}
// Store the current state of the store into a snapshot, together with the additional visualisation
// information.
void take_snapshot(Bdd_store* store) {
// Compute the reachable set of nodes from the ones in snapshot_parents
Array_dyn<u32> reachable;
Array_t<u64> reached {(u64*)calloc((store->bdd_data.size+63)/64, sizeof(u64)), (store->bdd_data.size+63)/64};
defer { array_free(&reachable); };
defer { array_free(&reached); };
for (u32 i: store->snapshot_parents) {
if (not bitset_get(reached, i)) {
array_push_back(&reachable, i);
bitset_set(&reached, i, 1);
}
}
bitset_set(&reached, 0, 1);
for (s64 i = 0; i < reachable.size; ++i) {
Bdd bdd = store->bdd_data[reachable[i]];
// Works implicitly for T and F
if (not bitset_get(reached, bdd.child0)) {
array_push_back(&reachable, bdd.child0);
bitset_set(&reached, bdd.child0, 1);
}
if (not bitset_get(reached, bdd.child1)) {
array_push_back(&reachable, bdd.child1);
bitset_set(&reached, bdd.child1, 1);
}
}
// Set the cosmetic flags
for (Bdd& i: store->bdd_data) {
i.flags &= ~Bdd::COSMETIC;
}
if (store->snapshot_cur_node.size) {
u32 cur = store->snapshot_cur_node[store->snapshot_cur_node.size-1];
store->bdd_data[cur].flags |= Bdd::CURRENT;
}
for (u32 i: store->snapshot_marked) {
store->bdd_data[i].flags |= Bdd::MARKED;
}
for (u32 i: store->snapshot_marked_e) {
store->bdd_data[i >> 1].flags |= i & 1 ? Bdd::MARKED_E1 : Bdd::MARKED_E0;
}
// Save nodes and context into the snapshot
for (u32 i: reachable) {
array_push_back(&store->snapshot_data_bdd, store->bdd_data[i]);
}
array_append(&store->snapshot_data_context, store->snapshot_context);
array_append(&store->snapshot_data_levels, store->snapshot_levels);
// Commit the snapshot
array_push_back(&store->snapshots, {(u64)store->snapshot_data_bdd.size, (u64)store->snapshot_data_context.size, (u64)store->snapshot_data_levels.size});
// Reset the marked nodes
store->snapshot_marked.size = 0;
store->snapshot_marked_e.size = 0;
}
// Note on stepwise functions: bdd_union_stepwise, bdd_intersection_stepwise,
// bdd_complement_stepwise, and bdd_from_list_stepwise do the heavy lifting in executing the
// algorithms in a step-by-step manner. There are some key points regarding their implementation:
// - They take an additional bdd argument, which is the id of the bdd they should write their
// result into. This is only used for recursive calls.
// - Recursive calls will leave one item on the context stack from the bdd_finalize call.
// - Children are created by the parent call and then populated by recusive calls, using the bdd
// argument.
// - Management of the context stack is done very carefully, so that nothing remains at the end.
// Out of these functions, only bdd_union stepwise has general comments regarding the common
// functionality.
// Compute the union of a and b while doing all the visualisation work. The bdd argument is meant
// for recursive calls, no need to use it.
u32 bdd_union_stepwise(Bdd_store* store, u32 a, u32 b, u32 bdd = -1, u32 a_parent = -1, u32 b_parent = -1) {
// Duplicated code below in bdd_intersection_stepwise. Take care. Also see the note there.
Bdd a_bdd = store->bdd_data[a];
Bdd b_bdd = store->bdd_data[b];
// Check whether we are the top-level call or in a recursive call
Bdd bdd_temp;
if (bdd == (u32)-1) {
// Create a new node for the union
bdd_temp = {0, 0, std::max(a_bdd.level, b_bdd.level), Bdd::TEMPORARY};
bdd_name_amend_setop(store, a, b, '|');
bdd_temp.creation = store->creations.size-1;
array_printf(&store->creation_data, "The node was created as union of ");
bdd_creation_amend_bdd(store, a);
array_printf(&store->creation_data, " and ");
bdd_creation_amend_bdd(store, b);
array_printf(&store->creation_data, ".");
array_push_back(&store->creations, store->creation_data.size);
bdd_create_inplace(store, &bdd_temp);
array_push_back(&store->snapshot_parents, bdd_temp.id); // Mark it as parent, so that snapshots capture it and its descenndants.
context_append(store, "Calculating union of ");
} else {
// In a recursive call, we get passed a node into which we construct the result
bdd_temp = store->bdd_data[bdd];
context_append(store, "Doing union of ");
}
context_amend_bdd(store, a);
if (a == 0 and a_parent != -1) {
context_amend(store, " (node ");
context_amend_bdd(store, a_parent >> 1);
context_amend(store, a_parent & 1 ? " has no child 1)" : " has no child 0)");
}
context_amend(store, " and ");
context_amend_bdd(store, b);
if (b == 0 and b_parent != -1) {
context_amend(store, " (node ");
context_amend_bdd(store, b_parent >> 1);
context_amend(store, b_parent & 1 ? " has no child 1)" : " has no child 0)");
}
// Set the current node and take snapshot
array_push_back(&store->snapshot_cur_node, bdd_temp.id);
array_append(&store->snapshot_marked, {a, b});
if (a_parent != -1) array_push_back(&store->snapshot_marked_e, a_parent);
if (b_parent != -1) array_push_back(&store->snapshot_marked_e, b_parent);
take_snapshot(store);
// Now there are a bunch of special cases. Only a few of them are actually necessary to
// implement the algorithm, but the visualisation is much more tedious if they are not
// considered.
u32 bdd_final;
if (a == 1 or b == 1) {
// One node is T
bdd_final = 1;
context_pop(store);
context_append(store, a == 1 ? "First" : "Second");
context_amend(store, " node is <s>T</s>, connect to <s>T</s>");
} else if (a == 0 or b == 0) {
// One node is F
bdd_final = a == 0 ? b : a;
context_pop(store);
if (bdd_final == 0) {
context_append(store, "Both nodes are <s>F</s>, no connection");
} else {
context_append(store, a == 0 ? "First" : "Second");
context_amend(store, " node is <s>F</s>, connect to ");
context_amend_bdd(store, bdd_final);
}
} else if (a == b) {
// Both nodes are equal
context_pop(store);
context_append(store, "The nodes are equal, connect");
bdd_final = a;
} else if (std::max(a_bdd.level, b_bdd.level) == 1) {
// We are at the last level. This one is not necessary.
context_append(store, "Both nodes (");
context_amend_bdd(store, a);
context_amend(store, " and ");
context_amend_bdd(store, b);
context_amend(store, ") at last level, direct connections");
array_append(&store->snapshot_marked, {a, b});
take_snapshot(store);
context_pop(store);
// We know that we are at the last level, so the only possible children are F and T, having
// ids 0 and 1, respectively. Hence, we can use bit-operations on the ids.
bdd_temp.child0 = a_bdd.child0 | b_bdd.child0;
store->bdd_data[bdd_temp.id] = bdd_temp; // Write back changes into the store
context_append(store, "Child 0 is ");
context_amend_bdd(store, bdd_temp.child0);
context_amend(store, ", union of ");
context_amend_bdd(store, a_bdd.child0);
context_amend(store, " and ");
context_amend_bdd(store, b_bdd.child0);
array_append(&store->snapshot_marked, {a_bdd.child0, b_bdd.child0});
take_snapshot(store);
context_pop(store);
// See above.
bdd_temp.child1 = a_bdd.child1 | b_bdd.child1;
store->bdd_data[bdd_temp.id] = bdd_temp; // Write back changes into the store
context_append(store, "Child 1 is ");
context_amend_bdd(store, bdd_temp.child1);
context_amend(store, ", union of ");
context_amend_bdd(store, a_bdd.child1);
context_amend(store, " and ");
context_amend_bdd(store, b_bdd.child1);
array_append(&store->snapshot_marked, {a_bdd.child1, b_bdd.child1});
take_snapshot(store);
context_pop(store);
context_pop(store);
bdd_final = bdd_finalize(store, bdd_temp);
} else {
// The general case
u8 child_level = std::max(a_bdd.level, b_bdd.level) - 1;
if (a_bdd.level > b_bdd.level) {
context_append(store, "First node (");
context_amend_bdd(store, a);
context_amend(store, ") has higher level, take its branches");