|
| 1 | +/*******************************************************************\ |
| 2 | +
|
| 3 | +Module: AIG Simplifier |
| 4 | +
|
| 5 | +Author: Daniel Kroening, dkr@amazon.com |
| 6 | +
|
| 7 | +\*******************************************************************/ |
| 8 | + |
| 9 | +#include "aig_simplifier.h" |
| 10 | + |
| 11 | +literalt |
| 12 | +apply_substitution(literalt l, const std::vector<literalt> &substitution) |
| 13 | +{ |
| 14 | + if(l.is_constant()) |
| 15 | + return l; |
| 16 | + |
| 17 | + auto var_no = l.var_no(); |
| 18 | + PRECONDITION(var_no < substitution.size()); |
| 19 | + |
| 20 | + // Get the replacement literal and apply the sign |
| 21 | + return substitution[var_no] ^ l.sign(); |
| 22 | +} |
| 23 | + |
| 24 | +/// Simplify an AND node after substitution |
| 25 | +/// \param a: First input literal (after substitution) |
| 26 | +/// \param b: Second input literal (after substitution) |
| 27 | +/// \param dest: The destination AIG to add the node to |
| 28 | +/// \return The literal representing the simplified AND |
| 29 | +static literalt simplify_and(literalt a, literalt b, aigt &dest) |
| 30 | +{ |
| 31 | + // Constant propagation |
| 32 | + if(a.is_false() || b.is_false()) |
| 33 | + return const_literal(false); |
| 34 | + |
| 35 | + if(a.is_true()) |
| 36 | + return b; |
| 37 | + |
| 38 | + if(b.is_true()) |
| 39 | + return a; |
| 40 | + |
| 41 | + // a AND a = a |
| 42 | + if(a == b) |
| 43 | + return a; |
| 44 | + |
| 45 | + // a AND !a = false |
| 46 | + if(a == !b) |
| 47 | + return const_literal(false); |
| 48 | + |
| 49 | + // Normalize: smaller variable number first |
| 50 | + if(b.var_no() < a.var_no()) |
| 51 | + std::swap(a, b); |
| 52 | + |
| 53 | + // Create the AND node |
| 54 | + return dest.new_and_node(a, b); |
| 55 | +} |
| 56 | + |
| 57 | +std::vector<literalt> |
| 58 | +aig_substitution(const aigt &src, const equivalencest &equivalences) |
| 59 | +{ |
| 60 | + if(src.empty()) |
| 61 | + return std::vector<literalt>(); |
| 62 | + |
| 63 | + const auto num_nodes = src.number_of_nodes(); |
| 64 | + |
| 65 | + // Build a substitution map: for each variable, what literal should it |
| 66 | + // be replaced with? Initially, each variable maps to itself. |
| 67 | + std::vector<literalt> substitution; |
| 68 | + substitution.reserve(num_nodes); |
| 69 | + |
| 70 | + for(std::size_t i = 0; i < num_nodes; i++) |
| 71 | + substitution.emplace_back(i, false); |
| 72 | + |
| 73 | + // Process equivalences. For each equivalence (l1, l2), we want to |
| 74 | + // replace references to the larger variable with the smaller one. |
| 75 | + for(const auto &[l1, l2] : equivalences) |
| 76 | + { |
| 77 | + // Skip invalid equivalences |
| 78 | + if(l1.is_constant() && l2.is_constant()) |
| 79 | + continue; |
| 80 | + |
| 81 | + // If one is constant, the other should map to that constant |
| 82 | + if(l1.is_constant()) |
| 83 | + { |
| 84 | + if(l2.var_no() < num_nodes) |
| 85 | + substitution[l2.var_no()] = l1 ^ l2.sign(); |
| 86 | + continue; |
| 87 | + } |
| 88 | + |
| 89 | + if(l2.is_constant()) |
| 90 | + { |
| 91 | + if(l1.var_no() < num_nodes) |
| 92 | + substitution[l1.var_no()] = l2 ^ l1.sign(); |
| 93 | + continue; |
| 94 | + } |
| 95 | + |
| 96 | + // Both are non-constant |
| 97 | + // l1 ≡ l2 means: v1 ^ s1 ≡ v2 ^ s2 |
| 98 | + // We replace the larger variable with the smaller one |
| 99 | + auto v1 = l1.var_no(); |
| 100 | + auto v2 = l2.var_no(); |
| 101 | + |
| 102 | + if(v1 >= num_nodes || v2 >= num_nodes) |
| 103 | + continue; |
| 104 | + |
| 105 | + // Compute the sign relationship: if l1 ≡ l2, then |
| 106 | + // v1 should map to v2 ^ (s1 ^ s2), or vice versa |
| 107 | + bool signs_differ = l1.sign() != l2.sign(); |
| 108 | + |
| 109 | + if(v1 < v2) |
| 110 | + { |
| 111 | + // Replace v2 with v1 |
| 112 | + substitution[v2] = literalt(v1, signs_differ); |
| 113 | + } |
| 114 | + else if(v2 < v1) |
| 115 | + { |
| 116 | + // Replace v1 with v2 |
| 117 | + substitution[v1] = literalt(v2, signs_differ); |
| 118 | + } |
| 119 | + // If v1 == v2, the equivalence is trivial (l1 ≡ l1 or l1 ≡ !l1) |
| 120 | + // The latter case (l1 ≡ !l1) would be a contradiction, ignore it |
| 121 | + } |
| 122 | + |
| 123 | + // Compute transitive closure of substitutions and propagate constraints |
| 124 | + // backwards: if an AND node is false, both inputs must be false |
| 125 | + // Repeat until no changes |
| 126 | + bool changed; |
| 127 | + do |
| 128 | + { |
| 129 | + changed = false; |
| 130 | + |
| 131 | + // Transitive closure |
| 132 | + for(std::size_t i = 0; i < num_nodes; i++) |
| 133 | + { |
| 134 | + literalt subst = substitution[i]; |
| 135 | + if(subst.is_constant() || subst.var_no() == i) |
| 136 | + continue; |
| 137 | + |
| 138 | + // Follow the substitution chain |
| 139 | + literalt next = substitution[subst.var_no()] ^ subst.sign(); |
| 140 | + if(next != subst) |
| 141 | + { |
| 142 | + substitution[i] = next; |
| 143 | + changed = true; |
| 144 | + } |
| 145 | + } |
| 146 | + |
| 147 | + // Backward propagation: if AND(a, b) = false, then a = false and b = false |
| 148 | + for(std::size_t i = 0; i < num_nodes; i++) |
| 149 | + { |
| 150 | + const auto &node = src.nodes[i]; |
| 151 | + |
| 152 | + // Check if this is an AND node that's been set to false |
| 153 | + if(node.is_and() && substitution[i].is_false()) |
| 154 | + { |
| 155 | + // Both inputs must be false |
| 156 | + // For node.a: if it's literalt(var, sign), we need var to map to |
| 157 | + // const_literal(false) XOR sign, so that var XOR sign = false |
| 158 | + if(!node.a.is_constant()) |
| 159 | + { |
| 160 | + auto var_a = node.a.var_no(); |
| 161 | + // We want node.a to evaluate to false |
| 162 | + // node.a = var_a XOR sign_a |
| 163 | + // For this to be false, var_a must be (false XOR sign_a) |
| 164 | + literalt new_val = const_literal(false) ^ node.a.sign(); |
| 165 | + if(substitution[var_a] != new_val) |
| 166 | + { |
| 167 | + substitution[var_a] = new_val; |
| 168 | + changed = true; |
| 169 | + } |
| 170 | + } |
| 171 | + |
| 172 | + // Same for node.b |
| 173 | + if(!node.b.is_constant()) |
| 174 | + { |
| 175 | + auto var_b = node.b.var_no(); |
| 176 | + literalt new_val = const_literal(false) ^ node.b.sign(); |
| 177 | + if(substitution[var_b] != new_val) |
| 178 | + { |
| 179 | + substitution[var_b] = new_val; |
| 180 | + changed = true; |
| 181 | + } |
| 182 | + } |
| 183 | + } |
| 184 | + } |
| 185 | + } while(changed); |
| 186 | + |
| 187 | + return substitution; |
| 188 | +} |
| 189 | + |
| 190 | +std::pair<aigt, substitutiont> apply_substitution( |
| 191 | + const aigt &src, |
| 192 | + const std::vector<literalt> &substitution) |
| 193 | +{ |
| 194 | + if(src.empty()) |
| 195 | + return {aigt{}, {}}; |
| 196 | + |
| 197 | + const auto num_nodes = src.number_of_nodes(); |
| 198 | + PRECONDITION(substitution.size() == num_nodes); |
| 199 | + |
| 200 | + // Now rebuild the AIG with substitutions applied |
| 201 | + aigt dest; |
| 202 | + |
| 203 | + // Map from old node indices to new literals |
| 204 | + std::vector<literalt> old_to_new(num_nodes); |
| 205 | + |
| 206 | + for(std::size_t i = 0; i < num_nodes; i++) |
| 207 | + { |
| 208 | + const auto &node = src.nodes[i]; |
| 209 | + |
| 210 | + // First, check what this node is substituted with |
| 211 | + literalt subst = substitution[i]; |
| 212 | + |
| 213 | + if(subst.is_constant()) |
| 214 | + { |
| 215 | + // This node is equivalent to a constant |
| 216 | + old_to_new[i] = subst; |
| 217 | + continue; |
| 218 | + } |
| 219 | + |
| 220 | + if(subst.var_no() != i) |
| 221 | + { |
| 222 | + // This node is equivalent to another (smaller) node |
| 223 | + // Use what that node maps to, applying the sign |
| 224 | + old_to_new[i] = old_to_new[subst.var_no()] ^ subst.sign(); |
| 225 | + continue; |
| 226 | + } |
| 227 | + |
| 228 | + // This node is a representative (maps to itself), we need to create it |
| 229 | + if(node.is_input()) |
| 230 | + { |
| 231 | + // primary input |
| 232 | + old_to_new[i] = dest.new_input(); |
| 233 | + } |
| 234 | + else |
| 235 | + { |
| 236 | + // AND node - substitute inputs and simplify |
| 237 | + literalt new_a = apply_substitution(node.a, old_to_new); |
| 238 | + literalt new_b = apply_substitution(node.b, old_to_new); |
| 239 | + old_to_new[i] = simplify_and(new_a, new_b, dest); |
| 240 | + } |
| 241 | + } |
| 242 | + |
| 243 | + // Re-number the labeling |
| 244 | + for(const auto &[label, lit] : src.labeling) |
| 245 | + { |
| 246 | + dest.labeling[label] = apply_substitution(lit, old_to_new); |
| 247 | + } |
| 248 | + |
| 249 | + return {dest, old_to_new}; |
| 250 | +} |
| 251 | + |
| 252 | +std::pair<aigt, substitutiont> aig_simplify(const aigt &src, const equivalencest &equivalences) |
| 253 | +{ |
| 254 | + auto substitution = aig_substitution(src, equivalences); |
| 255 | + return apply_substitution(src, substitution); |
| 256 | +} |
0 commit comments