1 |
|
/****************************************************************************** |
2 |
|
* Top contributors (to current version): |
3 |
|
* Morgan Deters, Andrew Reynolds, Clark Barrett |
4 |
|
* |
5 |
|
* This file is part of the cvc5 project. |
6 |
|
* |
7 |
|
* Copyright (c) 2009-2021 by the authors listed in the file AUTHORS |
8 |
|
* in the top-level source directory and their institutional affiliations. |
9 |
|
* All rights reserved. See the file COPYING in the top-level source |
10 |
|
* directory for licensing information. |
11 |
|
* **************************************************************************** |
12 |
|
* |
13 |
|
* Theory of arrays. |
14 |
|
*/ |
15 |
|
|
16 |
|
#include "cvc5_private.h" |
17 |
|
|
18 |
|
#ifndef CVC5__THEORY__ARRAYS__THEORY_ARRAYS_H |
19 |
|
#define CVC5__THEORY__ARRAYS__THEORY_ARRAYS_H |
20 |
|
|
21 |
|
#include <tuple> |
22 |
|
#include <unordered_map> |
23 |
|
|
24 |
|
#include "context/cdhashmap.h" |
25 |
|
#include "context/cdhashset.h" |
26 |
|
#include "context/cdqueue.h" |
27 |
|
#include "theory/arrays/array_info.h" |
28 |
|
#include "theory/arrays/inference_manager.h" |
29 |
|
#include "theory/arrays/proof_checker.h" |
30 |
|
#include "theory/arrays/theory_arrays_rewriter.h" |
31 |
|
#include "theory/decision_strategy.h" |
32 |
|
#include "theory/theory.h" |
33 |
|
#include "theory/theory_state.h" |
34 |
|
#include "theory/uf/equality_engine.h" |
35 |
|
#include "util/statistics_stats.h" |
36 |
|
|
37 |
|
namespace cvc5 { |
38 |
|
namespace theory { |
39 |
|
namespace arrays { |
40 |
|
|
41 |
|
/** |
42 |
|
* Decision procedure for arrays. |
43 |
|
* |
44 |
|
* Overview of decision procedure: |
45 |
|
* |
46 |
|
* Preliminary notation: |
47 |
|
* Stores(a) = {t | a ~ t and t = store( _ _ _ )} |
48 |
|
* InStores(a) = {t | t = store (b _ _) and a ~ b } |
49 |
|
* Indices(a) = {i | there exists a term b[i] such that a ~ b or store(b i v)} |
50 |
|
* ~ represents the equivalence relation based on the asserted equalities in the |
51 |
|
* current context. |
52 |
|
* |
53 |
|
* The rules implemented are the following: |
54 |
|
* store(b i v) |
55 |
|
* Row1 ------------------- |
56 |
|
* store(b i v)[i] = v |
57 |
|
* |
58 |
|
* store(b i v) a'[j] |
59 |
|
* Row ---------------------- [ a' ~ store(b i v) or a' ~ b ] |
60 |
|
* i = j OR a[j] = b[j] |
61 |
|
* |
62 |
|
* a b same kind arrays |
63 |
|
* Ext ------------------------ [ a!= b in current context, k new var] |
64 |
|
* a = b OR a[k] != b[k]p |
65 |
|
* |
66 |
|
* |
67 |
|
* The Row1 one rule is implemented implicitly as follows: |
68 |
|
* - for each store(b i v) term add the following equality to the congruence |
69 |
|
* closure store(b i v)[i] = v |
70 |
|
* - if one of the literals in a conflict is of the form store(b i v)[i] = v |
71 |
|
* remove it from the conflict |
72 |
|
* |
73 |
|
* Because new store terms are not created, we need to check if we need to |
74 |
|
* instantiate a new Row axiom in the following cases: |
75 |
|
* 1. the congruence relation changes (i.e. two terms get merged) |
76 |
|
* - when a new equality between array terms a = b is asserted we check if |
77 |
|
* we can instantiate a Row lemma for all pairs of indices i where a is |
78 |
|
* being read and stores |
79 |
|
* - this is only done during full effort check |
80 |
|
* 2. a new read term is created either as a consequences of an Ext lemma or a |
81 |
|
* Row lemma |
82 |
|
* - this is implemented in the checkRowForIndex method which is called |
83 |
|
* when preregistering a term of the form a[i]. |
84 |
|
* - as a consequence lemmas are instantiated even before full effort check |
85 |
|
* |
86 |
|
* The Ext axiom is instantiated when a disequality is asserted during full effort |
87 |
|
* check. Ext lemmas are stored in a cache to prevent instantiating essentially |
88 |
|
* the same lemma multiple times. |
89 |
|
*/ |
90 |
|
|
91 |
776996 |
static inline std::string spaces(int level) |
92 |
|
{ |
93 |
776996 |
std::string indentStr(level, ' '); |
94 |
776996 |
return indentStr; |
95 |
|
} |
96 |
|
|
97 |
|
class TheoryArrays : public Theory { |
98 |
|
|
99 |
|
///////////////////////////////////////////////////////////////////////////// |
100 |
|
// MISC |
101 |
|
///////////////////////////////////////////////////////////////////////////// |
102 |
|
|
103 |
|
private: |
104 |
|
|
105 |
|
/** True node for predicates = true */ |
106 |
|
Node d_true; |
107 |
|
|
108 |
|
/** True node for predicates = false */ |
109 |
|
Node d_false; |
110 |
|
|
111 |
|
// Statistics |
112 |
|
|
113 |
|
/** number of Row lemmas */ |
114 |
|
IntStat d_numRow; |
115 |
|
/** number of Ext lemmas */ |
116 |
|
IntStat d_numExt; |
117 |
|
/** number of propagations */ |
118 |
|
IntStat d_numProp; |
119 |
|
/** number of explanations */ |
120 |
|
IntStat d_numExplain; |
121 |
|
/** calls to non-linear */ |
122 |
|
IntStat d_numNonLinear; |
123 |
|
/** splits on array variables */ |
124 |
|
IntStat d_numSharedArrayVarSplits; |
125 |
|
/** splits in getModelVal */ |
126 |
|
IntStat d_numGetModelValSplits; |
127 |
|
/** conflicts in getModelVal */ |
128 |
|
IntStat d_numGetModelValConflicts; |
129 |
|
/** splits in setModelVal */ |
130 |
|
IntStat d_numSetModelValSplits; |
131 |
|
/** conflicts in setModelVal */ |
132 |
|
IntStat d_numSetModelValConflicts; |
133 |
|
|
134 |
|
public: |
135 |
|
TheoryArrays(Env& env, |
136 |
|
OutputChannel& out, |
137 |
|
Valuation valuation, |
138 |
|
std::string name = "theory::arrays::"); |
139 |
|
~TheoryArrays(); |
140 |
|
|
141 |
|
//--------------------------------- initialization |
142 |
|
/** get the official theory rewriter of this theory */ |
143 |
|
TheoryRewriter* getTheoryRewriter() override; |
144 |
|
/** get the proof checker of this theory */ |
145 |
|
ProofRuleChecker* getProofChecker() override; |
146 |
|
/** |
147 |
|
* Returns true if we need an equality engine. If so, we initialize the |
148 |
|
* information regarding how it should be setup. For details, see the |
149 |
|
* documentation in Theory::needsEqualityEngine. |
150 |
|
*/ |
151 |
|
bool needsEqualityEngine(EeSetupInfo& esi) override; |
152 |
|
/** finish initialization */ |
153 |
|
void finishInit() override; |
154 |
|
//--------------------------------- end initialization |
155 |
|
|
156 |
|
std::string identify() const override { return std::string("TheoryArrays"); } |
157 |
|
|
158 |
|
///////////////////////////////////////////////////////////////////////////// |
159 |
|
// PREPROCESSING |
160 |
|
///////////////////////////////////////////////////////////////////////////// |
161 |
|
|
162 |
|
private: |
163 |
|
|
164 |
|
// PPNotifyClass: dummy template class for d_ppEqualityEngine - notifications not used |
165 |
|
class PPNotifyClass { |
166 |
|
public: |
167 |
|
bool notify(TNode propagation) { return true; } |
168 |
|
void notify(TNode t1, TNode t2) { } |
169 |
|
}; |
170 |
|
|
171 |
|
/** The notify class for d_ppEqualityEngine */ |
172 |
|
PPNotifyClass d_ppNotify; |
173 |
|
|
174 |
|
/** Equaltity engine */ |
175 |
|
eq::EqualityEngine d_ppEqualityEngine; |
176 |
|
|
177 |
|
// List of facts learned by preprocessor - needed for permanent ref for benefit of d_ppEqualityEngine |
178 |
|
context::CDList<Node> d_ppFacts; |
179 |
|
|
180 |
|
Node preprocessTerm(TNode term); |
181 |
|
Node recursivePreprocessTerm(TNode term); |
182 |
|
bool ppDisequal(TNode a, TNode b); |
183 |
|
Node solveWrite(TNode term, bool solve1, bool solve2, bool ppCheck); |
184 |
|
|
185 |
|
/** The theory rewriter for this theory. */ |
186 |
|
TheoryArraysRewriter d_rewriter; |
187 |
|
/** A (default) theory state object */ |
188 |
|
TheoryState d_state; |
189 |
|
/** The arrays inference manager */ |
190 |
|
InferenceManager d_im; |
191 |
|
|
192 |
|
public: |
193 |
|
PPAssertStatus ppAssert(TrustNode tin, |
194 |
|
TrustSubstitutionMap& outSubstitutions) override; |
195 |
|
TrustNode ppRewrite(TNode atom, std::vector<SkolemLemma>& lems) override; |
196 |
|
|
197 |
|
///////////////////////////////////////////////////////////////////////////// |
198 |
|
// T-PROPAGATION / REGISTRATION |
199 |
|
///////////////////////////////////////////////////////////////////////////// |
200 |
|
|
201 |
|
private: |
202 |
|
/** Literals to propagate */ |
203 |
|
context::CDList<Node> d_literalsToPropagate; |
204 |
|
|
205 |
|
/** Index of the next literal to propagate */ |
206 |
|
context::CDO<unsigned> d_literalsToPropagateIndex; |
207 |
|
|
208 |
|
/** Should be called to propagate the literal. */ |
209 |
|
bool propagateLit(TNode literal); |
210 |
|
|
211 |
|
/** Explain why this literal is true by building an explanation */ |
212 |
|
void explain(TNode literal, Node& exp); |
213 |
|
|
214 |
|
/** For debugging only- checks invariants about when things are preregistered*/ |
215 |
|
context::CDHashSet<Node> d_isPreRegistered; |
216 |
|
|
217 |
|
/** Helper for preRegisterTerm, also used internally */ |
218 |
|
void preRegisterTermInternal(TNode n); |
219 |
|
|
220 |
|
public: |
221 |
|
void preRegisterTerm(TNode n) override; |
222 |
|
TrustNode explain(TNode n) override; |
223 |
|
|
224 |
|
///////////////////////////////////////////////////////////////////////////// |
225 |
|
// SHARING |
226 |
|
///////////////////////////////////////////////////////////////////////////// |
227 |
|
|
228 |
|
private: |
229 |
|
class MayEqualNotifyClass { |
230 |
|
public: |
231 |
|
bool notify(TNode propagation) { return true; } |
232 |
|
void notify(TNode t1, TNode t2) { } |
233 |
|
}; |
234 |
|
|
235 |
|
/** The notify class for d_mayEqualEqualityEngine */ |
236 |
|
MayEqualNotifyClass d_mayEqualNotify; |
237 |
|
|
238 |
|
/** Equaltity engine for determining if two arrays might be equal */ |
239 |
|
eq::EqualityEngine d_mayEqualEqualityEngine; |
240 |
|
|
241 |
|
// Helper for computeCareGraph |
242 |
|
void checkPair(TNode r1, TNode r2); |
243 |
|
|
244 |
|
public: |
245 |
|
void notifySharedTerm(TNode t) override; |
246 |
|
void computeCareGraph() override; |
247 |
|
bool isShared(TNode t) |
248 |
|
{ |
249 |
|
return (d_sharedArrays.find(t) != d_sharedArrays.end()); |
250 |
|
} |
251 |
|
|
252 |
|
///////////////////////////////////////////////////////////////////////////// |
253 |
|
// MODEL GENERATION |
254 |
|
///////////////////////////////////////////////////////////////////////////// |
255 |
|
|
256 |
|
public: |
257 |
|
/** Collect model values in m based on the relevant terms given by termSet */ |
258 |
|
bool collectModelValues(TheoryModel* m, |
259 |
|
const std::set<Node>& termSet) override; |
260 |
|
|
261 |
|
///////////////////////////////////////////////////////////////////////////// |
262 |
|
// NOTIFICATIONS |
263 |
|
///////////////////////////////////////////////////////////////////////////// |
264 |
|
|
265 |
|
|
266 |
|
void presolve() override; |
267 |
9848 |
void shutdown() override {} |
268 |
|
|
269 |
|
///////////////////////////////////////////////////////////////////////////// |
270 |
|
// MAIN SOLVER |
271 |
|
///////////////////////////////////////////////////////////////////////////// |
272 |
|
|
273 |
|
//--------------------------------- standard check |
274 |
|
/** Post-check, called after the fact queue of the theory is processed. */ |
275 |
|
void postCheck(Effort level) override; |
276 |
|
/** Pre-notify fact, return true if processed. */ |
277 |
|
bool preNotifyFact(TNode atom, |
278 |
|
bool pol, |
279 |
|
TNode fact, |
280 |
|
bool isPrereg, |
281 |
|
bool isInternal) override; |
282 |
|
/** Notify fact */ |
283 |
|
void notifyFact(TNode atom, bool pol, TNode fact, bool isInternal) override; |
284 |
|
//--------------------------------- end standard check |
285 |
|
|
286 |
|
private: |
287 |
|
TNode weakEquivGetRep(TNode node); |
288 |
|
TNode weakEquivGetRepIndex(TNode node, TNode index); |
289 |
|
void visitAllLeaves(TNode reason, std::vector<TNode>& conjunctions); |
290 |
|
void weakEquivBuildCond(TNode node, TNode index, std::vector<TNode>& conjunctions); |
291 |
|
void weakEquivMakeRep(TNode node); |
292 |
|
void weakEquivMakeRepIndex(TNode node); |
293 |
|
void weakEquivAddSecondary(TNode index, TNode arrayFrom, TNode arrayTo, TNode reason); |
294 |
|
void checkWeakEquiv(bool arraysMerged); |
295 |
|
|
296 |
|
// NotifyClass: template helper class for d_equalityEngine - handles call-back from congruence closure module |
297 |
9854 |
class NotifyClass : public eq::EqualityEngineNotify { |
298 |
|
TheoryArrays& d_arrays; |
299 |
|
public: |
300 |
9854 |
NotifyClass(TheoryArrays& arrays): d_arrays(arrays) {} |
301 |
|
|
302 |
54135 |
bool eqNotifyTriggerPredicate(TNode predicate, bool value) override |
303 |
|
{ |
304 |
108270 |
Debug("arrays::propagate") |
305 |
108270 |
<< spaces(d_arrays.getSatContext()->getLevel()) |
306 |
54135 |
<< "NotifyClass::eqNotifyTriggerPredicate(" << predicate << ", " |
307 |
54135 |
<< (value ? "true" : "false") << ")" << std::endl; |
308 |
|
// Just forward to arrays |
309 |
54135 |
if (value) { |
310 |
36846 |
return d_arrays.propagateLit(predicate); |
311 |
|
} |
312 |
17289 |
return d_arrays.propagateLit(predicate.notNode()); |
313 |
|
} |
314 |
|
|
315 |
155094 |
bool eqNotifyTriggerTermEquality(TheoryId tag, |
316 |
|
TNode t1, |
317 |
|
TNode t2, |
318 |
|
bool value) override |
319 |
|
{ |
320 |
155094 |
Debug("arrays::propagate") << spaces(d_arrays.getSatContext()->getLevel()) << "NotifyClass::eqNotifyTriggerTermEquality(" << t1 << ", " << t2 << ", " << (value ? "true" : "false") << ")" << std::endl; |
321 |
155094 |
if (value) { |
322 |
|
// Propagate equality between shared terms |
323 |
128390 |
return d_arrays.propagateLit(t1.eqNode(t2)); |
324 |
|
} |
325 |
26704 |
return d_arrays.propagateLit(t1.eqNode(t2).notNode()); |
326 |
|
} |
327 |
|
|
328 |
69 |
void eqNotifyConstantTermMerge(TNode t1, TNode t2) override |
329 |
|
{ |
330 |
69 |
Debug("arrays::propagate") << spaces(d_arrays.getSatContext()->getLevel()) << "NotifyClass::eqNotifyConstantTermMerge(" << t1 << ", " << t2 << ")" << std::endl; |
331 |
69 |
d_arrays.conflict(t1, t2); |
332 |
69 |
} |
333 |
|
|
334 |
41180 |
void eqNotifyNewClass(TNode t) override |
335 |
|
{ |
336 |
41180 |
d_arrays.preRegisterTermInternal(t); |
337 |
41180 |
} |
338 |
272015 |
void eqNotifyMerge(TNode t1, TNode t2) override |
339 |
|
{ |
340 |
272015 |
if (t1.getType().isArray()) { |
341 |
5814 |
d_arrays.mergeArrays(t1, t2); |
342 |
|
} |
343 |
272012 |
} |
344 |
31369 |
void eqNotifyDisequal(TNode t1, TNode t2, TNode reason) override {} |
345 |
|
}; |
346 |
|
|
347 |
|
/** The notify class for d_equalityEngine */ |
348 |
|
NotifyClass d_notify; |
349 |
|
|
350 |
|
/** The proof checker */ |
351 |
|
ArraysProofRuleChecker d_checker; |
352 |
|
|
353 |
|
/** Conflict when merging constants */ |
354 |
|
void conflict(TNode a, TNode b); |
355 |
|
|
356 |
|
/** The conflict node */ |
357 |
|
Node d_conflictNode; |
358 |
|
|
359 |
|
/** |
360 |
|
* Context dependent map from a congruence class canonical representative of |
361 |
|
* type array to an Info pointer that keeps track of information useful to axiom |
362 |
|
* instantiation |
363 |
|
*/ |
364 |
|
|
365 |
|
Backtracker<TNode> d_backtracker; |
366 |
|
ArrayInfo d_infoMap; |
367 |
|
|
368 |
|
context::CDQueue<Node> d_mergeQueue; |
369 |
|
|
370 |
|
bool d_mergeInProgress; |
371 |
|
|
372 |
|
using RowLemmaType = std::tuple<TNode, TNode, TNode, TNode>; |
373 |
|
|
374 |
|
context::CDQueue<RowLemmaType> d_RowQueue; |
375 |
|
context::CDHashSet<RowLemmaType, RowLemmaTypeHashFunction > d_RowAlreadyAdded; |
376 |
|
|
377 |
|
typedef context::CDHashSet<Node> CDNodeSet; |
378 |
|
|
379 |
|
CDNodeSet d_sharedArrays; |
380 |
|
CDNodeSet d_sharedOther; |
381 |
|
context::CDO<bool> d_sharedTerms; |
382 |
|
|
383 |
|
// Map from constant values to read terms that read from that values equal to that constant value in the current model |
384 |
|
// When a new read term is created, we check the index to see if we know the model value. If so, we add it to d_constReads (and d_constReadsList) |
385 |
|
// If not, we push it onto d_reads and figure out where it goes at computeCareGraph time. |
386 |
|
// d_constReadsList is used as a backup in case we can't compute the model at computeCareGraph time. |
387 |
|
typedef std::unordered_map<Node, CTNodeList*> CNodeNListMap; |
388 |
|
CNodeNListMap d_constReads; |
389 |
|
context::CDList<TNode> d_reads; |
390 |
|
context::CDList<TNode> d_constReadsList; |
391 |
|
context::Context* d_constReadsContext; |
392 |
|
/** Helper class to keep d_constReadsContext in sync with satContext */ |
393 |
9854 |
class ContextPopper : public context::ContextNotifyObj { |
394 |
|
context::Context* d_satContext; |
395 |
|
context::Context* d_contextToPop; |
396 |
|
protected: |
397 |
2966564 |
void contextNotifyPop() override |
398 |
|
{ |
399 |
2966564 |
if (d_contextToPop->getLevel() > d_satContext->getLevel()) |
400 |
|
{ |
401 |
23160 |
d_contextToPop->pop(); |
402 |
|
} |
403 |
2966564 |
} |
404 |
|
public: |
405 |
9854 |
ContextPopper(context::Context* context, context::Context* contextToPop) |
406 |
9854 |
:context::ContextNotifyObj(context), d_satContext(context), |
407 |
9854 |
d_contextToPop(contextToPop) |
408 |
9854 |
{} |
409 |
|
|
410 |
|
};/* class ContextPopper */ |
411 |
|
ContextPopper d_contextPopper; |
412 |
|
|
413 |
|
std::unordered_map<Node, Node> d_skolemCache; |
414 |
|
context::CDO<unsigned> d_skolemIndex; |
415 |
|
std::vector<Node> d_skolemAssertions; |
416 |
|
|
417 |
|
// The decision requests we have for the core |
418 |
|
context::CDQueue<Node> d_decisionRequests; |
419 |
|
|
420 |
|
// List of nodes that need permanent references in this context |
421 |
|
context::CDList<Node> d_permRef; |
422 |
|
context::CDList<Node> d_modelConstraints; |
423 |
|
context::CDHashSet<Node> d_lemmasSaved; |
424 |
|
std::vector<Node> d_lemmas; |
425 |
|
|
426 |
|
// Default values for each mayEqual equivalence class |
427 |
|
typedef context::CDHashMap<Node, Node> DefValMap; |
428 |
|
DefValMap d_defValues; |
429 |
|
|
430 |
|
typedef std::unordered_map<std::pair<TNode, TNode>, CTNodeList*, TNodePairHashFunction> ReadBucketMap; |
431 |
|
ReadBucketMap d_readBucketTable; |
432 |
|
context::Context* d_readTableContext; |
433 |
|
context::CDList<Node> d_arrayMerges; |
434 |
|
std::vector<CTNodeList*> d_readBucketAllocations; |
435 |
|
|
436 |
|
Node getSkolem(TNode ref); |
437 |
|
Node mkAnd(std::vector<TNode>& conjunctions, bool invert = false, unsigned startIndex = 0); |
438 |
|
void setNonLinear(TNode a); |
439 |
|
Node removeRepLoops(TNode a, TNode rep); |
440 |
|
Node expandStores(TNode s, std::vector<TNode>& assumptions, bool checkLoop = false, TNode a = TNode(), TNode b = TNode()); |
441 |
|
void mergeArrays(TNode a, TNode b); |
442 |
|
void checkStore(TNode a); |
443 |
|
void checkRowForIndex(TNode i, TNode a); |
444 |
|
void checkRowLemmas(TNode a, TNode b); |
445 |
|
void propagateRowLemma(RowLemmaType lem); |
446 |
|
void queueRowLemma(RowLemmaType lem); |
447 |
|
bool dischargeLemmas(); |
448 |
|
|
449 |
|
std::vector<Node> d_decisions; |
450 |
|
bool d_inCheckModel; |
451 |
|
int d_topLevel; |
452 |
|
|
453 |
|
/** |
454 |
|
* The decision strategy for the theory of arrays, which calls the |
455 |
|
* getNextDecisionEngineRequest function below. |
456 |
|
*/ |
457 |
19708 |
class TheoryArraysDecisionStrategy : public DecisionStrategy |
458 |
|
{ |
459 |
|
public: |
460 |
|
TheoryArraysDecisionStrategy(TheoryArrays* ta); |
461 |
|
/** initialize */ |
462 |
|
void initialize() override; |
463 |
|
/** get next decision request */ |
464 |
|
Node getNextDecisionRequest() override; |
465 |
|
/** identify */ |
466 |
|
std::string identify() const override; |
467 |
|
|
468 |
|
private: |
469 |
|
/** pointer to the theory of arrays */ |
470 |
|
TheoryArrays* d_ta; |
471 |
|
}; |
472 |
|
/** an instance of the above decision strategy */ |
473 |
|
std::unique_ptr<TheoryArraysDecisionStrategy> d_dstrat; |
474 |
|
/** Have we registered the above strategy? (context-independent) */ |
475 |
|
bool d_dstratInit; |
476 |
|
/** get the next decision request |
477 |
|
* |
478 |
|
* If the "arrays-eager-index" option is enabled, then whenever a |
479 |
|
* read-over-write lemma is generated, a decision request is also generated |
480 |
|
* for the comparison between the indexes that appears in the lemma. |
481 |
|
*/ |
482 |
|
Node getNextDecisionRequest(); |
483 |
|
/** |
484 |
|
* Compute relevant terms. This includes select nodes for the |
485 |
|
* RIntro1 and RIntro2 rules. |
486 |
|
*/ |
487 |
|
void computeRelevantTerms(std::set<Node>& termSet) override; |
488 |
|
};/* class TheoryArrays */ |
489 |
|
|
490 |
|
} // namespace arrays |
491 |
|
} // namespace theory |
492 |
|
} // namespace cvc5 |
493 |
|
|
494 |
|
#endif /* CVC5__THEORY__ARRAYS__THEORY_ARRAYS_H */ |