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.