Allow FieldSelection to simultaneously include and exclude fields

The basic use of `FieldSelection` is to construct the class with a mode
(`None`, `Any`, `Select`, `Exclude`), and then specify particular fields
based off of this mode. This works fine for basic uses where the same code
that constructs a `FieldSelection` sets all the fields.

But what happens, for example, if you have code that takes an existing
`FieldSelection` and wants to exclude the field named `foo`? If the
`FieldSelection` mode happens to be anything other than `Exclude`, the code
would have to go through several hoops to construct a new `FieldSelection`
object with this modified selection.

To make this case easier, `FieldSelection` now has the ability to specify
the mode independently for each field. The `AddField` method now has an
optional mode argument the specifies whether the mode for that field should
be `Select` or `Exclude`.

In the example above, the code can simply add the `foo` field with the
`Exclude` mode. Regardless of whatever state the `FieldSelection` was in
before, it will now report the `foo` field as not selected.
This commit is contained in:
Kenneth Moreland 2022-11-03 12:34:58 -06:00
parent 8738d79b4a
commit f3fa4f127a
5 changed files with 352 additions and 159 deletions

@ -0,0 +1,21 @@
# Allow FieldSelection to simultaneously include and exclude fields
The basic use of `FieldSelection` is to construct the class with a mode
(`None`, `Any`, `Select`, `Exclude`), and then specify particular fields
based off of this mode. This works fine for basic uses where the same code
that constructs a `FieldSelection` sets all the fields.
But what happens, for example, if you have code that takes an existing
`FieldSelection` and wants to exclude the field named `foo`? If the
`FieldSelection` mode happens to be anything other than `Exclude`, the code
would have to go through several hoops to construct a new `FieldSelection`
object with this modified selection.
To make this case easier, `FieldSelection` now has the ability to specify
the mode independently for each field. The `AddField` method now has an
optional mode argument the specifies whether the mode for that field should
be `Select` or `Exclude`.
In the example above, the code can simply add the `foo` field with the
`Exclude` mode. Regardless of whatever state the `FieldSelection` was in
before, it will now report the `foo` field as not selected.

