diff --git a/core/algorithms/substitute.cc b/core/algorithms/substitute.cc index de8b28d0f0..4410829a87 100644 --- a/core/algorithms/substitute.cc +++ b/core/algorithms/substitute.cc @@ -13,6 +13,11 @@ using namespace cadabra; + +/* Global instance of substitute::rules */ +substitute::Rules replacement_rules; + + substitute::substitute(const Kernel& k, Ex& tr, Ex& args_, bool partial) : Algorithm(k, tr), comparator(k.properties), args(args_), sort_product_(k, tr), partial(partial) { @@ -21,68 +26,81 @@ substitute::substitute(const Kernel& k, Ex& tr, Ex& args_, bool partial) // Stopwatch sw; // sw.start(); - cadabra::do_list(args, args.begin(), [&](Ex::iterator arrow) { - //args.print_recursive_treeform(std::cerr, arrow); - if(*arrow->name!="\\arrow" && *arrow->name!="\\equals") - throw ArgumentException("substitute: Argument is neither a replacement rule nor an equality."); - - sibling_iterator lhs=args.begin(arrow); - sibling_iterator rhs=lhs; - rhs.skip_children(); - ++rhs; - - if(*lhs->name=="") { // replacing a sub or superscript - lhs=tr.flatten_and_erase(lhs); - } - if(*rhs->name=="") { // replacing with a sub or superscript - rhs=tr.flatten_and_erase(rhs); - } - try { - if(*lhs->multiplier!=1) { - throw ArgumentException("substitute: No numerical pre-factors allowed on lhs of replacement rule."); + // Check if args are present in global_rules + // bool skipchecks = k.replacement_rules->is_present(args); + bool skipchecks = replacement_rules.is_present(args); + + // skip if args have already been checked + if (!skipchecks) { + cadabra::do_list(args, args.begin(), [&](Ex::iterator arrow) { + //args.print_recursive_treeform(std::cerr, arrow); + if(*arrow->name!="\\arrow" && *arrow->name!="\\equals") + throw ArgumentException("substitute: Argument is neither a replacement rule nor an equality."); + + sibling_iterator lhs=args.begin(arrow); + sibling_iterator rhs=lhs; + rhs.skip_children(); + ++rhs; + + if(*lhs->name=="") { // replacing a sub or superscript + lhs=tr.flatten_and_erase(lhs); + } + if(*rhs->name=="") { // replacing with a sub or superscript + rhs=tr.flatten_and_erase(rhs); } - // test validity of lhs and rhs - iterator lhsit=lhs, stopit=lhs; - stopit.skip_children(); - ++stopit; - while(lhsit!=stopit) { - if(lhsit->is_object_wildcard()) { - if(tr.number_of_children(lhsit)>0) { - throw ArgumentException("substitute: Object wildcards cannot have child nodes."); + + try { + if(*lhs->multiplier!=1) { + throw ArgumentException("substitute: No numerical pre-factors allowed on lhs of replacement rule."); + } + // test validity of lhs and rhs + iterator lhsit=lhs, stopit=lhs; + stopit.skip_children(); + ++stopit; + while(lhsit!=stopit) { + if(lhsit->is_object_wildcard()) { + if(tr.number_of_children(lhsit)>0) { + throw ArgumentException("substitute: Object wildcards cannot have child nodes."); + } } + ++lhsit; } - ++lhsit; - } - lhsit=rhs; - stopit=rhs; - stopit.skip_children(); - ++stopit; - while(lhsit!=stopit) { - if(lhsit->is_object_wildcard()) { - if(tr.number_of_children(lhsit)>0) { - throw ArgumentException("substitute: Object wildcards cannot have child nodes."); + lhsit=rhs; + stopit=rhs; + stopit.skip_children(); + ++stopit; + while(lhsit!=stopit) { + if(lhsit->is_object_wildcard()) { + if(tr.number_of_children(lhsit)>0) { + throw ArgumentException("substitute: Object wildcards cannot have child nodes."); + } } + ++lhsit; } - ++lhsit; - } - // check whether there are dummies. - index_map_t ind_free, ind_dummy; - classify_indices(lhs, ind_free, ind_dummy); - lhs_contains_dummies[arrow]= ind_dummy.size()>0; - ind_free.clear(); - ind_dummy.clear(); - if(rhs!=tr.end()) { - classify_indices(rhs, ind_free, ind_dummy); - rhs_contains_dummies[arrow]=ind_dummy.size()>0; + // check whether there are dummies. + index_map_t ind_free, ind_dummy; + classify_indices(lhs, ind_free, ind_dummy); + lhs_contains_dummies[arrow]= ind_dummy.size()>0; + ind_free.clear(); + ind_dummy.clear(); + if(rhs!=tr.end()) { + classify_indices(rhs, ind_free, ind_dummy); + rhs_contains_dummies[arrow]=ind_dummy.size()>0; + } } - } - catch(std::exception& er) { - throw ArgumentException(std::string("substitute: Index error in replacement rule. ")+er.what()); - } - return true; - }); + catch(std::exception& er) { + throw ArgumentException(std::string("substitute: Index error in replacement rule. ")+er.what()); + } + return true; + }); + replacement_rules.store(args, lhs_contains_dummies, rhs_contains_dummies); + } + else { + replacement_rules.retrieve(args, lhs_contains_dummies, rhs_contains_dummies); + } + // sw.stop(); // std::cerr << "preparation took " << sw << std::endl; } @@ -436,3 +454,85 @@ Algorithm::result_t substitute::apply(iterator& st) return result_t::l_applied; } + + + + +void substitute::Rules::store(Ex& rules, + std::map& lhs_contains_dummies, + std::map& rhs_contains_dummies) { + + // if number of stored rules has grown large, clean them up. + if (properties.size() >= cleanup_threshold) { + cleanup(); + } + // If that didn't fix it, double the cleanup_threshold up to max_size + if (cleanup_threshold != max_size && properties.size() >= cleanup_threshold) { + if (cleanup_threshold * 2 < max_size) { + cleanup_threshold *= 2; + } + else { + cleanup_threshold = max_size; + } + } + // If we're too big, don't add anything else. + if (properties.size() >= max_size) { + return; + } + + std::weak_ptr rules_ptr = rules.shared_from_this(); + properties[rules_ptr] = { lhs_contains_dummies, rhs_contains_dummies }; + // Set state of rules to l_checkpointed to track if the rules ever change + rules.reset_state(); + } + +void substitute::Rules::retrieve(Ex& rules, + std::map& lhs_contains_dummies, + std::map& rhs_contains_dummies) { + + // Rules::present is assumed to have been called to check that the rules are valid + std::weak_ptr rules_ptr = rules.shared_from_this(); + lhs_contains_dummies = properties[rules_ptr].first; + rhs_contains_dummies = properties[rules_ptr].second; + } + + +bool substitute::Rules::is_present(Ex& rules) { + // Look to see if the rules are present in the map + + std::weak_ptr rules_ptr = rules.shared_from_this(); + bool rule_found = (properties.find(rules_ptr) != properties.end()); + if (!rule_found) return false; + + // rules should have l_checkpointed set + bool rule_unchanged = (rules.state() == result_t::l_checkpointed); + + // If rule has been changed, erase it. + if (!rule_unchanged) { + properties.erase(rules_ptr); + return false; + } + else { + return true; + } + } + +int substitute::Rules::size() { + return properties.size(); +} + +void substitute::Rules::cleanup() { + // Erase rules that are pointing to garbage-collected Ex expressions + // or rules that have possibly been changed. + for (auto it = properties.begin(); it != properties.end(); ) { + if (it->first.expired()) { + it = properties.erase(it); + } + else if (it->first.lock()->state() != result_t::l_checkpointed) { + it = properties.erase(it); + } + else { + ++it; + } + } + } diff --git a/core/algorithms/substitute.hh b/core/algorithms/substitute.hh index 9174e03428..86ba5eaf7c 100644 --- a/core/algorithms/substitute.hh +++ b/core/algorithms/substitute.hh @@ -35,6 +35,35 @@ namespace cadabra { virtual result_t apply(iterator&); Ex_comparator comparator; + + // Rules is a class for caching properties of substitution rules to avoid + // processing them in subsequent calls + class Rules { + public: + // Associate rule properties with a specific object + void store(Ex& rules, std::map& lhs_contains_dummies, std::map& rhs_contains_dummies); + // Check if rules are present + bool is_present(Ex& rules); + // Retrieve properties from rules + void retrieve(Ex& rules, std::map& lhs_contains_dummies, std::map& rhs_contains_dummies); + // Count number of rules + int size(); + // Eliminate rules that are expired + void cleanup(); + + private: + // Map storing weak pointers to `Ex` and pairs of lhs/rhs maps as values + std::map, + std::pair, std::map>, + std::owner_less>> properties; + + // Initial size threshold to trigger cleanup_rules + unsigned int cleanup_threshold = 100; + // Store max size of the rules list to avoid it getting out of hand + unsigned int max_size = 1000; + }; + + private: Ex& args; @@ -48,4 +77,9 @@ namespace cadabra { bool partial; }; + /* Global instance of substitute::rules */ + extern substitute::Rules replacement_rules; + } + + diff --git a/core/pythoncdb/py_algorithms.cc b/core/pythoncdb/py_algorithms.cc index 4a8604e9ce..6cc1f8d58d 100644 --- a/core/pythoncdb/py_algorithms.cc +++ b/core/pythoncdb/py_algorithms.cc @@ -130,7 +130,7 @@ namespace cadabra { def_algo(m, "factor_in", true, false, 0, py::arg("factors")); def_algo(m, "factor_out", true, false, 0, py::arg("factors"), py::arg("right") = false); def_algo(m, "fierz", true, false, 0, py::arg("spinors")); - def_algo(m, "substitute", true, false, 0, py::arg("rules"), py::arg("partial") = true); + def_algo(m, "substitute", true, false, 0, py::arg("rules"), py::arg("partial") = true); def_algo(m, "take_match", true, false, 0, py::arg("rules")); def_algo(m, "replace_match", false, false, 0); def_algo(m, "zoom", true, false, 0, py::arg("rules"), py::arg("partial") = true); diff --git a/web2/cadabra2/source/changelog.html b/web2/cadabra2/source/changelog.html index 7d734f8440..a905b20d21 100644 --- a/web2/cadabra2/source/changelog.html +++ b/web2/cadabra2/source/changelog.html @@ -17,11 +17,11 @@

Change log

even-numbered ones are released in packaged/installer form.

-

github devel branch (2.5.7)

+

github devel branch (2.5.9)

- +

2.5.8 (released 25-Oct-2024)

  • Fix handling of spurious backslashes @@ -45,6 +45,7 @@

    2.5.8 (released 25-Oct-2024)

  • Generate source tarball asset with microtex included.
+

2.5.6 (released 29-Sep-2024)

  • Windows build is working again, now using MSYS2 because that's the only system that provides binary packages of our dependencies and also still carries gtkmm-3.0. An installer is now auto-generated by the CI on every release.
  • @@ -52,6 +53,7 @@

    2.5.6 (released 29-Sep-2024)

  • Many other small fixes and updates to ensure that all binaries can be built by the CI.
+

2.5.2 (released 14-Jun-2024)

  • Fix simplification of pow nodes.
  • @@ -74,6 +76,7 @@

    2.5.2 (released 14-Jun-2024)

  • Added dynamical cell updates, e.g. to create animations.
+

2.4.4 (released 20-Sep-2023)

  • Fix canonicalisation of rationals.
  • @@ -82,6 +85,7 @@

    2.4.4 (released 20-Sep-2023)

  • Prevent dummy indices from leaking out of \pow nodes.
+

2.4.2 (released 22-Oct-2022)

  • Add missing trig functions for display and passthrough to sympy (Oscar).
  • @@ -97,6 +101,7 @@

    2.4.2 (released 22-Oct-2022)

  • Fix compatibility issues with Python-3.11.
+

2.4.0 (released 25-Aug-2022)

  • Version to accompany the publication of diff --git a/web2/cadabra2/source/index.html b/web2/cadabra2/source/index.html index 220cf6b336..4629508338 100644 --- a/web2/cadabra2/source/index.html +++ b/web2/cadabra2/source/index.html @@ -26,6 +26,10 @@

    News

    +
    25 October 2024
    +
    Release of 2.5.8 (changes). + Mostly bug fixes and some improvements to the notebook interface. +
    24 September 2024
    Windows installer now available again, get it from the github release page.