Skip to content

Commit

Permalink
Remove vestigial subfields concept from afw.table.
Browse files Browse the repository at this point in the history
We no longer have any compound keys - they've been replaced by the
FunctorKey concept instead.  This lets us simplify a lot of code,
especially in Schema.cc.
  • Loading branch information
TallJimbo committed Feb 8, 2021
1 parent b53abcb commit c6d2377
Show file tree
Hide file tree
Showing 5 changed files with 18 additions and 213 deletions.
6 changes: 0 additions & 6 deletions include/lsst/afw/table/KeyBase.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,6 @@ class BaseRecord;
template <typename T>
class KeyBase {
public:
static bool const HAS_NAMED_SUBFIELDS = false;

KeyBase() = default;
KeyBase(KeyBase const &) = default;
KeyBase(KeyBase &&) = default;
Expand All @@ -31,8 +29,6 @@ class KeyBase {
template <typename U>
class KeyBase<Array<U> > {
public:
static bool const HAS_NAMED_SUBFIELDS = false;

KeyBase() = default;
KeyBase(KeyBase const &) = default;
KeyBase(KeyBase &&) = default;
Expand All @@ -53,8 +49,6 @@ class KeyBase<Array<U> > {
template <>
class KeyBase<Flag> {
public:
static bool const HAS_NAMED_SUBFIELDS = false;

KeyBase() = default;
KeyBase(KeyBase const &) = default;
KeyBase(KeyBase &&) = default;
Expand Down
19 changes: 5 additions & 14 deletions python/lsst/afw/table/_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,11 @@
class BaseRecord: # noqa: F811

def extract(self, *patterns, **kwargs):
"""Extract a dictionary of {<name>: <field-value>} in which the field names
match the given shell-style glob pattern(s).
"""Extract a dictionary of {<name>: <field-value>} in which the field
names match the given shell-style glob pattern(s).
Any number of glob patterns may be passed; the result will be the union of all
the result of each glob considered separately.
Any number of glob patterns may be passed; the result will be the union
of all the result of each glob considered separately.
Parameters
----------
Expand Down Expand Up @@ -70,16 +70,7 @@ def extract(self, *patterns, **kwargs):
elif kwargs:
kwargsStr = ", ".join(kwargs.keys())
raise ValueError(f"Unrecognized keyword arguments for extract: {kwargsStr}")
# must use list because we might be adding/deleting elements
for name, schemaItem in list(d.items()):
key = schemaItem.key
if split and key.HAS_NAMED_SUBFIELDS:
for subname, subkey in zip(key.subfields, key.subkeys):
d[f"{name}.{subname}"] = self.get(subkey)
del d[name]
else:
d[name] = self.get(schemaItem.key)
return d
return {name: self.get(schemaItem.key) for name, schemaItem in d.items()}

def __repr__(self):
return f"{type(self)}\n{self}"
Expand Down
1 change: 0 additions & 1 deletion python/lsst/afw/table/_schema.cc
Original file line number Diff line number Diff line change
Expand Up @@ -314,7 +314,6 @@ void declareSchemaType(WrapperCollection &wrappers) {

// KeyBase
wrappers.wrapType(PyKeyBase<T>(wrappers.module, ("KeyBase" + suffix).c_str()), [](auto &mod, auto &cls) {
cls.def_readonly_static("HAS_NAMED_SUBFIELDS", &KeyBase<T>::HAS_NAMED_SUBFIELDS);
declareKeyBaseSpecializations(cls);
});

Expand Down
8 changes: 0 additions & 8 deletions src/table/KeyBase.cc
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,6 @@ Key<FieldBase<Flag>::Element> KeyBase<Flag>::getStorage() const {
return detail::Access::extractElement(*this, 0);
}

bool const KeyBase<Flag>::HAS_NAMED_SUBFIELDS;

template <typename T>
bool const KeyBase<T>::HAS_NAMED_SUBFIELDS;

template <typename U>
bool const KeyBase<Array<U>>::HAS_NAMED_SUBFIELDS;

template <typename U>
std::vector<U> KeyBase<Array<U>>::extractVector(BaseRecord const &record) const {
Key<Array<U>> const *self = static_cast<Key<Array<U>> const *>(this);
Expand Down
197 changes: 13 additions & 184 deletions src/table/Schema.cc
Original file line number Diff line number Diff line change
Expand Up @@ -106,203 +106,32 @@ class ItemFunctors {

namespace detail {

//----- Finding a SchemaItem by field name ------------------------------------------------------------------

// This is easier to understand if you start reading from the bottom of this section, with
// SchemaImpl::find(std::string const &), then work your way up.

namespace {

// Given a SchemaItem for a regular field, look for a subfield with the given name.
// Return the index of the subfield (>= 0) on success, -1 on failure.
template <typename T>
inline int findNamedSubfield(
SchemaItem<T> const &item, std::string const &name, char delimiter,
boost::mpl::true_ * // whether a match is possible based on the type of T; computed by caller
) {
if (name.size() <= item.field.getName().size()) return -1;

if ( // compare invocation is equivalent to "name.startswith(item.field.getName())" in Python
name.compare(0, item.field.getName().size(), item.field.getName()) == 0 &&
name[item.field.getName().size()] == delimiter) {
int const position = item.field.getName().size() + 1;
int const size = name.size() - position;
int const nElements = item.field.getElementCount();
for (int i = 0; i < nElements; ++i) {
if (name.compare(position, size, Key<T>::subfields[i]) == 0) {
return i;
}
}
}
return -1;
}

// This is an overload of findNamedSubfield that always fails; it's called when we
// know from the type of the field and subfield that they're incompatible.
template <typename T>
inline int findNamedSubfield(
SchemaItem<T> const &item, std::string const &name, char delimiter,
boost::mpl::false_ * // whether a match is possible based on the type of T; computed by caller
) {
return -1;
}

// Given a SchemaItem and a subfield index, make a new SchemaItem that corresponds to that
// subfield and put it in the result smart pointer.
template <typename T, typename U>
inline void makeSubfieldItem(
SchemaItem<T> const &item, int index, char delimiter, std::unique_ptr<SchemaItem<U> > &result,
boost::mpl::true_ * // whether a match is possible based on the types of T and U; computed by caller
) {
result.reset(new SchemaItem<U>(detail::Access::extractElement(item.key, index),
Field<U>(join(item.field.getName(), Key<T>::subfields[index], delimiter),
item.field.getDoc(), item.field.getUnits())));
}

// An overload of makeSubfieldItem that always fails because we know T and U aren't compatible.
template <typename T, typename U>
inline void makeSubfieldItem(
SchemaItem<T> const &item, int index, char delimiter, std::unique_ptr<SchemaItem<U> > &result,
boost::mpl::false_ * // whether a match is possible based on the types of T and U; computed by caller
) {}

// This is a Variant visitation functor used to extract subfield items by name.
// For example, if we have a Point field "a", if we search the Schema for "a.x",
// we want to return a SchemaItem that makes it look like "a.x" is a full-fledged
// field in its own right.
template <typename U>
struct ExtractItemByName : public boost::static_visitor<> {
explicit ExtractItemByName(std::string const &name_, char delimiter_)
: delimiter(delimiter_), name(name_) {}

template <typename T>
void operator()(SchemaItem<T> const &item) const {
// We want to find out if 'item' has a subfield whose fully-qualified name matches our
// name data member. But we also know that the subfield needs to have type U, and that
// the field needs to have named subfields.
// This typedef is boost::mpl::true_ if all the above is true, and boost::mpl::false_ otherwise.
typedef typename boost::mpl::and_<std::is_same<U, typename Field<T>::Element>,
boost::mpl::bool_<KeyBase<T>::HAS_NAMED_SUBFIELDS> >::type
IsMatchPossible;
// We use that type to dispatch one of the two overloads of findNamedSubfield.
int n = findNamedSubfield(item, name, delimiter, (IsMatchPossible *)0);
// If we have a match, we call another overloaded template to make the subfield.
if (n >= 0) makeSubfieldItem(item, n, delimiter, result, (IsMatchPossible *)0);
}

char delimiter;
std::string name; // name we're looking for
mutable std::unique_ptr<SchemaItem<U> > result; // where we put the result to signal that we're done
};

} // namespace

// Here's the driver for the find-by-name algorithm.
template <typename T>
SchemaItem<T> SchemaImpl::find(std::string const &name) const {
NameMap::const_iterator i = _names.lower_bound(name);
if (i != _names.end()) {
if (i->first == name) {
// got an exact match; we're done if it has the right type, and dead if it doesn't.
try {
return boost::get<SchemaItem<T> const>(_items[i->second]);
} catch (boost::bad_get &err) {
throw LSST_EXCEPT(lsst::pex::exceptions::TypeError,
(boost::format("Field '%s' does not have the given type.") % name).str());
}
if (i != _names.end() && i->first == name) {
// got an exact match; we're done if it has the right type, and dead if it doesn't.
try {
return boost::get<SchemaItem<T>>(_items[i->second]);
} catch (boost::bad_get &err) {
throw LSST_EXCEPT(lsst::pex::exceptions::TypeError,
(boost::format("Field '%s' does not have the given type.") % name).str());
}
}
// We didn't get an exact match, but we might be searching for "a.x/a_x" and "a" might be a point field.
// Because the names are sorted, we know we overshot it, so we work backwards.
ExtractItemByName<T> extractor(name, getDelimiter());
while (i != _names.begin()) {
--i;
boost::apply_visitor(extractor, _items[i->second]); // see if the current item is a match
if (extractor.result) return *extractor.result;
}
throw LSST_EXCEPT(lsst::pex::exceptions::NotFoundError,
(boost::format("Field or subfield with name '%s' not found with type '%s'.") % name %
(boost::format("Field with name '%s' not found with type '%s'.") % name %
Field<T>::getTypeString())
.str());
}

//----- Finding a SchemaItem by key -------------------------------------------------------------------------

// This is easier to understand if you start reading from the bottom of this section, with
// SchemaImpl::find(Key<T> const &), then work your way up.

namespace {

// Given a SchemaItem for a regular field, look for a subfield with the given Key
// Return the index of the subfield (>= 0) on success, -1 on failure.
template <typename T, typename U>
inline int findKeySubfield(
SchemaItem<T> const &item, Key<U> const &key,
boost::mpl::true_ * // whether a match is possible based on the types of T and U; computed by caller
) {
int n = (key.getOffset() - item.key.getOffset()) / sizeof(U);
if (n >= 0 && n < item.key.getElementCount()) {
return n;
}
return -1;
}

// This is an overload of findKeySubfield that always fails; it's called when we
// know from the type of the field and subfield that they're incompatible.
template <typename T, typename U>
inline int findKeySubfield(
SchemaItem<T> const &item, Key<U> const &key,
boost::mpl::false_ * // whether a match is possible based on the types of T and U; computed by caller
) {
return -1;
}

// This is a Variant visitation functor used to extract subfield items by key.
template <typename U>
struct ExtractItemByKey : public boost::static_visitor<> {
explicit ExtractItemByKey(Key<U> const &key_, char delimiter_) : delimiter(delimiter_), key(key_) {}

template <typename T>
void operator()(SchemaItem<T> const &item) const {
// We want to find out if 'item' has a subfield whose matches our key data member.
// But we also know that the subfield needs to have type U.
// This typedef is boost::mpl::true_ if the above is true, and boost::mpl::false_ otherwise.
typedef typename boost::mpl::and_<std::is_same<U, typename Field<T>::Element>,
boost::mpl::bool_<KeyBase<T>::HAS_NAMED_SUBFIELDS> >::type
IsMatchPossible;
// We use that type to dispatch one of the two overloads of findKeySubfield.
int n = findKeySubfield(item, key, (IsMatchPossible *)0);
// If we have a match, we call another overloaded template to make the subfield.
// (this is the same makeSubfieldItem used in ExtractItemByName, so it's defined up there)
if (n >= 0) makeSubfieldItem(item, n, delimiter, result, (IsMatchPossible *)0);
}

char delimiter;
Key<U> key;
mutable std::unique_ptr<SchemaItem<U> > result;
};

} // namespace

// Here's the driver for the find-by-key algorithm. It's pretty similar to the find-by-name algorithm.
template <typename T>
SchemaItem<T> SchemaImpl::find(Key<T> const &key) const {
OffsetMap::const_iterator i = _offsets.lower_bound(key.getOffset());
if (i != _offsets.end()) {
if (i->first == key.getOffset()) {
try {
return boost::get<SchemaItem<T> const>(_items[i->second]);
} catch (boost::bad_get &err) {
// just swallow the exception; this might be a subfield key that points to the beginning.
}
}
// We didn't get an exact match, but we might be searching for a subfield.
// Because the offsets are sorted, we know we overshot it, so we work backwards.
ExtractItemByKey<T> extractor(key, getDelimiter());
while (i != _offsets.begin()) {
--i;
boost::apply_visitor(extractor, _items[i->second]);
if (extractor.result) return *extractor.result;
if (i != _offsets.end() && i->first == key.getOffset()) {
try {
return boost::get<SchemaItem<T>>(_items[i->second]);
} catch (boost::bad_get &err) {
// just swallow the exception; this might be a subfield key that points to the beginning.
}
}
throw LSST_EXCEPT(lsst::pex::exceptions::NotFoundError,
Expand Down

0 comments on commit c6d2377

Please sign in to comment.