Conversation
- Operator -> ForwardOperator - DiscreteOperator -> Operator
Looks sensible on a first (very short) look. Unfortunately, I won't have time to have a closer look at this in the next few weeks, but feel free to merge this already (with the hyperbolic tests disabled if necessary, I will then fix these in a follow-up PR). |
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.
Very happy with the overall new design.
General question: Can we make not be necessary to pass two seperate objects around controlling the logger state?
try { | ||
tried_linear_solvers.push_back(linear_solver_type); | ||
jacobian_solver.apply( | ||
residual, update, {{"type", linear_solver_type}, {"precision", XT::Common::to_string(0.1 * precision)}}); |
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.
0.1
is a magic number
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.
Yes, do you not like magic numbers? :) As in: this should be configurable via the options?
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 mean I like magic, just not in numbers maybe? 😉
If you didn't find it necessary to change this yet, maybe just make it a meaningfully named constant?
} catch (const XT::LA::Exceptions::linear_solver_failed&) { | ||
} | ||
} | ||
DUNE_THROW_IF(!linear_solve_succeeded, |
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 would not expect that a newton algorithms internally tries a number of not-configurable linear solvers.
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.
Mhm, but this is here for a reason, Felix from a year ago could have told you! :)
For arbitrary nonlinear operators, it happens quite often that systems are barely solvable and that the choice of the linear solver might really make a difference. How should we proceed then?
We could add an option to the newton cfg about the linear solver, where something like "auto"
is the current behavior. It's not that easy however, since we would also like to be able to pass the linear solver options as a sub cfg in the newton cfg. If the user selects a single linear solver this is easy.
However, a realistic scenario would be to specify my favorite direct solver and iterative solvers, say "superlu, amg"
, but how do I pass the respective opts as sub cfgs to the newton?
local_source_in_->bind(element); | ||
local_range_in_->bind(element); | ||
for (auto& data : element_data_) { | ||
for (auto& data : bilinear_form_.element_data()) { | ||
const auto& local_bilinear_form = *std::get<0>(data); |
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.
If you don't mind shifting the deref to the passing site, there's an alternate way to write this:
const auto&& [local_bilinear_form, filter] = data;
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.
??? Do we now have tuple unpacking in C++? That would be amazing!
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.
Well, yes and no. Look up structured bindings, got introduced with 17.
} | ||
|
||
|
||
} // namespace GDT | ||
} // namespace Dune | ||
|
||
#include "matrix.hh" |
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.
If this is not here accidentally a comment explaining the reason would be good for the future.
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.
Agree! Here on purpose. There is a comment at the top where the forward is, but another comment here will do.
return operator_.source_space(); | ||
this->assert_matching_range(range_vector); | ||
VectorType u_update = range_vector.copy(); | ||
u_update.set_all(0.); |
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.
Could this be changed to init with 0 and correct size? Also in a few other places 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.
Yes, agreed. We also have something like zeros_like
I think.
DUNE_THROW_IF(!opts.has_key("type"), Exceptions::operator_error, "opts = " << opts); | ||
const auto type = opts.get<std::string>("type"); | ||
if (type == "zero") { | ||
LOG_(info) << "not adding zero jacobian ..." << std::endl; |
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.
Allow adding zero operators here, but throwing an error otherwise is intentional? If there's a structural reason for this, maybe add a note?
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 is a default implementation in the interface for convenience. Ops with zero jacobian only need to implement available...
, not jacobian()
. So yes, some docs should be there.
1.); | ||
return ret; | ||
} | ||
DUNE_THROW(Exceptions::operator_error, "This DiscreteOperator does not support jacobian(source_vector)!"); |
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.
Unreachable code?
this->assert_matching_range(range_vector); | ||
LOG_(info) << "applying " << this->num_ops() << " operators ..." << std::endl; | ||
range_vector.set_all(0); | ||
auto tmp_range_vector = range_vector; |
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.
see above
{ | ||
DUNE_THROW_IF(type != this->jacobian_options().at(0), Exceptions::operator_error, "type = " << type); | ||
std::vector<XT::Common::Configuration> ret(1); |
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.
Could use plain Config here and return vector(cfg)
instead.
|
||
/// \} | ||
|
||
const std::list<std::pair<std::unique_ptr<LocalElementOperatorType>, std::unique_ptr<ElementFilterType>>>& |
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.
auto return?
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.
Do we have a view on this (when to use auto)? The old 'auto is easier' vs. 'I can see what the type is' argument. Currently, I use auto
when it gets too complicated, so this could apply here. Do we want to use it more often in general?
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 use something like "more than one nesting level --> use auto" as a rule of thumb? If one can just use a type alias I still think it unnecessary/silly do use auto, yeah.
Yes please! Lets discuss this here. |
still awesome
|
I think the mail-in thingy misplaced you comment |
True. Was referring to the tuple-unpack... |
I've just merged the required changes in dune-xt and updated the dune-gdt-super. So we can restart CI here once the new images are there and then start to tackle actual errors from this pr... |
I restarted the pipeline, the first error is that |
Moved to gitlab. |
Remaining todos:
This completely rewrites the (global) operators to address several issues we've had over the time. This is the main change I want to have in dune-gdt before we write the paper. Once this is in, we can have a first release of a wheel.
Notes:
I was playing around with tutorials in Jupyter Notebook using xeus-cling for interactive C++ code and surrounding Markdown cells, which would be my favorite solutions (however, I get lots of segfaults as our code seems to be a bit too much still); doxygen feels clumsy and not-read-by-many to me. I will just start to explain some of the concepts here if it would help the rest of you and we'll see where this ends up, though.
The main changes are:
BilinearFormInterface
, allowing forapply2(v, u)
andnorm(u)
, whereu
andv
are grid functions;ForwardOperatorInterface : public BilinearFormInterface
, additionally allowing forapply(u, range_vector)
, reading a grid functionu
and writing into arange_vector
(plus convenience methods forDiscreteFunction
and the like); andOperatorInterface : public ForwardOperatorInterface
, additionally allowing forapply(source_vector, range_vector)
, reading asource_vector
and writing into arange_vector
,which then allows for
jacobian(...)
and
apply_inverse(...)
all based on vectors, plus the usual convenience methods.
(The main difference between
ForwardOperatorInterface
andOperatorInterface
is, that the former acts on grid functions only and does not require a discrete source space, while the latter requires discrete source space but may additionally implementapply
for grid functions.)For each of the three interfaces there is exactly one default class
BilinearForm
, allowing to+=
local element/intersection bilinear formsForwardOperator
, allowing to+=
local element and intersection operatorsOperator
, allowing to+=
local element and intersection operatorswhich then implements the rest of the respective interface (including
jacobian
andapply_inverse
using dampened newton).Each of the three are appendable to the
GridWalker
, given all additional required information.Each more general class allows to be converted to a more specific one given additional information, e.g.
BilinearForm
can be converted to aMatrixOperator
given a matrix or a discrete source space andForwardOperator
can be converted to anOperator
given a discrete source space.Thus, there is now only one well-defined place where things happen. I.e., to assemble a bilinear form into a matrix, one cannot append the local bilinear forms to the
MatrixOperator
, but creates theBilinearForm
first and then converts this to aMatrixOperator
. In particular, one can still use the defined bilinear form as a product for arbitrary functions.Some examples:
GridFunction
can handle, see use of norms