`mesh_definition_u` interface
cmsquared opened this issue · comments
This is a follow-on to our discussions @charest & @tuxfan ;
tl;dr - I think CRTP might be a nice way to extend the interface, but there are other options.
The problem
mesh_definition_u
is an abstract base class that defines the API for a mesh definition as seen by FleCSI. Specializations define an implementation of this API (e.g. the exodus definition). The Burton specialization's specialization_tlt_init
method calls other methods in FleCSI-SP, which require methods of a mesh definition that are in addition to the API defined by FleCSI. In particular, the method mesh_def.entities(int, int)
called here is not defined in mesh_definition_u
but rather in flecsi_sp::io::exodus_definition__
.
This is currently not a problem because the Burton specialization currently explicitly instantiates an Exodus definition (from flecsi_sp::burton::partition_mesh
):
using exodus_definition_t = flecsi_sp::io::exodus_definition__<num_dims, real_t>;
...
// load the mesh
auto filename_string = filename.str();
exodus_definition_t mesh_def( filename_string );
This mesh_def
then gets passed around and is used as an exodus_definition__
or a mesh_definition_u
type where needed.
The problem arises now that we want to relax the requirement that the mesh file be of an Exodus format. In particular, I have implemented (but not committed) an X3D format reader that provides a mesh definition similar to that of Exodus:
template<typename T>
class x3d_definition__<2, T> : public flecsi::topology::mesh_definition_u<2> {
...
};
The corresponding code changes to partition_mesh
look like:
// mesh definitions
using mesh_definition_t = flecsi::topology::mesh_definition_u<num_dims>;
using exodus_definition_t = flecsi_sp::io::exodus_definition__<num_dims, real_t>;
using x3d_definition_t = flecsi_sp::io::x3d_definition__<num_dims, real_t>;
// load the mesh
auto filename_string = filename.str();
std::unique_ptr<mesh_definition_t> mesh_def;
if (filename_string.find(".exo") != std::string::npos)
mesh_def = std::make_unique<exodus_definition_t>(filename_string);
else if (filename_string.find(".x3d") != std::string::npos)
mesh_def = std::make_unique<x3d_definition_t>(filename_string);
else
clog_fatal("Burton specialization doesn't know how to read " <<
filename_string);
The problem is that now mesh_def
is a pointer to the base class type, it doesn't know about the extension to the API (the previously mentioned mesh_def.entities(int, int)
).
Proposed Solutions
What we discussed (doesn't work)
We talked about just adding the entities(int, int)
method to the ABC mesh_definition_u
as a virtual method with some no-op default implementation. This doesn't work directly because the actual return type of this method depends on the connectivity type. For example, although it is auto
deduced in exodus_definition__
, the return type is of type exodus_definition__::connectivity_t
, which eventually resolves to std::vector<std::vector<std::size_t>>
.
We could enforce this type for the return within mesh_definition_u
, but that seems to disregard the notion that mesh_definition_u
should not need to know details of a specialization. If everyone is fine with this, then this is the cleanest solution. Note that because this is a virtual function, we can't just template the return type.
CRTP
The other idea I have been toying with is using CRTP to handle the polymorphism required between mesh_definition_u
and any specializations on top of this. Formally, this would require redefining the API for the derived classes, which would be non-backwards compatible. To be concrete, we'd have something like this (I'm open to better naming for the methods):
template<size_t DIMENSION, typename Specialization>
class mesh_definition_u {
...
const auto & entities(size_t from_dim, size_t to_dim) const {
return static_cast<Specialization const &>(*this).entities_imp(from_dim, to_dim);
}
};
and then the derived classes would be something like
template<typename T>
class exodus_definition__<1, T> : public flecsi::topology::mesh_definition_u<1, template exodus_definition__<1,T>> {
...
const auto & entities_imp(size_t from_dim, size_t to_dim) const {
return entities_.at(from_dim).at(to_dim);
}
...
};
If the return type couldn't be auto
resolved like this, we could use traits to define the return type.
I think this is the most flexible and allows for expanding the API without needing to know details of the specialization.
Some sort of wrapper
We might be able to put another class in the way between mesh_definition_u
and the "final" specialized mesh definition (like exodus_definition__
) that would declare the functions needed by the specialization that are not needed by the FleCSI API. Something like
// in FleCSI - this stays as is
class mesh_definition_u ... {...};
// in FleCSI-SP
class intermediate... : mesh_definition_u ... {
virtual std::vector<std::vector<std::size_t>> entities(int, int) = 0;
};
class exodus_definition__... : intermediate... {
virtual std::vector<std::vector<std::size_t>> entities(int, int) override {...}
};
The unique_ptr
above then points to an intermediate
type, but we static_cast
to a mesh_definition_u
when needed?
Other options?
I think entities(int,int) should just be moved into the abstract class interface. Because the other entities() member functions force a return type anyway. And the code calling the entities() member functions requires a specific return type as well. This is the easiest solution to me.
Sorry I hadn't updated this Issue.
I basically came to the same conclusion as @charest I should commit this soon, but had gotten side tracked.
I'll keep this issue open until I get that committed.
@cmsquared how close are you to committing this?
@ipdemes Apologies, I was strongly side-tracked on another project. I will get to this in the next couple of days?
Finally getting around to working on this today.
@cmsquared :Thank you for working on this! Could you, please, close an issue now?