@ -103,6 +103,7 @@ set(core_headers
TaskQueue.h
)
set(core_sources
FieldSelection.cxx
NewFilterField.cxx
)
set(core_sources_device

@ -0,0 +1,215 @@
//============================================================================
// Copyright (c) Kitware, Inc.
// All rights reserved.
// See LICENSE.txt for details.
//
// This software is distributed WITHOUT ANY WARRANTY; without even
// the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
// PURPOSE. See the above copyright notice for more information.
//============================================================================
#include <vtkm/filter/FieldSelection.h>
#include <map>
namespace
{
struct FieldDescription
{
std::string Name;
vtkm::cont::Field::Association Association;
FieldDescription() = default;
FieldDescription(const std::string& name, vtkm::cont::Field::Association assoc)
: Name(name)
, Association(assoc)
{
}
FieldDescription(const FieldDescription&) = default;
FieldDescription& operator=(const FieldDescription&) = default;
bool operator<(const FieldDescription& other) const
{
return (this->Association == other.Association) ? (this->Name < other.Name)
: (this->Association < other.Association);
}
};
} // anonymous namespace
namespace vtkm
{
namespace filter
{
struct FieldSelection::InternalStruct
{
Mode ModeType;
std::map<FieldDescription, Mode> Fields;
};
FieldSelection::FieldSelection(Mode mode)
: Internals(new InternalStruct)
{
this->SetMode(mode);
}
FieldSelection::FieldSelection(const std::string& field, Mode mode)
: FieldSelection(mode)
{
this->AddField(field, vtkm::cont::Field::Association::Any);
}
FieldSelection::FieldSelection(const char* field, Mode mode)
: FieldSelection(mode)
{
this->AddField(field, vtkm::cont::Field::Association::Any);
}
FieldSelection::FieldSelection(const std::string& field,
vtkm::cont::Field::Association association,
Mode mode)
: FieldSelection(mode)
{
this->AddField(field, association);
}
FieldSelection::FieldSelection(std::initializer_list<std::string> fields, Mode mode)
: FieldSelection(mode)
{
for (const std::string& afield : fields)
{
this->AddField(afield, vtkm::cont::Field::Association::Any);
}
}
FieldSelection::FieldSelection(
std::initializer_list<std::pair<std::string, vtkm::cont::Field::Association>> fields,
Mode mode)
: FieldSelection(mode)
{
for (const auto& item : fields)
{
this->AddField(item.first, item.second);
}
}
FieldSelection::FieldSelection(
std::initializer_list<vtkm::Pair<std::string, vtkm::cont::Field::Association>> fields,
Mode mode)
: FieldSelection(mode)
{
for (const auto& item : fields)
{
this->AddField(item.first, item.second);
}
}
FieldSelection::FieldSelection(const FieldSelection& src)
: Internals(new InternalStruct(*src.Internals))
{
}
FieldSelection::FieldSelection(FieldSelection&&) = default;
FieldSelection& FieldSelection::operator=(const FieldSelection& src)
{
*this->Internals = *src.Internals;
return *this;
}
FieldSelection& FieldSelection::operator=(FieldSelection&&) = default;
FieldSelection::~FieldSelection() = default;
bool FieldSelection::IsFieldSelected(const std::string& name,
vtkm::cont::Field::Association association) const
{
switch (this->GetFieldMode(name, association))
{
case Mode::Select:
return true;
case Mode::Exclude:
return false;
default:
switch (this->GetMode())
{
case Mode::None:
case Mode::Select:
// Fields are not selected unless explicitly set
return false;
case Mode::All:
case Mode::Exclude:
// Fields are selected unless explicitly excluded
return true;
}
}
VTKM_ASSERT(false && "Internal error. Unexpected mode");
return false;
}
void FieldSelection::AddField(const std::string& fieldName,
vtkm::cont::Field::Association association,
Mode mode)
{
this->Internals->Fields[FieldDescription(fieldName, association)] = mode;
}
FieldSelection::Mode FieldSelection::GetFieldMode(const std::string& fieldName,
vtkm::cont::Field::Association association) const
{
auto iter = this->Internals->Fields.find(FieldDescription(fieldName, association));
if (iter != this->Internals->Fields.end())
{
return iter->second;
}
// if not exact match, let's lookup for Association::Any.
for (const auto& aField : this->Internals->Fields)
{
if (aField.first.Name == fieldName)
{
if (aField.first.Association == vtkm::cont::Field::Association::Any ||
association == vtkm::cont::Field::Association::Any)
{
return aField.second;
}
}
}
return Mode::None;
}
void FieldSelection::ClearFields()
{
this->Internals->Fields.clear();
}
FieldSelection::Mode FieldSelection::GetMode() const
{
return this->Internals->ModeType;
}
void FieldSelection::SetMode(Mode val)
{
switch (val)
{
case Mode::None:
this->ClearFields();
this->Internals->ModeType = Mode::Select;
break;
case Mode::All:
this->ClearFields();
this->Internals->ModeType = Mode::Exclude;
break;
case Mode::Select:
case Mode::Exclude:
this->Internals->ModeType = val;
break;
}
}
}
} // namespace vtkm::filter

@ -10,12 +10,15 @@
#ifndef vtk_m_filter_FieldSelection_h
#define vtk_m_filter_FieldSelection_h
#include <initializer_list>
#include <set>
#include <vtkm/Deprecated.h>
#include <vtkm/Pair.h>
#include <vtkm/cont/Field.h>
#include <vtkm/filter/vtkm_filter_core_export.h>
#include <initializer_list>
#include <memory>
namespace vtkm
{
namespace filter
@ -26,7 +29,7 @@ namespace filter
/// `vtkm::filter::Filter::Execute` to execute the filter and map selected
/// fields. It is possible to easily construct \c FieldSelection that selects all or
/// none of the input fields.
class FieldSelection
class VTKM_FILTER_CORE_EXPORT FieldSelection
{
public:
enum struct Mode
@ -45,60 +48,33 @@ public:
static constexpr Mode MODE_EXCLUDE = Mode::Exclude;
using ModeEnum VTKM_DEPRECATED(1.8, "Use FieldSelection::Mode.") = Mode;
VTKM_CONT
FieldSelection(Mode mode = Mode::Select)
: ModeType(mode)
{
}
VTKM_CONT FieldSelection(Mode mode = Mode::Select);
/// Use this constructor to create a field selection given a single field name
/// \code{cpp}
/// FieldSelection("field_name");
/// \endcode
VTKM_CONT
FieldSelection(const std::string& field, Mode mode = Mode::Select)
: ModeType(mode)
{
this->AddField(field, vtkm::cont::Field::Association::Any);
}
VTKM_CONT FieldSelection(const std::string& field, Mode mode = Mode::Select);
/// Use this constructor to create a field selection given a single field name
/// \code{cpp}
/// FieldSelection("field_name");
/// \endcode
VTKM_CONT
FieldSelection(const char* field, Mode mode = Mode::Select)
: ModeType(mode)
{
this->AddField(field, vtkm::cont::Field::Association::Any);
}
VTKM_CONT FieldSelection(const char* field, Mode mode = Mode::Select);
/// Use this constructor to create a field selection given a single name and association.
/// \code{cpp}
/// FieldSelection("field_name", vtkm::cont::Field::Association::Points)
/// \endcode{cpp}
VTKM_CONT
FieldSelection(const std::string& field,
vtkm::cont::Field::Association association,
Mode mode = Mode::Select)
: ModeType(mode)
{
this->AddField(field, association);
}
VTKM_CONT FieldSelection(const std::string& field,
vtkm::cont::Field::Association association,
Mode mode = Mode::Select);
/// Use this constructor to create a field selection given the field names.
/// \code{cpp}
/// FieldSelection({"field_one", "field_two"});
/// \endcode
VTKM_CONT
FieldSelection(std::initializer_list<std::string> fields, Mode mode = Mode::Select)
: ModeType(mode)
{
for (const std::string& afield : fields)
{
this->AddField(afield, vtkm::cont::Field::Association::Any);
}
}
VTKM_CONT FieldSelection(std::initializer_list<std::string> fields, Mode mode = Mode::Select);
/// Use this constructor create a field selection given the field names and
/// associations e.g.
@ -108,17 +84,9 @@ public:
/// pair_type{"field_one", vtkm::cont::Field::Association::Points},
/// pair_type{"field_two", vtkm::cont::Field::Association::Cells} });
/// @endcode
VTKM_CONT
FieldSelection(
VTKM_CONT FieldSelection(
std::initializer_list<std::pair<std::string, vtkm::cont::Field::Association>> fields,
Mode mode = Mode::Select)
: ModeType(mode)
{
for (const auto& item : fields)
{
this->AddField(item.first, item.second);
}
}
Mode mode = Mode::Select);
/// Use this constructor create a field selection given the field names and
/// associations e.g.
@ -128,44 +96,16 @@ public:
/// pair_type{"field_one", vtkm::cont::Field::Association::Points},
/// pair_type{"field_two", vtkm::cont::Field::Association::Cells} });
/// @endcode
VTKM_CONT
FieldSelection(
VTKM_CONT FieldSelection(
std::initializer_list<vtkm::Pair<std::string, vtkm::cont::Field::Association>> fields,
Mode mode = Mode::Select)
: ModeType(mode)
{
for (const auto& item : fields)
{
this->AddField(item.first, item.second);
}
}
Mode mode = Mode::Select);
// Normally the default compiler construction of each of these would be fine,
// but we don't want any of them compiled for devices (like CUDA), so we have
// to explicitly mark them as VTKM_CONT.
VTKM_CONT FieldSelection(const FieldSelection& src)
: ModeType(src.ModeType)
, Fields(src.Fields)
{
}
VTKM_CONT FieldSelection(FieldSelection&& rhs)
: ModeType(rhs.ModeType)
, Fields(std::move(rhs.Fields))
{
}
VTKM_CONT FieldSelection& operator=(const FieldSelection& src)
{
this->ModeType = src.ModeType;
this->Fields = src.Fields;
return *this;
}
VTKM_CONT FieldSelection& operator=(FieldSelection&& rhs)
{
this->ModeType = rhs.ModeType;
this->Fields = std::move(rhs.Fields);
return *this;
}
VTKM_CONT ~FieldSelection() {}
VTKM_CONT FieldSelection(const FieldSelection& src);
VTKM_CONT FieldSelection(FieldSelection&& rhs);
VTKM_CONT FieldSelection& operator=(const FieldSelection& src);
VTKM_CONT FieldSelection& operator=(FieldSelection&& rhs);
VTKM_CONT ~FieldSelection();
/// Returns true if the input field should be mapped to the output
/// dataset.
@ -175,113 +115,98 @@ public:
return this->IsFieldSelected(inputField.GetName(), inputField.GetAssociation());
}
bool IsFieldSelected(
VTKM_CONT bool IsFieldSelected(
const std::string& name,
vtkm::cont::Field::Association association = vtkm::cont::Field::Association::Any) const
{
switch (this->ModeType)
{
case Mode::None:
return false;
case Mode::All:
return true;
case Mode::Select:
default:
return this->HasField(name, association);
case Mode::Exclude:
return !this->HasField(name, association);
}
}
vtkm::cont::Field::Association association = vtkm::cont::Field::Association::Any) const;
///@{
/// Add fields to map. Note, if Mode is not MODE_SELECT, then adding fields
/// will have no impact of the fields that will be mapped.
VTKM_CONT
void AddField(const vtkm::cont::Field& inputField)
/// Add fields to select or exclude. If no mode is specified, then the mode will follow
/// that of `GetMode()`.
VTKM_CONT void AddField(const vtkm::cont::Field& inputField)
{
this->AddField(inputField.GetName(), inputField.GetAssociation());
this->AddField(inputField.GetName(), inputField.GetAssociation(), this->GetMode());
}
VTKM_CONT void AddField(const vtkm::cont::Field& inputField, Mode mode)
{
this->AddField(inputField.GetName(), inputField.GetAssociation(), mode);
}
VTKM_CONT
void AddField(const std::string& fieldName,
vtkm::cont::Field::Association association = vtkm::cont::Field::Association::Any)
{
this->Fields.insert(Field(fieldName, association));
this->AddField(fieldName, association, this->GetMode());
}
VTKM_CONT void AddField(const std::string& fieldName, Mode mode)
{
this->AddField(fieldName, vtkm::cont::Field::Association::Any, mode);
}
VTKM_CONT void AddField(const std::string& fieldName,
vtkm::cont::Field::Association association,
Mode mode);
///@}
///@{
/// Returns the mode for a particular field. If the field as been added with `AddField`
/// (or another means), then this will return `Select` or `Exclude`. If the field has
/// not been added, `None` will be returned.
VTKM_CONT Mode GetFieldMode(const vtkm::cont::Field& inputField) const
{
return this->GetFieldMode(inputField.GetName(), inputField.GetAssociation());
}
VTKM_CONT Mode GetFieldMode(
const std::string& fieldName,
vtkm::cont::Field::Association association = vtkm::cont::Field::Association::Any) const;
///@}
/// Returns true if the input field has been added to this selection.
/// Note that depending on the mode of this selection, the result of HasField
/// is not necessarily the same as IsFieldSelected. (If the mode is MODE_SELECT,
/// then the result of the two will be the same.)
VTKM_CONT
bool HasField(const vtkm::cont::Field& inputField) const
VTKM_CONT bool HasField(const vtkm::cont::Field& inputField) const
{
return this->HasField(inputField.GetName(), inputField.GetAssociation());
}
bool HasField(
VTKM_CONT bool HasField(
const std::string& name,
vtkm::cont::Field::Association association = vtkm::cont::Field::Association::Any) const
{
if (this->Fields.find(Field(name, association)) != this->Fields.end())
{
return true;
}
// if not exact match, let's lookup for Association::Any.
for (const auto& aField : this->Fields)
{
if (aField.Name == name)
{
if (aField.Association == vtkm::cont::Field::Association::Any ||
association == vtkm::cont::Field::Association::Any)
{
return true;
}
}
}
return false;
return (this->GetFieldMode(name, association) != Mode::None);
}
/// Clear all fields added using `AddField`.
VTKM_CONT
void ClearFields() { this->Fields.clear(); }
VTKM_CONT void ClearFields();
VTKM_CONT
Mode GetMode() const { return this->ModeType; }
void SetMode(Mode val) { this->ModeType = val; }
/// Gets the mode of the field selection. If `Select` mode is on, then only fields that have a
/// `Select` mode are considered as selected. (All others are considered unselected.) Calling
/// `AddField` in this mode will mark it as `Select`. If `Exclude` mode is on, then all fields
/// are considered selected except those fields with an `Exclude` mode. Calling `AddField` in
/// this mode will mark it as `Exclude`.
VTKM_CONT Mode GetMode() const;
/// Sets the mode of the field selection. If `Select` mode is on, then only fields that have a
/// `Select` mode are considered as selected. (All others are considered unselected.) Calling
/// `AddField` in this mode will mark it as `Select`. If `Exclude` mode is on, then all fields
/// are considered selected except those fields with an `Exclude` mode. Calling `AddField` in
/// this mode will mark it as `Exclude`.
///
/// If the mode is set to `None`, then the field modes are cleared and the overall mode is set to
/// `Select` (meaning none of the fields are initially selected). If the mode is set to `All`,
/// then the field modes are cleared and the overall mode is set to `Exclude` (meaning all of the
/// fields are initially selected).
VTKM_CONT void SetMode(Mode val);
private:
Mode ModeType; ///< mode
struct Field
{
std::string Name;
vtkm::cont::Field::Association Association;
Field() = default;
Field(const std::string& name, vtkm::cont::Field::Association assoc)
: Name(name)
, Association(assoc)
{
}
Field(const Field&) = default;
Field& operator=(const Field&) = default;
bool operator<(const Field& other) const
{
return (this->Association == other.Association) ? (this->Name < other.Name)
: (this->Association < other.Association);
}
};
std::set<Field> Fields;
struct InternalStruct;
std::unique_ptr<InternalStruct> Internals;
};
}
}
}
} // namespace vtkm::filter
#endif // vtk_m_filter_FieldSelection_h

