mCRL2
Loading...
Searching...
No Matches
rewrite.cpp
Go to the documentation of this file.
1// Author(s): Muck van Weerdenburg
2// Copyright: see the accompanying file COPYING or copy at
3// https://github.com/mCRL2org/mCRL2/blob/master/COPYING
4//
5// Distributed under the Boost Software License, Version 1.0.
6// (See accompanying file LICENSE_1_0.txt or copy at
7// http://www.boost.org/LICENSE_1_0.txt)
8
9#include <ranges>
10
11#include "mcrl2/data/detail/rewrite/jitty.h"
12#include "mcrl2/data/detail/rewrite/jitty_jittyc.h"
13#include "mcrl2/data/detail/enumerator_iteration_limit.h"
14
15#ifdef MCRL2_ENABLE_JITTYC
16#include "mcrl2/data/detail/rewrite/jittyc.h"
17#endif
18
19#include "mcrl2/data/detail/rewrite/with_prover.h"
20
21#include "mcrl2/data/detail/rewriter_wrapper.h"
22#include "mcrl2/data/enumerator.h"
23
24using namespace mcrl2::core;
25using namespace mcrl2::core::detail;
26using namespace mcrl2::log;
27
28
29
30namespace mcrl2::data::detail
31{
32
33#ifndef NDEBUG
34// function object to test if it is an aterm with function symbol "f"
36{
37 bool operator()(const atermpp::aterm& t) const
38 {
39 return is_variable(t);
40 }
41};
42
43static
44bool occur_check(const variable& v, const atermpp::aterm& e)
45{
46 if (v==e)
47 {
48 // The variable is reset. This is ok.
49 return true;
50 }
51 std::set<variable> s;
52 find_all_if(e,is_a_variable(),std::inserter(s,s.begin()));
53 if (s.count(v)>0)
54 {
55 return false; // Occur check failed.
56 }
57 return true;
58}
59#endif
60
62 data_expression& result,
63 const where_clause& term,
64 substitution_type& sigma)
65{
66 const assignment_list& assignments = term.assignments();
67 const data_expression& body=term.body();
68
69 mutable_map_substitution<std::map < variable,data_expression> > variable_renaming;
70 bool rewrite_stack_too_big_exception_thrown=false;
71 try
72 {
73 for (const assignment& a: assignments)
74 {
75 const variable& v=a.lhs();
76 const variable v_fresh(m_generator(), v.sort());
77 variable_renaming[v]=v_fresh;
78 sigma[v_fresh]=rewrite(a.rhs(),sigma);
79 }
80 rewrite(result, replace_variables(body,variable_renaming),sigma);
81 }
83 {
84 rewrite_stack_too_big_exception_thrown=true;
85 }
86
87 // Reset variables in sigma
88 for (mutable_map_substitution<std::map < variable,data_expression> >::const_iterator it=variable_renaming.begin();
89 it!=variable_renaming.end(); ++it)
90 {
91 sigma[atermpp::down_cast<variable>(it->second)]=it->second;
92 }
93 if (rewrite_stack_too_big_exception_thrown)
94 {
96 }
97 return;
98}
99
100void Rewriter::rewrite_single_lambda(
101 data_expression& result,
102 const variable_list& vl,
103 const data_expression& body,
104 substitution_type& sigma,
105 const bool body_in_normal_form)
106{
107 assert(vl.size()>0);
108 // A lambda term without arguments; Take care that the bound variable is made unique with respect to
109 // the variables occurring in sigma. But in case vl is empty, just rewrite...
110
111 // First filter the variables in vl by those occuring as left/right hand sides in sigma.
112
113 std::size_t number_of_renamed_variables=0;
114 std::size_t count=0;
115 std::vector<variable> new_variables(vl.size());
116 {
117 // Create new unique variables to replace the old and create storage for
118 // storing old values for variables in vl.
119 for(const variable& v: vl)
120 {
121 if (sigma(v)!=v || sigma.variable_occurs_in_a_rhs(v))
122 {
123 number_of_renamed_variables++;
124 new_variables[count]=data::variable(m_generator(), v.sort());
125 assert(occur_check(v, new_variables[count]));
126 }
127 else
128 {
129 new_variables[count]=v;
130 }
131 count++;
132 }
133 }
134
135 if (number_of_renamed_variables==0)
136 {
137 make_abstraction(result,lambda_binder(),vl,(body_in_normal_form?body:rewrite(body,sigma)));
138 return;
139 }
140
141 data_expression result_aux;
142 variable_list::const_iterator v;
143 if (body_in_normal_form)
144 {
145 // If the body is already in normal form, a simple replacement of the old variables
146 // by the new ones will do
147 mutable_map_substitution<std::map<variable,data_expression> > variable_renaming;
148 for(v = vl.begin(), count = 0; v != vl.end(); ++v, ++count)
149 {
150 if (*v != new_variables[count])
151 {
152 variable_renaming[*v] = new_variables[count];
153 }
154 }
155 result_aux = replace_variables(body, variable_renaming);
156 }
157 else
158 {
159 // If the body is not in normal form, then we have to rewrite with an updated sigma.
160 // We first change sigma and save the values in sigma we overwrote...
161 std::vector<data_expression> saved_substitutions;
162 for(v = vl.begin(), count = 0; v != vl.end(); ++v, ++count)
163 {
164 if (*v != new_variables[count])
165 {
166 saved_substitutions.push_back(sigma(*v));
167 sigma[*v] = new_variables[count];
168 }
169 }
170 // ... then we rewrite with the new sigma ...
171 bool rewrite_stack_too_big_exception_thrown=false;
172 try
173 {
174 rewrite(result_aux,body,sigma);
175 }
177 {
178 rewrite_stack_too_big_exception_thrown=true;
179 }
180 // ... and then we restore sigma to its old state.
181 std::size_t new_variable_count = 0;
182 for(v = vl.begin(), count = 0; v != vl.end(); ++v, ++count)
183 {
184 if (*v != new_variables[count])
185 {
186 sigma[*v] = saved_substitutions[new_variable_count++];
187 }
188 }
189 if (rewrite_stack_too_big_exception_thrown)
190 {
192 }
193 }
194 variable_list new_variable_list(new_variables.begin(), new_variables.end());
195 make_abstraction(result, lambda_binder(),new_variable_list,result_aux);
196 return;
197}
198
199
200// The function rewrite_lambda_application assumes that t has the shape
201// application(...application(lambda x:D...., arg1,...,argn),argn+1,...,argN).
202// It applies the lambda term to its arguments, and rewrites the result to
203// normal form.
204
205void Rewriter::rewrite_lambda_application(
206 data_expression& result,
207 const data_expression& t,
208 substitution_type& sigma,
209 const bool arguments_are_in_normal_form /*= false*/)
210{
211 if (is_lambda(t))
212 {
213 const abstraction& ta=atermpp::down_cast<abstraction>(t);
214 rewrite_single_lambda(result,ta.variables(),ta.body(), sigma, arguments_are_in_normal_form);
215 return;
216 }
217
218 const application ta(t);
219 assert(is_lambda(ta.head()));
220 rewrite_lambda_application(result,atermpp::down_cast<abstraction>(ta.head()),ta,sigma, arguments_are_in_normal_form);
221 return;
222}
223
224// The function rewrite_lambda_application rewrites a lambda term to a set of
225// arguments which are the arguments 1,...,n of t. If t has the shape
226// #REWR#(t0,t1,....,tn) and the lambda term is L, we calculate the normal form
227// in internal format for L(t1,...,tn). Note that the term t0 is ignored.
228// Note that we assume that neither L, nor t is in normal form.
229
230void Rewriter::rewrite_lambda_application(
231 data_expression& result,
232 const abstraction& lambda_term,
233 const application& t,
234 substitution_type& sigma,
235 const bool arguments_are_in_normal_form /*= false*/)
236{
237 assert(is_lambda(lambda_term)); // The function symbol in this position cannot be anything else than a lambda term.
238 const variable_list& vl=lambda_term.variables();
239 const data_expression& lambda_body=lambda_term.body();
240 std::size_t arity=t.size();
241 if (arity==0) // The term has shape application(lambda d..:D...t), i.e. without arguments.
242 {
243 rewrite_single_lambda(result, vl, lambda_body, sigma, arguments_are_in_normal_form);
244 return;
245 }
246 assert(vl.size()<=arity);
247
248 // The variable vl_backup will be used to first store the values to be substituted
249 // for the variables in vl. Subsequently, it will be used to temporarily save the values in sigma
250 // assigned to vl.
251 data_expression* vl_backup = MCRL2_SPECIFIC_STACK_ALLOCATOR(data_expression,vl.size());
252
253 // Calculate the values that must be substituted for the variables in vl and store these in vl_backup.
254 for(std::size_t count=0; count<vl.size(); count++)
255 {
256 if (arguments_are_in_normal_form)
257 {
258 new (&vl_backup[count]) data_expression(t[count]);
259 }
260 else
261 {
262 new (&vl_backup[count]) data_expression(rewrite(t[count],sigma));
263 }
264 }
265
266 // Swap the values assigned to variables in vl with those in vl_backup.
267 std::size_t count=0;
268 for(const variable& v: vl)
269 {
270 const data_expression temp=sigma(v);
271 sigma[v]=vl_backup[count];
272 vl_backup[count]=temp;
273 count++;
274 }
275
276 bool rewrite_stack_too_big_exception_thrown=false;
277 try
278 {
279 rewrite(result,lambda_body,sigma);
280 }
282 {
283 rewrite_stack_too_big_exception_thrown=true;
284 }
285
286 // Reset variables in sigma and destroy the elements in vl_backup.
287 count=0;
288 for(const variable& v: vl)
289 {
290 sigma[v]=vl_backup[count];
291 vl_backup[count].~data_expression();
292 count++;
293 }
294 if (rewrite_stack_too_big_exception_thrown)
295 {
297 }
298
299 if (vl.size()==arity)
300 {
301 return;
302 }
303
304 // There are more arguments than bound variables.
305 // Rewrite the remaining arguments and apply the rewritten lambda term to them.
306 make_application(result,
307 result,
308 t.begin()+static_cast<std::ptrdiff_t>(vl.size()),
309 t.end(),
310 [this, arguments_are_in_normal_form, &sigma](data_expression& result, const data_expression& t) -> void
311 {
312 if (arguments_are_in_normal_form){ result=t;} else { result=rewrite(t, sigma); }
313 },
314 false // This false indicates that the function is not applied to head, i.e., result.
315 );
316 return;
317}
318
319void Rewriter::existential_quantifier_enumeration(
320 data_expression& result,
321 const abstraction& t,
322 substitution_type& sigma,
323 const bool t_is_normal_form /*= false*/)
324{
325 // This is a quantifier elimination that works on the existential quantifier as specified
326 // in data types, i.e. without applying the implement function anymore.
327
328 assert(is_exists(t));
329 existential_quantifier_enumeration(result, t.variables(), t.body(), sigma, t_is_normal_form);
330 return;
331}
332
333// Generate a term equivalent to exists vl.t1.
334// The variable t1_is_normal_form indicates whether t1 is in normal
335// form, but this information is not used as it stands.
336void Rewriter::existential_quantifier_enumeration(
337 data_expression& result,
338 const variable_list& vl,
339 const data_expression& t1,
340 substitution_type& sigma,
341 const bool t1_is_normal_form /*= false*/)
342{
343 quantifier_enumeration(result, vl, t1, t1_is_normal_form, sigma, exists_binder(), &lazy::or_, sort_bool::false_(), sort_bool::true_());
344 return;
345}
346
347
348void Rewriter::universal_quantifier_enumeration(
349 data_expression& result,
350 const abstraction& t,
351 substitution_type& sigma,
352 const bool t_is_normal_form /*= false*/)
353{
354 assert(is_forall(t));
355 universal_quantifier_enumeration(result, t.variables(),t.body(),sigma, t_is_normal_form);
356 return;
357}
358
359// Generate a term equivalent to forall vl.t1.
360// The variable t1_is_normal_form indicates whether t1 is in normal
361// form, but this information is not used as it stands.
362void Rewriter::universal_quantifier_enumeration(
363 data_expression& result,
364 const variable_list& vl,
365 const data_expression& t1,
366 substitution_type& sigma,
367 const bool t1_is_normal_form)
368{
369 quantifier_enumeration(result, vl, t1, t1_is_normal_form, sigma, forall_binder(), &lazy::and_, sort_bool::true_(), sort_bool::false_());
370 return;
371}
372
373void Rewriter::quantifier_enumeration(
374 data_expression& result,
375 const variable_list& vl,
376 const data_expression& t1,
377 const bool t1_is_normal_form,
378 substitution_type& sigma,
379 const binder_type& binder,
380 data_expression (*lazy_op)(const data_expression&, const data_expression&),
381 const data_expression& identity_element,
382 const data_expression& absorbing_element
383 )
384{
385 // Rename the bound variables to unique
386 // variables, to avoid naming conflicts.
387
388 mutable_map_substitution<std::map < variable,data_expression> > variable_renaming;
389 variable_vector vl_new_v;
390 for(const variable& v: vl)
391 {
392 if (sigma(v)!=v || sigma.variable_occurs_in_a_rhs(v))
393 {
394 const variable v_fresh(m_generator(), v.sort());
395 variable_renaming[v]=v_fresh;
396 vl_new_v.push_back(v_fresh);
397 }
398 else
399 {
400 vl_new_v.push_back(v);
401 }
402 }
403
404 const data_expression t2=replace_variables(t1,variable_renaming);
405 const data_expression t3=(t1_is_normal_form?t2:rewrite(t2,sigma));
406
407 // Check whether the bound variables occur free in the rewritten body
408 std::set < variable > free_variables=find_free_variables(t3);
409 variable_list vl_new_l_enum;
410 variable_list vl_new_l_other;
411
412 bool sorts_are_finite=true;
413 for (const variable& v: std::ranges::reverse_view(vl_new_v))
414 {
415 if (free_variables.count(v)>0)
416 {
417 if (is_enumerable(m_data_specification_for_enumeration, rewriter_wrapper(this), v.sort()))
418 {
419 vl_new_l_enum.push_front(v);
420 sorts_are_finite = sorts_are_finite && m_data_specification_for_enumeration.is_certainly_finite(v.sort());
421 }
422 else
423 {
424 vl_new_l_other.push_front(v);
425 }
426 }
427 }
428
429 if (vl_new_l_enum.empty())
430 {
431 if (vl_new_l_other.empty())
432 {
433 result=t3;
434 return; // No quantified variables are bound.
435 }
436 else
437 {
438 make_abstraction(result, binder, vl_new_l_other, t3);
439 return;
440 }
441 }
442
444 using enumerator_element = data::enumerator_list_element<data_expression>;
445
446 /* Find A solution*/
447 rewriter_wrapper wrapped_rewriter(this);
448 const std::size_t max_count = sorts_are_finite || data::detail::get_enumerator_iteration_limit()==0?
449 std::numeric_limits<std::size_t>::max() :
450 data::detail::get_enumerator_iteration_limit();
451 try
452 {
453 enumerator_type enumerator(wrapped_rewriter, m_data_specification_for_enumeration,
454 wrapped_rewriter, m_generator, false, max_count);
455
456 /* Create a list to store solutions */
457 data_expression partial_result = identity_element;
458
459 std::size_t count=enumerator.enumerate(enumerator_element(vl_new_l_enum, t3),
460 sigma,
461 [&](const enumerator_element& p)
462 {
463 partial_result = lazy_op(partial_result, p.expression());
464 return partial_result==absorbing_element;
465 },
466 [&identity_element](const data_expression& d)->bool { return d==identity_element; },
467 [&absorbing_element](const data_expression& d)->bool { return d==absorbing_element; }
468 );
469
470 if (count<=max_count) // Enumeration is successful. If not fall through and return the original, simplified expression.
471 {
472 if (vl_new_l_other.empty())
473 {
474 result=partial_result;
475 return;
476 }
477 else
478 {
479 make_abstraction(result,binder, vl_new_l_other, partial_result);
480 return;
481 }
482 }
483 }
484 catch(const mcrl2::runtime_error&) // NOLINT(bugprone-empty-catch)
485 {
486 // if (max_count==std::numeric_limits<std::size_t>::max()) // No bound on enumeration.
487 // {
488 // throw e;
489 // }
490 // It is not possible to enumerate one of the bound variables, or the enumerator limit of the
491 // iterator is exceeded. So, we just return the original non enumerated, simplified expression.
492 }
493 make_abstraction(result, binder,vl_new_l_enum+vl_new_l_other,rewrite(t3,sigma));
494 return;
495}
496
497
498std::shared_ptr<Rewriter> createRewriter(
499 const data_specification& data_spec,
500 const used_data_equation_selector& equations_selector,
501 const rewrite_strategy strategy)
502{
503 switch (strategy)
504 {
505 case data::rewrite_strategy::jitty:
506 return std::shared_ptr<Rewriter>(new RewriterJitty(data_spec,equations_selector));
507#ifdef MCRL2_ENABLE_JITTYC
508 case data::rewrite_strategy::jitty_compiling:
509 return std::shared_ptr<Rewriter>(new RewriterCompilingJitty(data_spec,equations_selector));
510#endif
511 case data::rewrite_strategy::jitty_prover:
512 return std::shared_ptr<Rewriter>(new RewriterProver(data_spec,data::rewrite_strategy::jitty,equations_selector));
513#ifdef MCRL2_ENABLE_JITTYC
514 case data::rewrite_strategy::jitty_compiling_prover:
515 return std::shared_ptr<Rewriter>(new RewriterProver(data_spec,data::rewrite_strategy::jitty_compiling,equations_selector));
516#endif
517 default: throw mcrl2::runtime_error("Cannot create a rewriter using strategy " + pp(strategy) + ".");
518 }
519}
520
521//Prototype
522static void check_vars(const data_expression& expr, const std::set <variable>& vars, std::set <variable>& used_vars);
523
524static void check_vars(application::const_iterator begin,
525 const application::const_iterator& end,
526 const std::set <variable>& vars,
527 std::set <variable>& used_vars)
528{
529 while (begin != end)
530 {
531 check_vars(*begin++, vars, used_vars);
532 }
533}
534
535static void check_vars(const data_expression& expr, const std::set <variable>& vars, std::set <variable>& used_vars)
536{
537 if (is_application(expr))
538 {
539 const application& a=atermpp::down_cast<application>(expr);
540 check_vars(a.head(),vars,used_vars);
541 check_vars(a.begin(),a.end(),vars,used_vars);
542 }
543 else if (is_variable(expr))
544 {
545 const variable& v=atermpp::down_cast<variable>(expr);
546 used_vars.insert(v);
547
548 if (vars.count(v)==0)
549 {
550 throw variable(v);
551 }
552 }
553}
554
555//Prototype
556static void checkPattern(const data_expression& p);
557
558static void checkPattern(application::const_iterator begin,
559 const application::const_iterator& end)
560{
561 while (begin != end)
562 {
563 checkPattern(*begin++);
564 }
565}
566
567static void checkPattern(const data_expression& p)
568{
569 if (is_application(p))
570 {
571 if (is_variable(atermpp::down_cast<application>(p).head()))
572 {
573 throw mcrl2::runtime_error(std::string("variable ") + data::pp(application(p).head()) +
574 " is used as head symbol in an application, which is not supported");
575 }
576 const application& a=atermpp::down_cast<application>(p);
577 checkPattern(a.head());
578 checkPattern(a.begin(),a.end());
579 }
580}
581
582void CheckRewriteRule(const data_equation& data_eqn)
583{
584 const variable_list& rule_var_list = data_eqn.variables();
585 const std::set <variable> rule_vars(rule_var_list.begin(),rule_var_list.end());
586
587 // collect variables from lhs and check that they are in rule_vars
588 std::set <variable> lhs_vars;
589 try
590 {
591 check_vars(data_eqn.lhs(),rule_vars,lhs_vars);
592 }
593 catch (variable& var)
594 {
595 // This should never occur if data_eqn is a valid data equation
596 mCRL2log(log::log_level_t::error) << "Data equation: " << data_expression(data_eqn) << std::endl;
597 assert(0);
598 throw runtime_error("variable " + pp(var) + " occurs in left-hand side of equation but is not defined (in equation: " + pp(data_eqn) + ")");
599 }
600
601 // check that variables from the condition occur in the lhs
602 try
603 {
604 std::set <variable> dummy;
605 check_vars(data_eqn.condition(),lhs_vars,dummy);
606 }
607 catch (variable& var)
608 {
609 throw runtime_error("variable " + pp(var) + " occurs in condition of equation but not in left-hand side (in equation: " +
610 pp(data_eqn) + "); equation cannot be used as rewrite rule");
611 }
612
613 // check that variables from the rhs are occur in the lhs
614 try
615 {
616 std::set <variable> dummy;
617 check_vars(data_eqn.rhs(),lhs_vars,dummy);
618 }
619 catch (variable& var)
620 {
621 throw runtime_error("variable " + pp(var) + " occurs in right-hand side of equation but not in left-hand side (in equation: " +
622 pp(data_eqn) + "); equation cannot be used as rewrite rule");
623 }
624
625 // check that the lhs is a supported pattern
626 if (is_variable(data_eqn.lhs()))
627 {
628 throw runtime_error("left-hand side of equation is a variable; this is not allowed for rewriting");
629 }
630 try
631 {
632 checkPattern(data_eqn.lhs());
633 }
634 catch (mcrl2::runtime_error& s)
635 {
636 throw runtime_error(std::string(s.what()) + " (in equation: " + pp(data_eqn) + "); equation cannot be used as rewrite rule");
637 }
638}
639
641{
642 try
643 {
644 CheckRewriteRule(data_eqn);
645 return true;
646 }
647 catch (runtime_error&)
648 {
649 return false;
650 }
651 return false; // compiler warning
652}
653
654}
A unordered_map class in which aterms can be stored.
An abstraction expression.
Definition abstraction.h:23
const variable_list & variables() const
Definition abstraction.h:60
const data_expression & body() const
Definition abstraction.h:65
\brief A data equation
const data_expression & lhs() const
const variable_list & variables() const
data_expression & operator=(const data_expression &) noexcept=default
Rewriter interface class.
Definition rewrite.h:39
void rewrite_where(data_expression &result, const where_clause &term, substitution_type &sigma)
Definition rewrite.cpp:61
An enumerator algorithm that generates solutions of a condition.
Definition enumerator.h:599
The default element for the todo list of the enumerator.
Definition enumerator.h:229
Component for selecting a subset of equations that are actually used in an encompassing specification...
Definition selection.h:36
\brief A data variable
Definition variable.h:25
variable(const variable &) noexcept=default
Move semantics.
\brief A where expression
const assignment_list & assignments() const
const data_expression & body() const
#define mCRL2log(LEVEL)
mCRL2log(LEVEL) provides the stream used to log.
Definition logger.h:435
void CheckRewriteRule(const data_equation &data_eqn)
Check that an mCRL2 data equation is a valid rewrite rule. If not, an runtime_error is thrown indicat...
Definition rewrite.cpp:582
static void checkPattern(const data_expression &p)
Definition rewrite.cpp:567
static void check_vars(application::const_iterator begin, const application::const_iterator &end, const std::set< variable > &vars, std::set< variable > &used_vars)
Definition rewrite.cpp:524
bool isValidRewriteRule(const data_equation &data_eqn)
Check whether or not an mCRL2 data equation is a valid rewrite rule.
Definition rewrite.cpp:640
static bool occur_check(const variable &v, const atermpp::aterm &e)
Definition rewrite.cpp:44
static void checkPattern(application::const_iterator begin, const application::const_iterator &end)
Definition rewrite.cpp:558
static void check_vars(const data_expression &expr, const std::set< variable > &vars, std::set< variable > &used_vars)
Definition rewrite.cpp:535
bool is_application(const data_expression &t)
Returns true if the term t is an application.
bool is_forall(const atermpp::aterm &x)
Returns true if the term t is a universal quantification.
bool is_exists(const atermpp::aterm &x)
Returns true if the term t is an existential quantification.
bool is_lambda(const atermpp::aterm &x)
Returns true if the term t is a lambda abstraction.
bool is_variable(const atermpp::aterm &x)
Returns true if the term t is a variable.
bool operator()(const atermpp::aterm &t) const
Definition rewrite.cpp:37