The source code and dockerfile for the GSW2024 AI Lab.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
This repo is archived. You can view files and clone it, but cannot push or open issues/pull-requests.

394 lines
19 KiB

4 weeks ago
  1. Sylvan
  2. =====================
  3. Sylvan is a parallel (multi-core) MTBDD library written in C. Sylvan
  4. implements parallelized operations on BDDs, MTBDDs and LDDs. Both
  5. sequential and parallel BDD-based algorithms can benefit from
  6. parallelism. Sylvan uses the work-stealing framework Lace and parallel
  7. datastructures to implement scalable multi-core operations on decision
  8. diagrams.
  9. Sylvan is developed (© 2011-2016) by the `Formal Methods and
  10. Tools <http://fmt.ewi.utwente.nl/>`__ group at the University of Twente
  11. as part of the MaDriD project, which is funded by NWO, and (© 2016-2017)
  12. by the `Formal Methods and Verification <http://fmv.jku.at/>`__ group at
  13. the Johannes Kepler University Linz as part of the RiSE project. Sylvan
  14. is licensed with the Apache 2.0 license.
  15. The main author of the project is Tom van Dijk who can be reached via
  16. tom@tvandijk.nl.
  17. Please let us know if you use Sylvan in your projects and if you need
  18. decision diagram operations that are currently not implemented in Sylvan.
  19. The main repository of Sylvan is https://github.com/trolando/sylvan. A
  20. mirror is available at https://github.com/utwente-fmt/sylvan.
  21. Bindings for other languages than C/C++ also exist:
  22. - Java/JNI bindings: https://github.com/utwente-fmt/jsylvan
  23. - Haskell bindings: https://github.com/adamwalker/sylvan-haskell
  24. - Python bindings: https://github.com/johnyf/dd
  25. Dependencies
  26. ------------
  27. Sylvan has the following dependencies:
  28. - **CMake** for compiling.
  29. - **gmp** (``libgmp-dev``) for the GMP leaves in MTBDDs.
  30. - **Sphinx** if you want to build the documentation.
  31. Sylvan depends on the `work-stealing framework
  32. Lace <http://fmt.ewi.utwente.nl/tools/lace>`__ for its implementation.
  33. Lace is embedded in the Sylvan distribution.
  34. Lace requires one additional library:
  35. - **hwloc** (``libhwloc-dev``) for pinning worker threads to processors.
  36. Building
  37. --------
  38. It is recommended to build Sylvan in a separate build directory:
  39. .. code:: bash
  40. mkdir build
  41. cd build
  42. cmake ..
  43. make && make test && make install
  44. It is recommended to use ``ccmake`` to configure the build settings of Sylvan. For example,
  45. you can choose whether you want shared/static libraries, whether you want to enable
  46. statistics gathering and whether you want a ``Debug`` or a ``Release`` build.
  47. Using Sylvan
  48. ------------
  49. To use Sylvan, the library and its dependency Lace must be initialized:
  50. .. code:: c
  51. #include <sylvan.h>
  52. main() {
  53. int n_workers = 0; // auto-detect
  54. lace_init(n_workers, 0);
  55. lace_startup(0, NULL, NULL);
  56. // use at most 512 MB, nodes:cache ratio 2:1, initial size 1/32 of maximum
  57. sylvan_set_limits(512*1024*1024, 1, 5);
  58. sylvan_init_package();
  59. sylvan_init_mtbdd();
  60. /* ... do stuff ... */
  61. sylvan_stats_report(stdout);
  62. sylvan_quit();
  63. lace_exit();
  64. }
  65. The call to ``lace_init`` initializes the Lace framework, which sets up the data structures
  66. for work-stealing. The parameter ``n_workers`` can be set to 0 for auto-detection. The
  67. function ``lace_startup`` then creates all other worker threads. The worker threads run
  68. until ``lace_exit`` is called. Lace must be started before Sylvan can be initialized.
  69. Sylvan is initialized with a call to ``sylvan_init_package``. Before this call, Sylvan needs to know
  70. how much memory to allocate for the nodes table and the operation cache. In this example, we use the
  71. ``sylvan_set_limits`` function to tell Sylvan that it may allocate at most 512 MB for these tables.
  72. The second parameter indicates the ratio of the nodes table and the operation cache, with each
  73. higher number doubling the size of the nodes table. Negative numbers double the size of the operation
  74. cache instead. In the example, we want the nodes table to be twice as big as the operation cache.
  75. The third parameter controls how often garbage collection doubles the table sizes before
  76. their maximum size is reached. The value 5 means that the initial tables are 32x as small as the maximum size.
  77. By default, every execution of garbage collection doubles the table sizes.
  78. After ``sylvan_init_package``, subpackages like ``mtbdd`` and ``ldd`` can be initialized with
  79. ``sylvan_init_mtbdd`` and ``sylvan_init_ldd``. This allocates auxiliary datastructures.
  80. If you enabled statistics generation (via CMake), then you can use ``sylvan_stats_report`` to report
  81. the obtained statistics to a given ``FILE*``.
  82. The Lace framework
  83. ~~~~~~~~~~~~~~~~~~
  84. Sylvan uses the Lace framework to offer 'automatic' parallelization of decision diagram operations.
  85. Many functions in Sylvan are Lace tasks. To call a Lace task, the variables
  86. ``__lace_worker`` and ``__lace_dq_head`` must be initialized as **local** variables of the current function.
  87. Use the macro ``LACE_ME`` to initialize the variables in every function that calls Sylvan functions
  88. and is not itself a Lace task.
  89. Garbage collection and referencing nodes
  90. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  91. Like all decision diagram implementations, Sylvan performs garbage collection.
  92. Garbage collection is triggered when trying to insert a new node and no
  93. empty space can be found in the table within a reasonable upper bound.
  94. Garbage collection can be disabled with ``sylvan_gc_disable`` and enabled again with ``sylvan_gc_enable``.
  95. Call ``sylvan_gc`` to manually trigger garbage collection.
  96. To ensure that no decision diagram nodes are overwritten, you must ensure that
  97. Sylvan knows which decision diagrams you care about.
  98. Each subpackage implements mechanisms to store references to decision diagrams that must be kept.
  99. For example, the *mtbdd* subpackage implements ``mtbdd_protect`` and ``mtbdd_unprotect`` to store pointers to
  100. MTBDD variables.
  101. .. code:: c
  102. MTBDD* allocate_var() {
  103. MTBDD* my_var = (MTBDD*)calloc(sizeof(MTBDD), 1);
  104. mtbdd_protect(my_var);
  105. return my_var;
  106. }
  107. free_var(MTBDD* my_var) {
  108. mtbdd_unprotect(my_var);
  109. free(my_var);
  110. }
  111. If you use ``mtbdd_protect`` you do not need to update the reference every time the value changes.
  112. The *mtbdd* subpackage also implements thread-local stacks to temporarily store pointers and results of tasks:
  113. .. code:: c
  114. MTBDD some_thing = ...;
  115. mtbdd_refs_pushptr(&some_thing);
  116. MTBDD result_param1 = mtbdd_false, result_param2 = mtbdd_false;
  117. mtbdd_refs_pushptr(&result_param1);
  118. mtbdd_refs_pushptr(&result_param2);
  119. while (some_condition) {
  120. mtbdd_refs_spawn(SPAWN(an_operation, some_thing, param1));
  121. result_param2 = CALL(an_operation, some_thing, param2);
  122. result_param1 = mtbdd_refs_sync(SYNC(an_operation));
  123. some_thing = CALL(another_operation, result1, result2);
  124. }
  125. mtbdd_refs_popptr(3);
  126. return some_thing;
  127. It is recommended to use the thread-local stacks for local variables, and to use the ``protect`` and ``unprotect``
  128. functions for other variables. Every SPAWN and SYNC of a Lace task that returns an MTBDD must be decorated with
  129. ``mtbdd_refs_stack`` and ``mtbdd_refs_sync`` as in the above example.
  130. References to decision diagrams must be added before a worker may cooperate on garbage collection.
  131. Workers can cooperate on garbage collection during ``SYNC`` and when functions create nodes or use ``sylvan_gc_test`` to test whether to assist in garbage collection.
  132. Functions for adding or removing references never perform garbage collection.
  133. Furthermore, only the ``mtbdd_makenode`` function (and other node making primitives) implicitly reference their parameters; all other functions do not reference their parameters.
  134. Nesting Sylvan functions (including ``sylvan_ithvar``) is bad practice and should be avoided.
  135. **Warning**: Sylvan is a multi-threaded library and all workers must cooperate for garbage collection. If you use locking mechanisms in your code, beware of deadlocks!
  136. You can explicitly cooperate on garbage collection with ``sylvan_gc_test()``.
  137. Basic BDD/MTBDD functionality
  138. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  139. In Sylvan, BDDs are special cases of MTBDDs.
  140. Several functions are specific for BDDs and they start with ``sylvan_``, whereas generic MTBDD functions start
  141. with ``mtbdd_``.
  142. To create new BDDs, you can use:
  143. - ``mtbdd_true``: representation of constant ``true``.
  144. - ``mtbdd_false``: representation of constant ``false``.
  145. - ``sylvan_ithvar(var)``: representation of literal <var> (negated: ``sylvan_nithvar(var)``)
  146. To follow the BDD edges and obtain the variable at the root of a BDD,
  147. you can use (only for internal nodes, not for leaves ``mtbdd_true`` and ``mtbdd_false``):
  148. - ``mtbdd_getvar(bdd)``: obtain the variable of the root node of <bdd>.
  149. - ``mtbdd_gethigh(bdd)``: follow the high edge of <bdd>.
  150. - ``mtbdd_getlow(bdd)``: follow the low edge of <bdd>.
  151. You need to manually reference BDDs that you want to keep during garbage
  152. collection (see the above explanation):
  153. - ``mtbdd_protect(bddptr)``: add a pointer reference to <bddptr>.
  154. - ``mtbdd_unprotect(bddptr)``: remove a pointer reference to <bddptr>.
  155. - ``mtbdd_refs_pushptr(bddptr)``: add a local pointer reference to <bddptr>.
  156. - ``mtbdd_refs_popptr(amount)``: remove the last <amount> local pointer references.
  157. - ``mtbdd_refs_spawn(SPAWN(...))``: spawn a task that returns a BDD/MTBDD.
  158. - ``mtbdd_refs_sync(SYNC(...))``: sync a task that returns a BDD/MTBDD.
  159. It is recommended to use ``mtbdd_protect`` and ``mtbdd_unprotect``.
  160. The C++ objects (defined in ``sylvan_obj.hpp``) handle this automatically.
  161. For local variables, we recommend ``mtbdd_refs_pushptr`` and ``mtbdd_refs_popptr``.
  162. The following basic BDD operations are implemented:
  163. - ``sylvan_not(bdd)``: compute the negation of <bdd>.
  164. - ``sylvan_ite(a,b,c)``: compute 'if <a> then <b> else <c>'.
  165. - ``sylvan_and(a, b)``: compute '<a> and <b>'.
  166. - ``sylvan_or(a, b)``: compute '<a> or <b>'.
  167. - ``sylvan_nand(a, b)``: compute 'not (<a> and <b>)'.
  168. - ``sylvan_nor(a, b)``: compute 'not (<a> or <b>)'.
  169. - ``sylvan_imp(a, b)``: compute '<a> then <b>'.
  170. - ``sylvan_invimp(a, b)``: compute '<b> then <a>'.
  171. - ``sylvan_xor(a, b)``: compute '<a> xor <b>'.
  172. - ``sylvan_equiv(a, b)``: compute '<a> = <b>'.
  173. - ``sylvan_diff(a, b)``: compute '<a> and not <b>'.
  174. - ``sylvan_less(a, b)``: compute '<b> and not <a>'.
  175. - ``sylvan_exists(bdd, vars)``: existential quantification of <bdd> with respect to variables <vars>.
  176. - ``sylvan_forall(bdd, vars)``: universal quantification of <bdd> with respect to variables <vars>.
  177. - ``sylvan_project(bdd, vars)``: the dual of ``sylvan_exists``, projects the <bdd> to the variable domain <vars>.
  178. A set of variables (like <vars> above) is a BDD representing the conjunction of the variables.
  179. A number of convencience functions are defined to manipulate sets of variables:
  180. - ``mtbdd_set_empty()``: obtain an empty set.
  181. - ``mtbdd_set_isempty(set)``: compute whether the set is empty.
  182. - ``mtbdd_set_first(set)``: obtain the first variable of the set.
  183. - ``mtbdd_set_next(set)``: obtain the subset without the first variable.
  184. - ``mtbdd_set_from_array(arr, len)``: create a set from a given array.
  185. - ``mtbdd_set_to_array(set, arr)``: write the set to the given array.
  186. - ``mtbdd_set_add(set, var)``: compute the set plus the variable.
  187. - ``mtbdd_set_union(set1, set2)``: compute the union of two sets.
  188. - ``mtbdd_set_remove(set, var)``: compute the set minus the variable.
  189. - ``mtbdd_set_minus(set1, set2)``: compute the set <set1> minus the variables in <set2>.
  190. - ``mtbdd_set_count(set)``: compute the number of variables in the set.
  191. - ``mtbdd_set_contains(set, var)``: compute whether the set contains the variable.
  192. Sylvan also implements composition and substitution/variable renaming using a "MTBDD map". An MTBDD map is a special structure
  193. implemented with special MTBDD nodes to store a mapping from variables (uint32_t) to MTBDDs. Like sets of variables and MTBDDs, MTBDD maps must
  194. also be referenced for garbage collection. The following functions are related to MTBDD maps:
  195. - ``mtbdd_compose(dd, map)``: apply the map to the given decision diagram, transforming every node with a variable that is associated with some function F in the map by ``if <F> then <high> else <low>``.
  196. - ``sylvan_compose(dd, map)``: same as ``mtbdd_compose``, but assumes the decision diagram only has Boolean leaves.
  197. - ``mtbdd_map_empty()``: obtain an empty map.
  198. - ``mtbdd_map_isempty(map)``: compute whether the map is empty.
  199. - ``mtbdd_map_key(map)``: obtain the key of the first pair of the map.
  200. - ``mtbdd_map_value(map)``: obtain the value of the first pair of the map.
  201. - ``mtbdd_map_next(map)``: obtain the submap without the first pair.
  202. - ``mtbdd_map_add(map, key, value)``: compute the map plus the given key-value pair.
  203. - ``mtbdd_map_update(map1, map2)``: compute the union of two maps, with priority to map2 if both maps share variables.
  204. - ``mtbdd_map_remove(map, var)``: compute the map minus the variable.
  205. - ``mtbdd_map_removeall(map, set)``: compute the map minus the given variables.
  206. - ``mtbdd_map_count(set)``: compute the number of pairs in the map.
  207. - ``mtbdd_map_contains(map, var)``: compute whether the map contains the variable.
  208. Sylvan implements a number of counting operations:
  209. - ``mtbdd_satcount(bdd, number_of_vars)``: compute the number of minterms (assignments that lead to True) for a function with <number_of_vars> variables; we don't need to know the exact variables that may be in the BDD, just how many there are.
  210. - ``sylvan_pathcount(bdd)``: compute the number of distinct paths to True.
  211. - ``mtbdd_nodecount(bdd)``: compute the number of nodes (and leaves) in the BDD.
  212. - ``mtbdd_nodecount_more(array, length)``: compute the number of nodes (and leaves) in the array of BDDs.
  213. Sylvan implements various advanced operations:
  214. - ``sylvan_and_exists(bdd_a, bdd_b, vars)``: compute ``sylvan_exists(sylvan_and(bdd_a, bdd_b), vars)`` in one step.
  215. - ``sylvan_and_project(bdd_a, bdd_b, vars)``: compute ``sylvan_project(sylvan_and(bdd_a, bdd_b), vars)`` in one step.
  216. - ``sylvan_cube(vars, values)``: compute a cube (to leaf True) of the given variables, where the array values indicates for each variable whether to use it in negative form (value 0) or positive form (value 1) or to skip it (as dont-care, value 2).
  217. - ``sylvan_union_cube(set, vars, values)``: compute ``sylvan_or(set, sylvan_cube(vars, values))`` in one step.
  218. - ``sylvan_constrain(bdd_f, bdd_c)``: compute the generic cofactor of F constrained by C, i.e, set F to False for all assignments not in C.
  219. - ``sylvan_restrict(bdd_f, bdd_c)``: compute Coudert and Madre's restrict algorithm, which tries to minimize bdd_f according to a care set C using sibling substitution; the invariant is ``restrict(f, c) \and c == f \and c``; the result of this algorithm is often but not always smaller than the original.
  220. - ``sylvan_pick_cube(bdd)`` or ``sylvan_sat_one_bdd(bdd)``: extract a single path to True from the BDD (returns the BDD of this path)
  221. - ``sylvan_pick_single_cube(bdd, vars)`` or ``sylvan_sat_single(bdd, vars)`` extracts a single minterm from the BDD (returns the BDD of this assignment)
  222. - ``sylvan_sat_one(bdd, vars, array)``: extract a single minterm from the BDD given the set of variables and write the values of the variables in order to the given array, with 0 when it is negative, 1 when it is positive, and 2 when it is dontcare.
  223. Sylvan implements several operations for transition systems. These operations assume an interleaved variable ordering, such that *source* or *unprimed* variables have even parity (0, 2, 4...) and matching *target* or *primed* variables have odd parity (1, 3, 5...).
  224. The transition relations may be partial transition relations that only manipulate a subset of variables; hence, the operations also require the set of variables.
  225. - ``sylvan_relnext(set, relation, vars)``: apply the (partial) relation on the given variables to the set.
  226. - ``sylvan_relprev(relation, set, vars)``: apply the (partial) relation in reverse to the set; this computes predecessors but can also concatenate relations as follows: ``sylvan_relprev(rel1, rel2, rel1_vars)``.
  227. - ``sylvan_closure(relation)``: compute the transitive closure of the given set recursively (see Matsunaga et al, DAC 1993)
  228. See ``src/sylvan_bdd.h`` and ``src/mtbdd.h`` for other operations on BDDs and MTBDDs.
  229. Custom leaves
  230. ~~~~~~~~~~~~~
  231. See ``src/sylvan_mt.h`` and the example in ``src/sylvan_gmp.h`` and ``src/sylvan_gmp.c`` for custom leaves in MTBDDs.
  232. Custom decision diagram operations
  233. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  234. Adding custom decision diagram operations is easy. Include ``sylvan_int.h`` for the internal functions. See ``sylvan_cache.h``
  235. for how to use the operation cache.
  236. List decision diagrams
  237. ~~~~~~~~~~~~~~~~~~~~~~
  238. See ``src/sylvan_ldd.h`` for operations on list decision diagrams.
  239. File I/O
  240. ~~~~~~~~
  241. You can store and load BDDs using a number of methods, which are documented in the header files ``sylvan_mtbdd.h`` and ``sylvan_ldd.h``.
  242. Support for C++
  243. ~~~~~~~~~~~~~~~
  244. See ``src/sylvan_obj.hpp`` for the C++ interface.
  245. Table resizing
  246. ~~~~~~~~~~~~~~
  247. During garbage collection, it is possible to resize the nodes table and
  248. the cache. By default, Sylvan doubles the table sizes during every garbage
  249. collection until the maximum table size has been reached. There is also a
  250. less aggressive version that only resizes when at least half the table is
  251. full. This can be configured in ``src/sylvan_config.h``. It is not
  252. possible to decrease the size of the nodes table and the cache.
  253. Dynamic reordering
  254. ~~~~~~~~~~~~~~~~~~
  255. Dynamic reordening is not yet supported. For now, we suggest users
  256. find a good static variable ordering.
  257. Examples
  258. --------
  259. Simple examples can be found in the ``examples`` subdirectory. The file
  260. ``simple.cpp`` contains a toy program that uses the C++ objects to
  261. perform basic BDD manipulation. The ``mc.c`` and ``lddmc.c`` programs
  262. are more advanced examples of symbolic model checking (with example
  263. models in the ``models`` subdirectory).
  264. Troubleshooting
  265. ---------------
  266. Sylvan may require a larger than normal program stack. You may need to
  267. increase the program stack size on your system using ``ulimit -s``.
  268. Segmentation faults on large computations typically indicate a program
  269. stack overflow.
  270. I am getting the error "unable to allocate memory: ...!"
  271. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  272. Sylvan allocates virtual memory using mmap. If you specify a combined
  273. size for the cache and node table larger than your actual available
  274. memory you may need to set ``vm.overcommit_memory`` to ``1``. E.g.
  275. ``echo 1 > /proc/sys/vm/overcommit_memory``. You can make this setting
  276. permanent with
  277. ``echo "vm.overcommit_memory = 1" > /etc/sysctl.d/99-sylvan.conf``. You
  278. can verify the setting with ``cat /proc/sys/vm/overcommit_memory``. It
  279. should report ``1``.
  280. I get errors about ``__lace_worker`` and ``__lace_dq_head``
  281. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  282. Many Sylvan operations are implemented as Lace tasks. To call a Lace
  283. task, the variables ``__lace_worker`` and ``__lace_dq_head`` must be
  284. initialized. Use the macro ``LACE_ME`` to do this. Only use ``LACE_ME``
  285. locally (in a function), never globally!
  286. Publications
  287. ------------
  288. T. van Dijk (2016) `Sylvan: Multi-core Decision
  289. Diagrams <http://dx.doi.org/10.3990/1.9789036541602>`__. PhD Thesis.
  290. T. van Dijk and J.C. van de Pol (2016) `Sylvan: Multi-core Framework
  291. for Decision Diagrams <http://dx.doi.org/10.1007/s10009-016-0433-2>`__.
  292. In: STTT (Special Issue), Springer.
  293. T. van Dijk and J.C. van de Pol (2015) `Sylvan: Multi-core Decision
  294. Diagrams <http://dx.doi.org/10.1007/978-3-662-46681-0_60>`__. In: TACAS
  295. 2015, LNCS 9035. Springer.
  296. T. van Dijk and A.W. Laarman and J.C. van de Pol (2012) `Multi-Core BDD
  297. Operations for Symbolic
  298. Reachability <http://eprints.eemcs.utwente.nl/22166/>`__. In: PDMC 2012,
  299. ENTCS. Elsevier.