@ -140,6 +140,22 @@ void TestFieldSelection()
true,
"field selection failed.");
VTKM_TEST_ASSERT(selection.IsFieldSelected("bar") == true, "field selection failed.");
VTKM_TEST_ASSERT(selection.IsFieldSelected("baz") == false, "field selection failed.");
std::cout << " Select a field as excluded (should not change result)" << std::endl;
selection.AddField("baz", vtkm::filter::FieldSelection::Mode::Exclude);
VTKM_TEST_ASSERT(selection.IsFieldSelected("foo") == true, "field selection failed.");
VTKM_TEST_ASSERT(selection.IsFieldSelected("foo", vtkm::cont::Field::Association::Points) ==
true,
"field selection failed.");
VTKM_TEST_ASSERT(selection.IsFieldSelected("bar", vtkm::cont::Field::Association::Points) ==
false,
"field selection failed.");
VTKM_TEST_ASSERT(selection.IsFieldSelected("bar", vtkm::cont::Field::Association::Cells) ==
true,
"field selection failed.");
VTKM_TEST_ASSERT(selection.IsFieldSelected("bar") == true, "field selection failed.");
VTKM_TEST_ASSERT(selection.IsFieldSelected("baz") == false, "field selection failed.");
}
{
@ -161,6 +177,21 @@ void TestFieldSelection()
"field selection failed.");
VTKM_TEST_ASSERT(selection.IsFieldSelected("bar") == false, "field selection failed.");
VTKM_TEST_ASSERT(selection.IsFieldSelected("baz") == true, "field selection failed.");
std::cout << " Select a field as included (should not change result)" << std::endl;
selection.AddField("baz", vtkm::filter::FieldSelection::Mode::Select);
VTKM_TEST_ASSERT(selection.IsFieldSelected("foo") == false, "field selection failed.");
VTKM_TEST_ASSERT(selection.IsFieldSelected("foo", vtkm::cont::Field::Association::Points) ==
false,
"field selection failed.");
VTKM_TEST_ASSERT(selection.IsFieldSelected("bar", vtkm::cont::Field::Association::Points) ==
true,
"field selection failed.");
VTKM_TEST_ASSERT(selection.IsFieldSelected("bar", vtkm::cont::Field::Association::Cells) ==
false,
"field selection failed.");
VTKM_TEST_ASSERT(selection.IsFieldSelected("bar") == false, "field selection failed.");
VTKM_TEST_ASSERT(selection.IsFieldSelected("baz") == true, "field selection failed.");
}
}
}