diff --git a/core/Compare.cc b/core/Compare.cc index 41fddff3cd..8d08976a68 100644 --- a/core/Compare.cc +++ b/core/Compare.cc @@ -320,7 +320,7 @@ namespace cadabra { // // if(!i1.is_valid() && !i2.is_valid()) // return match_t::subtree_match; - + DEBUGLN( std::cerr << tab() << "equal_subtree with use_props = " << use_props << std::endl; ); ++offset; diff --git a/core/IndexClassifier.cc b/core/IndexClassifier.cc index 947e75e520..e1ac474c48 100644 --- a/core/IndexClassifier.cc +++ b/core/IndexClassifier.cc @@ -490,8 +490,7 @@ Ex IndexClassifier::get_dummy(const list_property *dums, const index_map_t * four, const index_map_t * five) const { - std::pair - pr=kernel.properties.pats.equal_range(dums); + std::pair pr=kernel.properties.equal_range(dums); // std::cerr << "finding index not in: " << std::endl; // if(one) @@ -512,9 +511,9 @@ Ex IndexClassifier::get_dummy(const list_property *dums, while(pr.first!=pr.second) { // std::cerr << "trying: " << std::endl; - // std::cerr << pr.first->second->obj << std::endl; + // std::cerr << pr.first->second->obj << std::endl; if(pr.first->second->obj.begin()->is_autodeclare_wildcard()) { - // std::cerr << "is autodeclare wildcard" << std::endl; + // std::cerr << "is autodeclare wildcard" << std::endl; std::string base=*pr.first->second->obj.begin()->name_only(); int used=max_numbered_name(base, one, two, three, four, five); std::ostringstream str; @@ -526,15 +525,15 @@ Ex IndexClassifier::get_dummy(const list_property *dums, return ret; } else { - // std::cerr << "is NOT autodeclare" << std::endl; + // std::cerr << "is NOT autodeclare" << std::endl; const Ex& inm=(*pr.first).second->obj; // BUG: even if only _{a} is in the used map, we should not // accept ^{a}. But since ... if(index_in_set(inm, one)==false && - index_in_set(inm, two)==false && - index_in_set(inm, three)==false && - index_in_set(inm, four)==false && - index_in_set(inm, five)==false) { + index_in_set(inm, two)==false && + index_in_set(inm, three)==false && + index_in_set(inm, four)==false && + index_in_set(inm, five)==false) { // std::cerr << "ok to use " << inm << std::endl; return inm; } diff --git a/core/Kernel.cc b/core/Kernel.cc index c5a71d95eb..e8fa1e5a10 100644 --- a/core/Kernel.cc +++ b/core/Kernel.cc @@ -112,7 +112,7 @@ Kernel::~Kernel() // std::cerr << "~Kernel() " << this << std::endl; } -void Kernel::inject_property(property *prop, std::shared_ptr ex, std::shared_ptr param) +const property* Kernel::inject_property(property *prop, std::shared_ptr ex, std::shared_ptr param) { Ex::iterator it=ex->begin(); @@ -124,7 +124,7 @@ void Kernel::inject_property(property *prop, std::shared_ptr ex, std::shared } // Validate and insert a copy of the property. prop->validate(*this, ex); - properties.master_insert(Ex(it), prop); + return properties.master_insert(Ex(it), prop); } std::shared_ptr Kernel::ex_from_string(const std::string& s) diff --git a/core/Kernel.hh b/core/Kernel.hh index 1c8c178708..57a12892b7 100644 --- a/core/Kernel.hh +++ b/core/Kernel.hh @@ -20,7 +20,7 @@ namespace cadabra { /// Inject a property into the system and attach it to the given pattern. /// Transfers property ownership to the kernel. - void inject_property(property *prop, std::shared_ptr pattern, std::shared_ptr property_arguments); + const property* inject_property(property *prop, std::shared_ptr pattern, std::shared_ptr property_arguments); /// Create an Ex expression object from a string, which will be parsed. std::shared_ptr ex_from_string(const std::string&); diff --git a/core/Props.cc b/core/Props.cc index 3b9683c074..c34be4f0da 100644 --- a/core/Props.cc +++ b/core/Props.cc @@ -22,10 +22,11 @@ along with this program. If not, see . #include "Exceptions.hh" #include "Compare.hh" #include -#include +// #include #include #include #include "properties/Indices.hh" +#include // #define DEBUG 1 @@ -139,37 +140,50 @@ bool pattern::children_wildcard() const bool Properties::has(const property *pb, Ex::iterator it) { - std::pair pit=props.equal_range(it->name_only()); - while(pit.first!=pit.second) { - // txtout << *it->name << std::endl; - // txtout << typeid(pit.first->second.second).name() << " versus " - // << typeid(pb).name() << std::endl; - const property *tmp = (pit.first->second.second); - if(typeid(*tmp)==typeid(*pb) && - pit.first->second.first->match(*this, it)) // match found + // Does Ex::iterator `it` possess property *pb? + + auto pdit = props_dict.find(it->name_only()); + if (pdit == props_dict.end()) { + return false; + } + + // Look for property *pb + typemap_t::iterator pptit = pdit->second.find(typeid(*pb)); + if (pptit == pdit->second.end()) { + return false; + } + + // Check if any prop_pat pair matches + for (const prop_pat_pair_t& prop_pat : pptit->second) { + if (prop_pat.second->match(*this, it)) { return true; - ++pit.first; + } } + return false; } + void Properties::clear() { // Clear and free the property lists. Since pointers to properties can - // be shared, we use the pats map and make sure that we only free each - // property* pointer once. - pattern_map_t::const_iterator it=pats.begin(); - const property *previous=0; - while(it!=pats.end()) { - if(previous!=it->first) { - previous=it->first; - delete it->first; + // be shared (but patterns cannot yet), we use the pats_dict map and make + // sure that we only free each property* pointer once. + + for (const auto& [_, this_pats] : pats_dict) { + auto it=this_pats.begin(); + const property *previous=0; + while(it!=this_pats.end()) { + if(previous!=it->first) { + previous=it->first; + delete it->first; + } + delete it->second; + ++it; } - delete it->second; - ++it; } - props.clear(); - pats.clear(); + props_dict.clear(); + pats_dict.clear(); } Properties::registered_property_map_t::~registered_property_map_t() @@ -177,11 +191,44 @@ Properties::registered_property_map_t::~registered_property_map_t() // FIXME: V2 } +template +void Properties::registered_property_map_t::register_type() { + T obj; + // obj is an actual property object (e.g. AntiCommuting) and not a pointer + std::type_index typeidx = typeid(obj); + auto it = types_to_names_.find(typeidx); + if (it == types_to_names_.end()) { + types_to_names_[typeidx] = obj.name(); + names_to_types_.emplace(obj.name(),typeidx); + } +} + +void Properties::registered_property_map_t::register_type(const property* prop) { + std::type_index typeidx(typeid(*prop)); + auto it = types_to_names_.find(typeidx); + if (it == types_to_names_.end()) { + types_to_names_[typeidx] = prop->name(); + names_to_types_.emplace(prop->name(),typeidx); + } +} + + void Properties::register_property(property* (*fun)(), const std::string& name) { registered_properties.store[name]=fun; } +template +void Properties::register_property_type() + { + registered_properties.register_type(); + } + +void Properties::register_property_type(const property* prop) + { + registered_properties.register_type(prop); + } + keyval_t::const_iterator keyval_t::find(const std::string& key) const { keyval_t::const_iterator it=keyvals.begin(); @@ -312,7 +359,7 @@ std::string property::unnamed_argument() const return ""; } -property::match_t property::equals(const property *) const +list_property::match_t list_property::equals(const property *) const { return exact_match; } @@ -346,138 +393,146 @@ bool labelled_property::parse(Kernel&, std::shared_ptr, keyval_t& keyvals) // return one.obj==two.obj; // FIXME: handle dummy indices // } -void Properties::insert_prop(const Ex& et, const property *pr) - { +void Properties::insert_prop(const Ex& et, const property *pr) { // assert(pats.find(pr)==pats.end()); // identical properties have to be assigned through insert_list_prop - // FIXME: is it really necessary to store this by pointer? We are in any case - // not cleaning this up correctly yet. - pattern *pat=new pattern(et); + // Create the pattern from et + pattern *pat = new pattern(et); - std::pair pit= - props.equal_range(pat->obj.begin()->name_only()); - - property_map_t::iterator first_nonpattern=pit.first; - - while(pit.first!=pit.second) { - // keep track of the first non-pattern element - if(Ex::number_of_children((*pit.first).second.first->obj.begin())==1) - if((*pit.first).second.first->obj.begin().begin()->is_range_wildcard()) - ++first_nonpattern; + // Make sure there is no existing property of the same type matching pat + auto walk = begin(pat->obj.begin()->name_only()); + while (walk != end()) { + if (typeid(*walk->first) != typeid(*pr)) { + walk.next_proptype(); + continue; + } // A given pattern can only have one property of any given type. The following - // triggers on entries in the props map which match the pattern to be inserted. - if((*pit.first).second.first->match(*this, et.begin())) { - - // Does this entry in props give a property of the same type as the one we - // are trying to insert? - const property *tmp = (*pit.first).second.second; - if(typeid(*pr)==typeid(*tmp)) { - - // If this is a labelled property, is the label different from the one on the - // property we are trying to insert? - const labelled_property *lp =dynamic_cast(pr); - const labelled_property *lpold=dynamic_cast(pit.first->second.second); - - if(!lp || !lpold || lp->label==lpold->label) { - - // The to-be-inserted property cannot co-exist on this pattern with the - // one that is currently associated to the pattern. Remove it. - - pattern *oldpat =pit.first->second.first; - const property *oldprop=pit.first->second.second; - - // If the new property instance is the same as the old one, we can stop - // (this happens if a pattern is accidentally repeated in a property assignment). - if(oldprop==pr) { - delete pat; - return; - } - - // Erase the pattern->property entry, and delete the pattern. - // FIXME: store pattern by value. - props.erase(pit.first); - delete oldpat; - - // Remove the property->pattern entry. Only delete the property - // if it is no longer associated to any other pattern. - // FIXME: - // {A, B}::SelfAntiCommuting. - // {A}::SelfAntiCommuting. - // {B}::SelfAntiCommuting. - // leads to two properties SelfAntiCommuting, which are identical. - // We need a way to compare properties and decide when they are - // identical, or when they can coexist, or something like that. - for(auto pi=pats.begin(); pi!=pats.end(); ++pi) { - if((*pi).first==oldprop && (*pi).second==oldpat) { - // std::cerr << "found old entry, deleting" << std::endl; - pats.erase(pi); - break; - } - } - if(pats.find(oldprop)==pats.end()) { - // std::cerr << "no other references" << std::endl; - delete oldprop; - } - - break; + // triggers on entries in the props map which match the pattern to be inserted + // and are of the same type as pr. + if( walk->second->match(*this, et.begin())) { + // If this is a labelled property, is the label different from the one on the + // property we are trying to insert? + const labelled_property *lp = dynamic_cast(pr); + const labelled_property *lpold = dynamic_cast(walk->first); + + if(!lp || !lpold || lp->label==lpold->label) { + // The to-be-inserted property cannot co-exist on this pattern with the + // one that is currently associated to the pattern. Remove it. + const property *oldprop = walk->first; + pattern *oldpat = walk->second; + + // If the new property instance is the same as the old one, we can stop + // (this happens if a pattern is accidentally repeated in a property assignment). + if(oldprop==pr) { + delete pat; + return; } - } + + // Erase the pattern->property entry, and delete the pattern. + // FIXME: store pattern by value. + iterator old_dup = walk; + ++walk; + // std::cerr << "Freeing: " << old_dup_it->first << '\n'; + erase(old_dup->first, old_dup->second); + + // Remove the property->pattern entry. Only delete the property + // if it is no longer associated to any other pattern. + // FIXME: + // {A, B}::SelfAntiCommuting. + // {A}::SelfAntiCommuting. + // {B}::SelfAntiCommuting. + // leads to two properties SelfAntiCommuting, which are identical. + // We need a way to compare properties and decide when they are + // identical, or when they can coexist, or something like that. + + // FIXME: SelfAntiCommuting is not a list property, so above should be fine. + } else { + ++walk; } - ++pit.first; + } else { + ++walk; } - - pats.insert(pattern_map_t::value_type(pr, pat)); - // std::cerr << "inserting for " << *(pat->obj.begin()->name) << std::endl; - props.insert(property_map_t::value_type(pat->obj.begin()->name_only(), pat_prop_pair_t(pat,pr))); } -void Properties::insert_list_prop(const std::vector& its, const list_property *pr) + register_property_type(pr); + // Add to the props_dict + props_dict[pat->obj.begin()->name_only()][typeid(*pr)].emplace(pr,pat); + // Add to the pats_dict + pats_dict[typeid(*pr)].emplace(pr, pat); +} + + + +// Insert a list property into the kernel. +const list_property* Properties::insert_list_prop(const std::vector& its, const list_property* pr) { - assert(pats.find(pr)==pats.end()); // identical properties have to be assigned through insert_list_prop assert(its.size()>0); + + auto& pats = pats_dict[typeid(*pr)]; + assert(pats.find(pr)==pats.end()); // identical properties have to be assigned through insert_list_prop + + /* Description of below code + + List properties in Cadabra include (currently) Indices, SortOrder, CommutingBehaviour (and derived classes). + Calling `equals` on another list property will yield `no_match`, `id_match`, or `exact_match`. + Upon insertion of a list property, we look for existing properties of the same type. + + If we find one of the same type and `equals` returns `exact_match`, we discard (and delete) + the list_property pointer. This ensures e.g. that there is only one + Indices property with the label "vector". The code then continues, as if this property was the + one passed to `insert_list_prop`. + + If we find a list property that returns `id_match`, we delete that older list property. + This happens e.g. if we have Indices(vector, position=free) and then create + Indices(vector, position=fixed). This invalidates all the prior free vector indices. + + If `no_match`, we continue as normal. + + The is the current state of affairs (except `equals` was declared in `property` rather than `list_property`.) + + However, a problem arises (as discussed below). In the older code, the following failed: + {a,b,c,d,e}::Indices(vector). + {a,b,c}::Indices(spinor). + This should make a,b,c spinor indices, and keep d,e as vector indices. + Instead, both sets were kept. + Similarly, + {a,b,c}::Indices(vector). + {a,b,c,d,e,f}::Indices(spinor). + This should make all indices spinor indices. + Instead, both sets were kept. + + Eventually we should fix this. It is somewhat subtle as it requires we do something else. + A fourth match type is needed that would indicate we need to look at the properties and + eliminate incompatible types. + + */ // If 'pr' is exactly equal to an existing property, we should use that one instead of // introducing a duplicate. - pattern_map_t::iterator fit=pats.begin(); - while(fit!=pats.end()) { - const property *tmp = (*fit).first; - if(typeid(*tmp)==typeid(*pr)) - if(pr->equals((*fit).first)==property::exact_match) { - pr=static_cast( (*fit).first ); - break; - } - ++fit; - } - - // If 'pr' has id_match with an existing property, we need to remove all property assignments - // for the existing one, except when there is an exact_match. - const property *to_delete_property=0; - pattern_map_t::iterator pit=pats.begin(); + // Alternatively, if 'pr' has id_match with an existing property, we need to remove all property assignments + // for the existing one. + auto pit=pats.begin(); while(pit!=pats.end()) { const property *tmp = (*pit).first; - if(typeid(*tmp)==typeid(*pr)) - if(pr->equals((*pit).first)==property::id_match) { - to_delete_property = (*pit).first; - break; - } - ++pit; - } - if(to_delete_property) { - pats.erase(to_delete_property); - property_map_t::iterator it=props.begin(); - while(it!=props.end()) { - property_map_t::iterator nxt=it; - ++nxt; - if((*it).second.second==to_delete_property) props.erase(it); - it=nxt; - } + // if(typeid(*tmp)==typeid(*pr)) // unnecessary now + list_property::match_t match_type = pr->equals((*pit).first); + if (match_type == list_property::exact_match) { + delete pr; + pr=static_cast( (*pit).first ); + // Later we return the new pr to the caller so they have a valid pointer. + break; + } else if (match_type == list_property::id_match) { + erase((*pit).first); + break; } - - + ++pit; + } + // Now register the list property. + register_property_type(pr); - for(unsigned int i=0; i& its, const list_propert // Now register the property. // txtout << "registering " << *(pat->headnode) << std::endl; - pats.insert(pattern_map_t::value_type(pr, pat)); - props.insert(property_map_t::value_type(pat->obj.begin()->name_only(), pat_prop_pair_t(pat,pr))); + + props_dict[pat->obj.begin()->name_only()][typeid(*pr)].emplace(pr, pat); + pats.emplace(pr, pat); } + return pr; } int Properties::serial_number(const property *listprop, const pattern *pat) const { int serialnum=0; - - std::pair - pm=pats.equal_range(listprop); - serialnum=0; + auto it = pats_dict.find(typeid(*listprop)); + if (it == pats_dict.end()) { + return serialnum; + } + auto pm=it->second.equal_range(listprop); while(pm.first!=pm.second) { if((*pm.first).second==pat) break; @@ -576,10 +634,12 @@ d,e should have their property removed. // Insert a property for the given pattern Ex. Determines whether the property // is a list property or a normal one, and dispatches accordingly. +// Returns a pointer to the new property. (For a list property, this may differ +// from the original pointer.) -std::string Properties::master_insert(Ex proptree, const property *thepropbase) +const property* Properties::master_insert(Ex proptree, const property *thepropbase) { - std::ostringstream str; + // std::ostringstream str; Ex::sibling_iterator st=proptree.begin(); @@ -614,10 +674,10 @@ std::string Properties::master_insert(Ex proptree, const property *thepropbase) obj2.begin()->fl.parent_rel=str_node::p_sub; objs2.push_back(obj2); } - insert_list_prop(objs2, thelistprop); + thelistprop = insert_list_prop(objs2, thelistprop); } else { - insert_list_prop(objs, thelistprop); + thelistprop = insert_list_prop(objs, thelistprop); } } else { // a normal property @@ -637,7 +697,12 @@ std::string Properties::master_insert(Ex proptree, const property *thepropbase) insert_prop(Ex(st), theprop); } } - return str.str(); + // return str.str(); + if (thelistprop) { + return dynamic_cast(thelistprop); + } else { + return thepropbase; + } } bool Properties::check_label(const property* p, const std::string& label) const @@ -659,3 +724,101 @@ void Properties::destroy_comparator(Ex_comparator *c) const { delete c; } + + +// Completely erase a property. +void Properties::erase(const property* p) { + // Make a list of matching patterns + auto pad_it = pats_dict.find(typeid(*p)); + if (pad_it == pats_dict.end()) + return; + + // FIXME: Above should perhaps throw an exception? Property not found or some such. + + std::vector pattern_names; + auto patterns = pad_it->second.equal_range(p); + for (auto it = patterns.first; it != patterns.second; ++it) { + // Store all the pattern names + pattern_names.push_back(it->second->obj.begin()->name_only()); + // Free all the patterns + delete it->second; + } + + // Delete all the entries in pats_dict + pad_it->second.erase(p); + + // Delete all the entries in props_dict + for (auto pattern_name : pattern_names) { + props_dict[pattern_name][typeid(*p)].erase(p); + } + + delete p; +} + +// Erase a property and related pattern. +void Properties::erase(const property* prop, pattern* pat) { + auto name = pat->obj.begin()->name_only(); + int num_found = 0; + + auto pad_it = pats_dict.find(typeid(*prop)); + if (pad_it == pats_dict.end()) { + throw ConsistencyException("Properties erase failure: Cannot find property of matching type to property/pattern pair to erase."); + } + + + // Eliminate from props_dict (where they are first keyed by name of pattern) + auto pdit = props_dict.find(name); + if (pdit != props_dict.end()) { + auto ppit = pdit->second.find(typeid(*prop)); + if (ppit != pdit->second.end()) { + auto& named_typed_pairs = ppit->second; + for (auto pairs_it = named_typed_pairs.begin(); pairs_it != named_typed_pairs.end(); ) { + if (pairs_it->first == prop && pairs_it->second == pat) { + pairs_it = named_typed_pairs.erase(pairs_it); + ++num_found; + } else { + ++pairs_it; + } + } + } + } + + // Look over all prop/pat pairs of matching types and erase them + auto& all_typed_pairs = pad_it->second; + for (auto pairs_it = all_typed_pairs.begin(); pairs_it != all_typed_pairs.end(); ) { + if (pairs_it->first == prop && pairs_it->second == pat) { + pairs_it = all_typed_pairs.erase(pairs_it); + ++num_found; + } else { + ++pairs_it; + } + } + + // Does prop have any more patterns associated with it? + if (all_typed_pairs.find(prop) == all_typed_pairs.end()) { + delete prop; + } + if (num_found != 2) { + throw ConsistencyException("Properties erase failure: Inconsistent numbers of property/patterns erased."); + } + + delete pat; +} + +std::pair > Properties::lookup_property(const property* sus) const { + // Warning: sus may be invalid + std::pair> ret = {nullptr, {}}; + + for (const auto& [_, pats] : pats_dict) { + auto range = pats.equal_range(sus); + if (range.first == range.second) continue; + + // property is found, so sus is valid + ret.first = sus; + for (auto it = range.first; it != range.second; ++it) { + ret.second.push_back(it->second); + } + break; + } + return ret; +} \ No newline at end of file diff --git a/core/Props.hh b/core/Props.hh index b2db4fa953..47f6a298a3 100644 --- a/core/Props.hh +++ b/core/Props.hh @@ -27,6 +27,11 @@ along with this program. If not, see . #include #include #include "Storage.hh" +#include +#include +#include + +#include namespace cadabra { @@ -166,15 +171,6 @@ namespace cadabra { virtual std::string name() const=0; virtual std::string unnamed_argument() const; - // To compare properties we sometimes need to compare their variables, not only - // their type. The following function needs to be overridden in all properties - // for which comparison by type is not sufficient to establish equality. - // - // id_match: only one of these properties can be registered, but their data is not the same - // exact_match: these properties are exactly identical - enum match_t { no_match, id_match, exact_match }; - virtual match_t equals(const property *) const; - /// Properties can be hidden because they only make sense to the /// system; they will not be printed when the user asks for a list /// of properties. @@ -197,6 +193,14 @@ namespace cadabra { class list_property : public property { public: + // To compare list properties we sometimes need to compare their variables, not only + // their type. The following function needs to be overridden in all properties + // for which comparison by type is not sufficient to establish equality. + // + // id_match: only one of these properties can be registered, but their data is not the same + // exact_match: these properties are exactly identical + enum match_t { no_match, id_match, exact_match }; + virtual match_t equals(const property *) const; }; /// If a property X derives from Inherit, and get is called on @@ -241,7 +245,8 @@ namespace cadabra { class Properties { public: - // Registering property types. + + // Class to store names and std::type_index for property objects class registered_property_map_t { public: ~registered_property_map_t(); @@ -250,15 +255,50 @@ namespace cadabra { typedef internal_property_map_t::iterator iterator; internal_property_map_t store; - }; + // Modifications to registered_property_map_t. + // FIXME: Pieces above here are old and do not seem to be used anywhere. + // (Pieces below here are new.) + + // Register a type by template. Usage: `register_type();` + template + void register_type(); + + // Register the type of an object. Usage: `register_type(prop);` + void register_type(const property* prop); + + // Fuzzy bool type + enum fbool {False, True, Unknown}; + fbool castable(std::type_index to, std::type_index from); + + private: + // Dictionary from type_index to human readable name. + std::map types_to_names_; + // Dictionary from human readable name to type_index. + std::multimap names_to_types_; + + + }; /// Registering properties. When inserting a property or /// list_property, ownership of the property gets transferred to /// this class. - + + // FIXME: register_property does not seem to be used. void register_property(property* (*)(), const std::string& name); + registered_property_map_t registered_properties; - typedef std::pair pat_prop_pair_t; + // typedef std::pair pat_prop_pair_t; + typedef std::pair prop_pat_pair_t; + + // Register a type by template. Usage: `register_property_type();` + // Just calls `registered_properties.register_type()` + template void register_property_type(); + + // Register the type of an object. Usage: `register_property_type(prop);` + // Just calls `registered_properties.register_type(obj)` + void register_property_type(const property* prop); + + /// We keep two multi-maps: one from the pattern to the property (roughly) and /// one from the property to the pattern. These are both multi-maps because @@ -267,13 +307,14 @@ namespace cadabra { /// /// When we delete properties, we check the pats map to see if the reference count /// for that property has dropped to zero. - typedef std::multimap property_map_t; - typedef std::multimap pattern_map_t; + + // typedef std::multimap property_map_t; + // typedef std::multimap pattern_map_t; /// Register a property for the indicated Ex. Takes both normal and list /// properties and works out which insert calls to make. The property ownership /// is transferred to us on using this call. - std::string master_insert(Ex proptree, const property *thepropbase); + const property* master_insert(Ex proptree, const property *thepropbase); void clear(); @@ -281,8 +322,15 @@ namespace cadabra { /// in them; use clear() to clean up. Note that pointers can sit in in more than one /// entry in this map (when they are pointing to list_property objects, which are /// shared between patterns). - property_map_t props; // pattern -> property - pattern_map_t pats; // property -> pattern; for list properties, patterns are stored here in order + // property_map_t props; // pattern -> property + // pattern_map_t pats; // property -> pattern; for list properties, patterns are stored here in order + + typedef std::multimap propmap_t; + typedef std::map typemap_t; + typedef std::map namemap_t; + + typemap_t pats_dict; + namemap_t props_dict; /// Normal search: given a pattern, get its property if any. template const T* get(Ex::iterator, bool ignore_parent_rel=false) const; @@ -307,6 +355,8 @@ namespace cadabra { const std::string& label, bool doserial=true, bool ignore_parent_rel=false) const; + + // Get the outermost node which has the given property attached, i.e. go down through // all (if any) nodes which have just inherited the property. template Ex::iterator head(Ex::iterator, bool ignore_parent_rel=false) const; @@ -316,16 +366,29 @@ namespace cadabra { // map from this particular point. Note: this searches on property type, not exact property. // template // property_map_t::iterator get_pattern(property_map_t::iterator=props.begin()); + // template + // property_map_t::iterator get_pattern(); + // template + // property_map_t::iterator get_pattern(property_map_t::iterator); // Equivalent search: given a node, get a pattern of equivalents. // property_map_t::iterator get_equivalent(Ex::iterator, // property_map_t::iterator=props.begin()); + + /// Erases property completely. + void erase(const property*); + /// Erases pattern from a given property, leaving other patterns alone. + void erase(const property*, pattern*); + + /// Helper function to lookup all patterns associated with a property. + /// If the property is invalid, it returns a null pointer in the first slot. + std::pair > lookup_property(const property*) const; private: // Insert a property. Do not use this directly, use the public // interface `master_insert` instead. void insert_prop(const Ex&, const property *); - void insert_list_prop(const std::vector&, const list_property *); + const list_property* insert_list_prop(const std::vector&, const list_property *); bool check_label(const property *, const std::string&) const; bool check_label(const labelled_property *, const std::string&) const; // Search through pointers @@ -334,6 +397,167 @@ namespace cadabra { int serial_number(const property *, const pattern *) const; Ex_comparator *create_comparator() const; void destroy_comparator(Ex_comparator *) const; + + + + + public: + // typedef std::multimap propmap_t; + // typedef std::map typemap_t; + + template + class iterator_base { + public: + using map_t = std::conditional_t; + using outer_iterator = std::conditional_t; + using inner_iterator = std::conditional_t; + using iterator_category = std::forward_iterator_tag; + using difference_type = std::ptrdiff_t; + using value_type = std::conditional_t; + using pointer = value_type*; + using reference = value_type&; + using self_type = iterator_base; + + iterator_base(bool is_end = true) { + typemap_ = nullptr; + } + + iterator_base(map_t* m, bool is_end_ = false): typemap_(m) { + if (is_end_) { + typemap_ = nullptr; + } else { + outer_it_ = typemap_->begin(); + skip_ahead(); + } + } + + iterator_base(map_t* m, outer_iterator outer, inner_iterator inner) + : typemap_(m), outer_it_(outer), inner_it_(inner) { + if (inner_it_ == outer_it_->second.end()) { + ++outer_it_; + skip_ahead(); + } + } + + self_type& operator++() { + ++inner_it_; + if (inner_it_ == outer_it_->second.end()) { + ++outer_it_; + skip_ahead(); + } + return *this; + } + + self_type& next_proptype() { + ++outer_it_; + skip_ahead(); + return *this; + } + + self_type& next_prop() { + const property* this_prop = inner_it_->first; + while (typemap_ && inner_it_->first == this_prop) { + operator++(); + } + return *this; + } + + std::type_index proptype() const { + return outer_it_->first; + } + + const propmap_t& prop_pat_pairs() const { + return outer_it_->second; + } + + reference operator*() const { return *inner_it_; } + pointer operator->() const { return &(*inner_it_); } + + bool operator==(const self_type& other) const { + return (!typemap_ && !other.typemap_) || (typemap_ == other.typemap_ && outer_it_==other.outer_it_ && inner_it_ == other.inner_it_); + } + + bool operator!=(const self_type& other) const { + return !(*this == other); + } + + protected: + map_t* typemap_; + outer_iterator outer_it_; + inner_iterator inner_it_; + + void skip_ahead() { + while (outer_it_ != typemap_->end()) { + if (outer_it_->second.empty()) { + ++outer_it_; + continue; + } + inner_it_ = outer_it_->second.begin(); + return; + /* + if (inner_it_ != outer_it_->second.end()) return; + ++outer_it_; + */ + } + // inner_it_ = inner_iterator(); + // is_end_ = true; + typemap_ = nullptr; + } + + + + }; + + typedef iterator_base iterator; + typedef iterator_base const_iterator; + + // Create iterator over all property/pattern pairs + iterator begin() {return iterator(&pats_dict);} + const_iterator begin() const {return const_iterator(&pats_dict);} + + // Create iterator over all property/pattern pairs with a pattern matching name + iterator begin(nset_t::iterator name) { + auto it = props_dict.find(name); + if (it == props_dict.end()) return iterator{true}; + else return iterator(&(it->second)); + } + const_iterator begin(nset_t::iterator name) const { + auto it = props_dict.find(name); + if (it == props_dict.end()) return const_iterator{true}; + else return const_iterator(&(it->second)); + } + + // Create iterator over all property/pattern pairs of a specific type + iterator begin(std::type_index type) { + auto it = pats_dict.find(type); + if (it == pats_dict.end()) return iterator{true}; + else return iterator(&(it->second)); + } + const_iterator begin(std::type_index type) const { + auto it = pats_dict.find(type); + if (it == pats_dict.end()) return const_iterator{true}; + else return const_iterator(&(it->second)); + } + + // Return pair corresponding to begin and end of a property range + std::pair equal_range(const property *prop) { + auto it = pats_dict.find(typeid(*prop)); + if (it == pats_dict.end()) return {iterator{true}, iterator{true}}; + auto range = it->second.equal_range(prop); + return {iterator{&pats_dict, it, range.first}, iterator{&pats_dict, it, range.second}}; + } + std::pair equal_range(const property *prop) const { + auto it = pats_dict.find(typeid(*prop)); + if (it == pats_dict.end()) return {const_iterator{true}, const_iterator{true}}; + auto range = it->second.equal_range(prop); + return {const_iterator{&pats_dict, it, range.first}, const_iterator{&pats_dict, it, range.second}}; + } + + // All end iterators are the same. + iterator end() {return iterator(/*is_end=*/ true);} + const_iterator end() const {return const_iterator(/*is_end=*/ true);} + + }; template @@ -363,18 +587,15 @@ namespace cadabra { template std::pair Properties::get_with_pattern_ext(Ex::iterator it, Ex_comparator& comp, - int& serialnum, const std::string& label, - bool doserial, bool ignore_parent_rel) const + int& serialnum, const std::string& label, + bool doserial, bool ignore_parent_rel) const { - std::pair ret; - ret.first=0; - ret.second=0; - bool inherits=false; + std::pair ret = {nullptr, nullptr}; + bool inherits = false; //std::cerr << *it->name_only() << std::endl; // std::cerr << props.size() << std::endl; - std::pair pit=props.equal_range(it->name_only()); - + // First look for properties of the node itself. Go through the loop twice: // once looking for patterns which do not have wildcards, and then looking // for wildcard patterns. @@ -386,44 +607,72 @@ namespace cadabra { bool ignore_properties=false; if(std::is_same::value) ignore_properties=true; + + // Outer loop runs first with wildcards = false, second (if needed) with wildcards = true for(;;) { - property_map_t::const_iterator walk=pit.first; - while(walk!=pit.second) { - if(wildcards==(*walk).second.first->children_wildcard()) { - // First check property type; a dynamic cast is much faster than a pattern match. - ret.first=dynamic_cast((*walk).second.second); - if(ret.first) { - if((*walk).second.first->match_ext(*this, it, comp, ignore_parent_rel, ignore_properties)) { - ret.second=(*walk).second.first; - if(!check_label(ret.first, label)) - ret.first=0; - else { - if(doserial) - serialnum=serial_number( (*walk).second.second, (*walk).second.first ); - break; - } - } + // first pass: wildcards == false + // second pass (optional): wildcards == true + auto walk = begin(it->name_only()); + auto end_it = end(); + std::type_index last_type = typeid(void); + + // walk takes us through all properties that have patterns matching name + while (walk != end_it) { + // Upon (re)starting the loop, check whether the property type has changed. + // If it has, check castability and skip if not castable. + if (last_type != walk.proptype()) { + // This is the first time we are encountering this property type in the loop. + last_type = walk.proptype(); + ret.first = dynamic_cast(walk->first); + if (!ret.first) { + // If we can't dynamically cast this property type, then move on... + walk.next_proptype(); + continue; + } + } + // We are only dealing with castable properties now. + if(wildcards==walk->second->children_wildcard()) { + if(walk->second->match_ext(*this, it, comp, ignore_parent_rel, ignore_properties)) { + ret.first = dynamic_cast(walk->first); + assert(ret.first != nullptr); + ret.second = walk->second; + if(!check_label(ret.first, label)) { + ret.first=0; + } + else { + if(doserial) + serialnum=serial_number( walk->first, walk->second); + break; } - ret.first=0; - if(dynamic_cast((*walk).second.second)) - inherits=true; - else if(dynamic_cast *>((*walk).second.second)) - inherits=true; } - ++walk; + ret.first=0; } + ++walk; + } if(!wildcards && !ret.first) { // std::cerr << "not yet found, switching to wildcards" << std::endl; wildcards=true; + // walk = begin(it->name_only()); // restarting the for loop } else break; - } + } // Do not walk down the tree if the property cannot be passed up the tree. // FIXME: see issue/259. - if(std::is_same::value) + if(std::is_same::value) { inherits=false; + } else if (!ret.first) { + auto walk = begin(it->name_only()); + auto end_it = end(); + while (walk != end_it) { + if (dynamic_cast*>(walk->first) || dynamic_cast(walk->first)) { + inherits = true; + break; + } + walk.next_proptype(); + } + } // If no property was found, figure out whether a property is inherited from a child node. if(!ret.first && inherits) { @@ -442,7 +691,6 @@ namespace cadabra { // std::cout << ret << std::endl; return ret; } - template const T* Properties::get(Ex::iterator it, const std::string& label) const { @@ -467,45 +715,102 @@ namespace cadabra { template const T* Properties::get(Ex::iterator it1, Ex::iterator it2, int& serialnum1, int& serialnum2, bool ignore_parent_rel) const { - const T* ret1=0; - const T* ret2=0; - bool found=false; - bool inherits1=false, inherits2=false; - std::pair pit1=props.equal_range(it1->name_only()); - std::pair pit2=props.equal_range(it2->name_only()); - - property_map_t::const_iterator walk1=pit1.first; - while(walk1!=pit1.second) { - if((*walk1).second.first->match(*this, it1, ignore_parent_rel)) { // match for object 1 found - ret1=dynamic_cast((*walk1).second.second); - if(ret1) { // property of the right type found for object 1 - property_map_t::const_iterator walk2=pit2.first; - while(walk2!=pit2.second) { - if((*walk2).second.first->match(*this, it2, ignore_parent_rel)) { // match for object 2 found - ret2=dynamic_cast((*walk2).second.second); - if(ret2) { // property of the right type found for object 2 - if(ret1==ret2 && walk1!=walk2) { // accept if properties are the same and patterns are not - serialnum1=serial_number( (*walk1).second.second, (*walk1).second.first ); - serialnum2=serial_number( (*walk2).second.second, (*walk2).second.first ); - found=true; - goto done; - } - } + + // Find all common properties of it1 and it2 + auto walk1 = begin(it1->name_only()); + auto walk2 = begin(it2->name_only()); + auto end_it = end(); + + if (walk1 != end_it && walk2 != end_it) { + if (walk1.proptype() < walk2.proptype()) { + walk1.next_proptype(); + } else if (walk2.proptype() < walk1.proptype()) { + walk2.next_proptype(); + } else if (!dynamic_cast(walk1->first)) { + walk1.next_proptype(); + walk2.next_proptype(); + } else { + // walk1 and walk2 are of the same castable property type. + // Unlike what we do with get() above for a single iterator. + // we dive in and process here ALL the properties of the same type. + + // Take the property maps + auto& propmap1 = walk1.prop_pat_pairs(); + auto& propmap2 = walk2.prop_pat_pairs(); + bool match1_found = false; + bool match2_found = false; + // By construction, the pat_props are always sorted by property pointer. + // This allows the iteration below to be as fast as possible. + for (auto ppit1 = propmap1.begin(), ppit2 = propmap2.begin(); + ppit1 != propmap1.end() && ppit2 != propmap2.end(); ) { + // Advance both iterators until their properties match + if (ppit1->first < ppit2->first) { + ++ppit1; + match1_found = false; + } else if (ppit2->first < ppit1->first) { + ++ppit2; + match2_found = false; + } else { + // The properties match, so see if there is a pattern match + if (!match1_found) { + if (ppit1->second->match(*this, it1, ignore_parent_rel)) { + match1_found = true; + } else { + ++ppit1; + // assert(match1_found == false); + continue; } - if(dynamic_cast((*walk2).second.second)) - inherits2=true; - ++walk2; } + if (!match2_found) { + if (ppit2->second->match(*this, it2, ignore_parent_rel)) { + match2_found = true; + } else { + ++ppit2; + // assert(match2_found == false); + continue; + } + } + // assert(match1_found && match2_found) + + // accept if properties are the same and patterns are not + if ((ppit1->first == ppit2->first) && (ppit1->second != ppit2->second)) { + serialnum1=serial_number( ppit1->first, ppit1->second ); + serialnum2=serial_number( ppit2->first, ppit2->second ); + // Return the recasted property + return dynamic_cast( ppit1->first ); + } + // Otherwise, continue. + ++ppit1; + ++ppit2; } - if(dynamic_cast((*walk1).second.second)) - inherits1=true; } - ++walk1; + walk1.next_proptype(); + walk2.next_proptype(); } + } + + // Are any of these properties heritable? + // FIXME: Below just uses PropertyInherit and not Inherit? + walk1 = begin(it1->name_only()); + while (walk1 != end_it) { + if (dynamic_cast(walk1->first)) { + inherits1 = true; + break; + } + walk1.next_proptype(); + } + walk2 = begin(it2->name_only()); + while (walk2 != end_it) { + if (dynamic_cast(walk2->first)) { + inherits2 = true; + break; + } + walk2.next_proptype(); + } // If no property was found, figure out whether a property is inherited from a child node. - if(!found && (inherits1 || inherits2)) { + if(inherits1 || inherits2) { Ex::sibling_iterator sib1, sib2; if(inherits1) sib1=it1.begin(); else sib1=it1; @@ -517,10 +822,8 @@ namespace cadabra { do { // 2 const T* tmp=get((Ex::iterator)(sib1), (Ex::iterator)(sib2), serialnum1, serialnum2, ignore_parent_rel); if(tmp) { - ret1=tmp; - found=true; - goto done; - } + return tmp; + } if(!inherits2 || ++sib2==it2.end()) keepgoing2=false; } @@ -530,10 +833,7 @@ namespace cadabra { } while(keepgoing1); } - -done: - if(!found) ret1=0; - return ret1; + return nullptr; } template @@ -552,4 +852,9 @@ done: return dn; } + + + + + } diff --git a/core/algorithms/rename_dummies.cc b/core/algorithms/rename_dummies.cc index 16d79fd9f6..fc6705efa0 100644 --- a/core/algorithms/rename_dummies.cc +++ b/core/algorithms/rename_dummies.cc @@ -84,8 +84,9 @@ Algorithm::result_t rename_dummies::apply(iterator& st) // with this name. const Indices *ind2=0; if(dset2!="") { - auto f2=kernel.properties.pats.begin(); - while(f2!=kernel.properties.pats.end()) { + // FIXME: We assume only Indices type is castable to Indices. + auto f2 = kernel.properties.begin(typeid(Indices)); + while(f2!=kernel.properties.end()) { ind2 = dynamic_cast(f2->first); if(ind2) { if(ind2->set_name==dset2) diff --git a/core/cadabra2_defaults.py.in b/core/cadabra2_defaults.py.in index eccb76d03f..ef654a2041 100644 --- a/core/cadabra2_defaults.py.in +++ b/core/cadabra2_defaults.py.in @@ -550,6 +550,7 @@ class Console(object): """ def log(self, *objs): """Sends a string representation of objs to the console""" + cell_id = 0 if server.architecture() == "terminal": print(*objs) elif server.architecture() == "client-server": @@ -557,10 +558,12 @@ class Console(object): def clear(self): """Clears the output of the console window""" + cell_id = 0 if server.architecture() == "client-server": server.send("", "csl_clear", 0, cell_id, False) def warn(self, msg): + cell_id = 0 if server.architecture() == "terminal": print("Warning: " + msg) elif server.architecture() == "client-server": diff --git a/core/properties/CommutingBehaviour.cc b/core/properties/CommutingBehaviour.cc index 28acdb30af..bbb7d76ce4 100644 --- a/core/properties/CommutingBehaviour.cc +++ b/core/properties/CommutingBehaviour.cc @@ -3,7 +3,7 @@ using namespace cadabra; -property::match_t CommutingBehaviour::equals(const property *) const +list_property::match_t CommutingBehaviour::equals(const property *) const { return no_match; // you can have as many of these as you like } diff --git a/core/properties/Indices.cc b/core/properties/Indices.cc index 5b15b64497..411387bc08 100644 --- a/core/properties/Indices.cc +++ b/core/properties/Indices.cc @@ -20,7 +20,7 @@ std::string Indices::name() const return "Indices"; } -property::match_t Indices::equals(const property *other) const +list_property::match_t Indices::equals(const property *other) const { const Indices *cast_other = dynamic_cast(other); if(cast_other) { @@ -32,7 +32,7 @@ property::match_t Indices::equals(const property *other) const } return no_match; } - return property::equals(other); + return list_property::equals(other); } bool Indices::parse(Kernel& kernel, std::shared_ptr ex, keyval_t& keyvals) diff --git a/core/properties/SortOrder.cc b/core/properties/SortOrder.cc index 5b12ecf584..b66b7e2e57 100644 --- a/core/properties/SortOrder.cc +++ b/core/properties/SortOrder.cc @@ -3,7 +3,7 @@ using namespace cadabra; -property::match_t SortOrder::equals(const property *) const +list_property::match_t SortOrder::equals(const property *) const { return no_match; // you can have as many of these as you like } diff --git a/core/pythoncdb/py_ex.cc b/core/pythoncdb/py_ex.cc index 7376b302f4..7da0196b17 100644 --- a/core/pythoncdb/py_ex.cc +++ b/core/pythoncdb/py_ex.cc @@ -715,6 +715,7 @@ namespace cadabra { .def("state", &Ex::state) .def("reset", &Ex::reset_state) .def("copy", [](const Ex& ex) { return std::make_shared(ex); }) + .def("size", [](const Ex& ex) { return ex.size();}) .def("changed", &Ex::changed_state) .def("cleanup", &Ex_cleanup) .def("__array__", [](Ex& ex) { diff --git a/core/pythoncdb/py_properties.cc b/core/pythoncdb/py_properties.cc index e725942284..8379d1f1b6 100644 --- a/core/pythoncdb/py_properties.cc +++ b/core/pythoncdb/py_properties.cc @@ -84,6 +84,8 @@ namespace cadabra { std::string BoundPropertyBase::str_() const { + validate(); + if (!prop) return "invalid"; std::ostringstream str; str << "Property "; // std::cerr << "going to print" << std::endl; @@ -94,11 +96,12 @@ namespace cadabra { std::string BoundPropertyBase::latex_() const { + validate(); + if (!prop) return "invalid"; std::ostringstream str; // HERE: this text should go away, property should just print itself in a python form, // the decorating text should be printed in a separate place. - str << "\\text{Property "; prop->latex(str); std::string bare = Ex_as_latex(for_obj); @@ -124,6 +127,7 @@ namespace cadabra { std::string BoundPropertyBase::repr_() const { + validate(); // FIXME: this needs work, it does not output things which can be fed back into python. return "Property::repr: " + prop->name(); } @@ -148,6 +152,24 @@ namespace cadabra { return for_obj->begin(); } + void BoundPropertyBase::validate() const + { + auto pair = get_kernel_from_scope()->properties.lookup_property(prop); + if (pair.first == nullptr) { + prop = nullptr; + } + // Update pattern + if (pair.second.size() == 1) { + for_obj = std::make_shared(pair.second[0]->obj); + } else { + for_obj = std::make_shared("\\comma"); + for (const auto& pat : pair.second) { + for_obj->append_child(for_obj->begin(), pat->obj.begin()); + } + } + } + + template BoundProperty::BoundProperty() @@ -168,14 +190,12 @@ namespace cadabra { : BoundPropertyBase(nullptr, ex) { auto new_prop = new cpp_type(); - get_kernel_from_scope()->inject_property(new_prop, ex, param); - BoundPropertyBase::prop = new_prop; + this->prop = get_kernel_from_scope()->inject_property(new_prop, ex, param); } template std::shared_ptr> BoundProperty::get_from_kernel(Ex::iterator it, const std::string& label, bool ignore_parent_rel) - { int tmp; auto res = get_kernel_from_scope()->properties.get_with_pattern( @@ -192,10 +212,19 @@ namespace cadabra { } + template + void BoundProperty::remove_from_kernel() + { + get_kernel_from_scope()->properties.erase(this->prop); + this->prop = nullptr; + } + + template const PropT* BoundProperty::get_prop() const { - return dynamic_cast(BoundPropertyBase::prop); + this->validate(); + return dynamic_cast(this->prop); } template @@ -206,6 +235,7 @@ namespace cadabra { Properties& props = kernel->properties; const auto *thisprop = get_prop(); + if (!thisprop) return; // Maybe raise error message? thisprop->validate(*kernel, obj); props.master_insert(*obj, thisprop); } @@ -251,6 +281,9 @@ namespace cadabra { using cpp_type = typename base_type::cpp_type; using py_type = typename base_type::py_type; + // Register the property type for dynamic lookup. + py_property_registry.register_type(); + return py_type(m, std::make_shared()->name().c_str(), py::multiple_inheritance(), read_manual(m, "properties", std::make_shared()->name().c_str()).c_str()) .def(py::init(), py::arg("ex"), py::arg("param")=Ex{}) @@ -260,11 +293,75 @@ namespace cadabra { .def("__str__", &BoundPropT::str_) .def("__repr__", &BoundPropT::repr_) .def("_latex_", &BoundPropT::latex_) + .def("erase", &BoundPropT::remove_from_kernel) ; } + pybind11::list list_properties_old() + { + // This function is fundamentally limited. We would *like* to return a list of + // BoundProperties, so that you can do something with the output. But we cannot + // walk the full property list and create a BoundProperty for each of them, as + // we do not know the type (we can only dynamic_cast). + // + // So for now this is just returning a list of LaTeXStrings, obtained by asking + // each property to print itself. + + Kernel *kernel = get_kernel_from_scope(); + Properties& props = kernel->properties; + + pybind11::dict globals = get_globals(); + bool handles_latex_view = globals["server"].attr("handles")(pybind11::str("latex_view")).cast(); + + pybind11::list ret; + std::string res; + bool multi = false; + + for (auto it = props.begin(); it != props.end(); ++it) { + if (it->first->hidden()) continue; + // print the property name if we are at the end or if the next entry is for + // a different property. + decltype(it) nxt = it; + ++nxt; + if (res == "" && (nxt != props.end() && it->first == nxt->first)) { + if(handles_latex_view) res += "\\{"; + else res += "{"; + multi = true; + } + + std::ostringstream str; + if(handles_latex_view) { + DisplayTeX dt(*get_kernel_from_scope(), it->second->obj); + dt.output(str); + } + else { + DisplayTerminal dt(*get_kernel_from_scope(), it->second->obj); + dt.output(str); + } + + res += str.str(); + + if (nxt == props.end() || it->first != nxt->first) { + if (multi) { + if(handles_latex_view) res += "\\}"; + else res += "}"; + } + multi = false; + res += "::\\texttt{"; + res += (*it).first->name() + "}"; + ret.append(LaTeXString(res)); + res = ""; + } + else { + res += ", "; + } + } + return ret; + } + + pybind11::list list_properties() { // This function is fundamentally limited. We would *like* to return a list of @@ -278,20 +375,32 @@ namespace cadabra { Kernel *kernel = get_kernel_from_scope(); Properties& props = kernel->properties; - pybind11::dict globals = get_globals(); - bool handles_latex_view = globals["server"].attr("handles")(pybind11::str("latex_view")).cast(); + // pybind11::dict globals = get_globals(); + // bool handles_latex_view = globals["server"].attr("handles")(pybind11::str("latex_view")).cast(); pybind11::list ret; - std::string res; - bool multi = false; - for (auto it = props.pats.begin(); it != props.pats.end(); ++it) { + // std::string res; + // bool multi = false; + for (auto it = props.begin(); it != props.end(); it.next_prop()) { if (it->first->hidden()) continue; - + const property* prop = it->first; + + Ex_ptr ex_ptr = std::make_shared(it->second->obj); + + pybind11::object bound_property = + py_property_registry.create_bound_property(prop, ex_ptr); + + if (bound_property.is_none()) + continue; + + ret.append(bound_property); + + /* // print the property name if we are at the end or if the next entry is for // a different property. decltype(it) nxt = it; ++nxt; - if (res == "" && (nxt != props.pats.end() && it->first == nxt->first)) { + if (res == "" && (nxt != pats.end() && it->first == nxt->first)) { if(handles_latex_view) res += "\\{"; else res += "{"; multi = true; @@ -309,7 +418,7 @@ namespace cadabra { res += str.str(); - if (nxt == props.pats.end() || it->first != nxt->first) { + if (nxt == pats.end() || it->first != nxt->first) { if (multi) { if(handles_latex_view) res += "\\}"; else res += "}"; @@ -323,15 +432,48 @@ namespace cadabra { else { res += ", "; } + */ } - return ret; } + pybind11::dict properties_dict() { + Kernel* kernel = get_kernel_from_scope(); + Properties& props = kernel->properties; + + // Dictionary of properties, keyed by property type + pybind11::dict ret; + + for (auto it = props.begin(); it!=props.end(); it.next_prop()) { + if (it->first->hidden()) continue; + const property* prop = it->first; + + Ex_ptr ex_ptr = std::make_shared(it->second->obj); + + pybind11::object bound_property = + py_property_registry.create_bound_property(prop, ex_ptr); + + if (bound_property.is_none()) + continue; + + // Key: Python class name of the bound property + pybind11::str key = + pybind11::str(bound_property.get_type().attr("__name__")); + + // Ensure a list exists for this key and append + if (!ret.contains(key)) + ret[key] = pybind11::list(); + + ret[key].cast().append(bound_property); + } + return ret; + } + std::vector indices_get_all(const Indices* indices, bool include_wildcards) { auto kernel = get_kernel_from_scope(); - auto its = kernel->properties.pats.equal_range(indices); + // auto its = kernel->properties.pats.equal_range(indices); + auto its = kernel->properties.equal_range(indices); std::vector res; for (auto it = its.first; it != its.second; ++it) { @@ -353,6 +495,8 @@ namespace cadabra { { m.def("properties", &list_properties); + m.def("properties_old", &list_properties_old); + m.def("properties_dict", &properties_dict); py::class_>(m, "Property") .def_property_readonly("for_obj", &BoundPropertyBase::get_ex); @@ -393,6 +537,16 @@ namespace cadabra { return mult; } else return pybind11::cast(m.get_double()); + }) + .def("value", [](const Py_WeightBase & p) { + auto m = p.get_prop()->value(p.get_kernel(), p.get_it(), p.get_prop()->label); + if(m.is_rational()) { + // This is mpq_class, convert to the Python equivalent. + pybind11::object mpq = pybind11::module::import("gmpy2").attr("mpq"); + pybind11::object mult = mpq(m.get_rational().get_num().get_si(), m.get_rational().get_den().get_si()); + return mult; + } + else return pybind11::cast(m.get_double()); }); def_abstract_prop(m, "DifferentialFormBase") @@ -558,4 +712,6 @@ namespace cadabra { } + + BoundPropertyRegistry py_property_registry; } diff --git a/core/pythoncdb/py_properties.hh b/core/pythoncdb/py_properties.hh index 0a72b74160..453245a458 100644 --- a/core/pythoncdb/py_properties.hh +++ b/core/pythoncdb/py_properties.hh @@ -5,8 +5,10 @@ #include #include #include -#include -#include +// #include +// #include +#include "pybind11/pybind11.h" +#include "pybind11/stl.h" #include "py_ex.hh" #include "py_kernel.hh" #include "py_tableau.hh" @@ -36,13 +38,22 @@ namespace cadabra { // We keep a pointer to the C++ property, so it is possible to // query properties using the Python interface. However, this C++ // object is owned by the C++ kernel and does not get destroyed - // when the Python object goes out of scope. - const property* prop; + // when the Python object goes out of scope. Member functions + // should always call validate(), which sets prop = nullptr if invalid. + mutable const property* prop; // We also keep a shared pointer to the expression for which we // have defined this property, so that we can print sensible // information. - Ex_ptr for_obj; + mutable Ex_ptr for_obj; + + /// Validate the property and return the associated patterns. + /// If the property is invalid, prop is set to nullptr. + void validate() const; + + // FIXME: The above is a mess because we now call validate() everywhere. + // None of these are actually const, because prop, for_obj above are + // mutable. It would make more sense to just eliminate the const everywhere. }; @@ -76,8 +87,51 @@ namespace cadabra { // Return type is not the same as BoundPropertyBase, but this is ok // by the standard as cpp_type* is convertible to property* const cpp_type* get_prop() const; + + /// Delete the entire property from the kernel + void remove_from_kernel(); + + }; + + class BoundPropertyRegistry { + public: + using FactoryFunction = std::function; + + template + void register_type() { + const std::type_index type_id = typeid(typename BoundPropT::cpp_type); + if (registry_.count(type_id) > 0) { + throw std::runtime_error("Property already registered"); + } + + registry_[type_id] = [](const property* prop, Ex_ptr ex) { + auto casted_prop = dynamic_cast(prop); + if (!casted_prop) { + throw std::runtime_error("Failed to cast property"); + } + return pybind11::cast(std::make_shared(casted_prop, ex)); + }; + + } + + pybind11::object create_bound_property(const property* prop, Ex_ptr ex) const { + std::type_index type_id = typeid(*prop); + auto it = registry_.find(type_id); + if (it == registry_.end()) { + // throw std::runtime_error("No BoundProperty registered for this type"); + return pybind11::none(); + } + return it->second(prop, ex); + } + + private: + // the registry_ maps a type_index for the cpp type to a FactoryFunction, + // which takes property and pattern pointers and returns a Python BoundProperty + std::map registry_; + }; + template std::string get_name(); template typename BoundPropT::py_type def_abstract_prop(pybind11::module& m); @@ -87,4 +141,5 @@ namespace cadabra { void init_properties(pybind11::module& m); + extern BoundPropertyRegistry py_property_registry; }