-
Notifications
You must be signed in to change notification settings - Fork 94
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Scalar parameter handling #820
Conversation
Most solvers and preconditioners use |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM, nice job so far! You could probably also add these changes to some examples, where we had to create scalars for 0, 1 and -1 before. I guess scalar_to_dense is your way of decoupling the dense dependency from lin_op? And as a final comment, we usually try to keep two empty lines between functions and other declarations unless we are inside a class declaration or the two adjacent declarations are directly related, i.e. overloads, template specializations or similar. For more details, see https://github.com/ginkgo-project/ginkgo/wiki/Contributing-guidelines
Codecov Report
@@ Coverage Diff @@
## develop #820 +/- ##
===========================================
+ Coverage 93.71% 94.24% +0.53%
===========================================
Files 408 410 +2
Lines 32758 32811 +53
===========================================
+ Hits 30698 30924 +226
+ Misses 2060 1887 -173
Continue to review full report at Codecov.
|
I'm working through the examples, as @upsj suggested, and I'm realizing that there are still some cases where this approach is not helpful. For example, in |
const ConcreteLinOp *apply(const ValueType alpha, const LinOp *b, | ||
const ValueType beta, LinOp *x) const | ||
{ | ||
auto dense_alpha = scalar_to_dense(alpha, this->get_executor()); | ||
auto dense_beta = scalar_to_dense(beta, this->get_executor()); | ||
return this->apply(dense_alpha.get(), b, dense_beta.get(), x); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this part can be templated and put it in the linop class directly?
and the scalar_to_dense is free function, so there is no cycle dep
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've tried that, and it is possible. However, one problem I haven't figured out is how to handle EnableLinOp
. If this is part of linop, it should be implemented again in enablelinop (or am I wrong with that?). This is more difficult, since the template needs to be instantiated explicitly somehow, and I don't know how to do this for a member function of an already templated class.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you does not need to instantiate the template member function
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
After some discussion with Tobias, I've decided to keep this in a separate mixin. Perhaps when linop gets some information about the used value type something like this could be implemented.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there any reason for that?
or any downside from templated member function?
If user does not use the same type as LinOp, ginkgo will handle it via mix_and_match, I think?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To make my example more concrete:
template <typename ValueType>
void apply(const ValueType alpha, const LinOp *b,
const ValueType beta, LinOp *x) const
{
auto dense_alpha = scalar_to_dense(alpha, this->get_executor());
auto dense_beta = scalar_to_dense(beta, this->get_executor());
return this->apply(dense_alpha.get(), b, dense_beta.get(), x);
}
...
op->apply(1, x, 0, y);
causes a linker error because scalar_to_dense<int>
is not instantiated.
On the other hand, in a non-templated ValueType overload
op->apply(1, x, 0, y);
casts 1 and 0 to ValueType and at worst gives a warning about the mismatching types.
I'm trying to avoid the use of unrestricted template member functions, since users don't get a compilation error at compile time, but only a much less useful error at link time complaining about a missing scalar_to_dense<int>
symbol. I believe having the concrete type in here makes for much better type-safety in the common use case, which is using concrete types instead of generic LinOp types.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the template one should be good for using.
auto dense_alpha = scalar_to_dense(int, this->get_executor);
// gives Dense<float> or Dense<double> with some addition on scalar_to_dense
it will not complain the scalar_to_dense, when we add the conversion.
op->apply(1, x, 0, y);
will gives implicitly conversion to ValueType, I think.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the apply will accept different input when the object is concrete type or linop
what's this target for? for all user, the developer using ginkgo or ginkgo's developer.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it will not complain the scalar_to_dense, when we add the conversion.
op->apply(1, x, 0, y); will gives implicitly conversion to ValueType, I think.
Yes, that is intended. Template type deduction requires the exact type to match, so scalar_to_dense(...) would try to use the non-existent scalar_to_dense<int>
instantiation and cause a linker error. On the other hand, with an explicit non-template overload, even implicit conversions can be applied, so for a complex LinOp, you could have something like
apply(1.0, x, std::complex<double>{0.0, 1.0}, y)
which works in the non-templated setting but doesn't compile in the templated setting.
what's this target for? for all user, the developer using ginkgo or ginkgo's developer.
I would imagine that this is most useful for users and maybe example development. For generic library code and things like solvers, where the repeated allocations are not negligible, I would use explicit LinOps anyways.
the apply will accept different input when the object is concrete type or linop
We have similar differences e.g. in the apply
function, where LinOp::apply
returns LinOp*
, but EnableLinOp::apply
returns a ConcreteLinOp*
. More generally, only concrete types expose all functionality that the type has explicitly, which I would be totally fine with.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
maybe I put thing together such that it is misleading.
// for integer
Dense<float> scalar_to_dense<int>(...)
// for floating
Dense<float> scalar_to_dense<float>(...)
If we have this, the conversion should be okay.
current op->apply(1, x, 0, y)
gives implicitly conversion to ValueType as templated one.
current pr conversion is on host but templated one may be on device.
op->apply(1.0, x, std::complex<double>{0.0, 1.0}, y)
can be handled by two templated parameter for alpha and beta.
It can used when user only create the object and then use it.
If they do some combination, the type could be LinOp*
In my feeling, when I know concrete type, it is easy to use scalar_to_dense.
when I have concrete type, it is easy to know the valuetype and its executor, so I can easily to use these parameter to generate alpha/beta.
but when I have LinOp, I only have executor, so I need to use dynamic_cast
through all possible types (and maybe more template params needs to know) or pick one valuetype to generate via scalar_to_dense
thanks to mixed_and_match
.
If we does not want scalar_to_dense<int>
return Dense<float>
, maybe the templated one needs to request the input always be floating point. Also, it will lead op->apply(scalar_to_dense(1, ref), ...)
fail and op->apply(1, ...)
work in current pr.
If we allow scalar_to_dense<int>
return Dense<float>
, it will be good for templated one.
It's good for example, but we start to have some LinOp like Multigrid without ValueType, so it will not always usable.
(Out of topic, I am thinking whether CG does really need ValueType because it just depends on the input matrix not the operation).
I think it is like the mixed_and_match place. It is a good tool to investigate ginkgo or develop something in ginkgo.
current pr:
pro: the conversion is host, do not contain any conversion on device.
con: needs to be concrete type or know the valuetype
templated:
pro: work for all linop
con: does not use the value type if it has one, so it might need conversion on device again.
If the linop does not implement the mixed_and_match, it will throw the link issue or runtime error?
Dense->apply return Dense and LinOp->apply return LinOP seems to be the same for me
I agree with you. only concreate type contains all functionalities but originally it's means different function name of the class to me. (maybe related to it, the naming issue compute_absolute_linop
and compute_absoulte
, sorry for out of topic again)
496ce85
to
6deb8e2
Compare
Currently, I think testing only the apply overloads of |
f855a3c
to
f044087
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM for the most part! I'd like to discuss the pointer out-parameter interface though.
examples/adaptiveprecision-blockjacobi/adaptiveprecision-blockjacobi.cpp
Outdated
Show resolved
Hide resolved
also fixes documentation for write
not possible if: - use combination/composition - knows only LinOp*
only tests `EnableValueTypedLinOp` for correctness, no derived classes are checked
1c4c37c
to
e8a9a04
Compare
examples/adaptiveprecision-blockjacobi/adaptiveprecision-blockjacobi.cpp
Outdated
Show resolved
Hide resolved
- reverts norm/dot overload with out pointer paramter Co-authored-by: Tobias Ribizel <ribizel@kit.edu>
e8a9a04
to
265e370
Compare
Kudos, SonarCloud Quality Gate passed! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit format and need some additional documentation.
some discussion on the EnableValueTypeLinOp
std::unique_ptr<matrix::Dense<_type>> scalar_to_dense( \ | ||
const _type, std::shared_ptr<const Executor>) | ||
GKO_INSTANTIATE_FOR_EACH_VALUE_TYPE(GKO_DECLARE_SCALAR_TO_DENSE); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
|
||
|
||
namespace gko { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
explicit DummyValueTypedLinOp(std::shared_ptr<const gko::Executor> exec, | ||
gko::dim<2> size = gko::dim<2>{}) | ||
: gko::EnableValueTypedLinOp<Self, T>(exec, size), value_() | ||
{} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
{} | |
{} | |
public: | ||
explicit DummyValueTypedLinOp(std::shared_ptr<const gko::Executor> exec, | ||
gko::dim<2> size = gko::dim<2>{}) | ||
: gko::EnableValueTypedLinOp<Self, T>(exec, size), value_() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
maybe give zero here?
TYPED_TEST(EnableValueTypedLinOp, CanCallExtendedApplyImplLinopLinop) | ||
{ | ||
using value_type = typename TestFixture::value_type; | ||
this->op->apply(lend(this->alpha), lend(this->b), lend(this->beta), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this->op can not be LinOP, right?
it needs to be the explicit type or EnableValuedTypeLinOp type
<< lplp_diff_norm->at(0) / hp_x_norm->at(0) << "\n"; | ||
std::cout << "Lp * Hp -> Hp time(s): " << lplp_sec << std::endl; | ||
std::cout << "Lp * Hp -> Hp relative error: " | ||
<< lphp_diff_norm->at(0) / hp_x_norm->at(0) << "\n"; | ||
<< lplp_diff_norm->get_values()[0] / hp_x_norm->get_values()[0] | ||
<< "\n"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
some additional delete?
const ConcreteLinOp *apply(const ValueType alpha, const LinOp *b, | ||
const ValueType beta, LinOp *x) const | ||
{ | ||
auto dense_alpha = scalar_to_dense(alpha, this->get_executor()); | ||
auto dense_beta = scalar_to_dense(beta, this->get_executor()); | ||
return this->apply(dense_alpha.get(), b, dense_beta.get(), x); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In current one, you also need to know which type of linop at least which valuetype of linop.
both cases you need to know about that.
it throws error when create dense with int.
if scalar_to_dense have a specialized to handle it, it should be okay.
integer will use float to represent.
using EnableLinOp<ConcreteLinOp, PolymorphicBase>::EnableLinOp; | ||
using EnableLinOp<ConcreteLinOp, PolymorphicBase>::apply; | ||
|
||
const ConcreteLinOp *apply(const ValueType alpha, const LinOp *b, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
need documentation
* Reads a matrix stored in matrix market format from an input stream. | ||
* Writes a matrix into an output in the matrix market format. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
good catch!
namespace matrix { | ||
template <typename ValueType> | ||
class Dense; | ||
} | ||
|
||
template <typename ValueType> | ||
std::unique_ptr<matrix::Dense<ValueType>> scalar_to_dense( | ||
ValueType val, std::shared_ptr<const Executor> exec); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
namespace matrix { | |
template <typename ValueType> | |
class Dense; | |
} | |
template <typename ValueType> | |
std::unique_ptr<matrix::Dense<ValueType>> scalar_to_dense( | |
ValueType val, std::shared_ptr<const Executor> exec); | |
namespace matrix { | |
template <typename ValueType> | |
class Dense; | |
} | |
template <typename ValueType> | |
std::unique_ptr<matrix::Dense<ValueType>> scalar_to_dense( | |
ValueType val, std::shared_ptr<const Executor> exec); | |
I'm closing this for now. ATM, there is no good way of adding the functionality. |
This PR implements solution 1. from #816.
Todo:
Fixes #816