You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
449 lines
20 KiB
449 lines
20 KiB
#ifndef MEDUSA_BITS_OPERATORS_IMPLICITOPERATORS_FWD_HPP_
|
|
#define MEDUSA_BITS_OPERATORS_IMPLICITOPERATORS_FWD_HPP_
|
|
|
|
/**
|
|
* @file
|
|
* Declarations of implicit operators.
|
|
*
|
|
* @example test/operators/ImplicitOperators_test.cpp
|
|
*/
|
|
|
|
#include <medusa/Config.hpp>
|
|
#include <medusa/bits/approximations/Operators_fwd.hpp>
|
|
#include <medusa/bits/utils/assert.hpp>
|
|
|
|
namespace mm {
|
|
|
|
/**
|
|
* This class represents implicit operators that fill given matrix @ref M and right hand side
|
|
* @ref rhs with appropriate coefficients approximating differential operators with shape
|
|
* functions from given shape storage @ref ss.
|
|
*
|
|
* @warning The operators grad(), value() etc.\ are lazy and will not write to the matrix
|
|
* until summed with another operator or assigned to. If eager evaluation is required,
|
|
* use the @ref OpBase::eval "eval()" method on each operation.
|
|
*
|
|
* @warning Each evaluated operation is only <b>added</b> to the matrix and rhs. Setting the same
|
|
* equation twice thus has the effect of multiplying it by 2. Therefore, the matrix and the rhs
|
|
* should be initialized (usually to zero) before setting any equations.
|
|
*
|
|
* @tparam shape_storage_type Any shape storage class satisfying the @ref ss-concept.
|
|
* @tparam matrix_type Usually and Eigen sparse matrix.
|
|
* @tparam rhs_type Usually an Eigen `VectorXd`.
|
|
* @sa ImplicitVectorOperators
|
|
*
|
|
* Usage example:
|
|
* @snippet operators/ImplicitOperators_test.cpp Implicit operators usage example
|
|
* @ingroup operators
|
|
*/
|
|
template <class shape_storage_type, class matrix_type, class rhs_type>
|
|
class ImplicitOperators {
|
|
public:
|
|
typedef shape_storage_type shape_storage_t; ///< Type of shape storage.
|
|
typedef matrix_type matrix_t; ///< Matrix type.
|
|
typedef rhs_type rhs_t; ///< Right hand side type.
|
|
typedef typename shape_storage_t::vector_t vector_t; ///< Vector type.
|
|
/// Scalar type of matrix elements. Could be different from shape storage scalar type, e.g.
|
|
/// shape storage type can be `double`, but the matrix could be `complex<double>`.
|
|
typedef typename matrix_t::Scalar scalar_t;
|
|
/// Store dimension of the domain.
|
|
enum { /** Dimensionality of the function domain. */ dim = shape_storage_t::dim };
|
|
|
|
private:
|
|
/// Shape storage, but name is shortened for readability.
|
|
const shape_storage_t* ss;
|
|
matrix_t* M; ///< Pointer to problem matrix.
|
|
rhs_t* rhs; ///< Pointer to right hand side.
|
|
int row_offset; ///< Row offset to be used when accessing matrix or rhs coefficients.
|
|
int col_offset; ///< Column offset to be used when accessing matrix coefficients.
|
|
|
|
/**
|
|
* Add `v` to `M(i, j)`.
|
|
* @throw Assertion fails if `v` is none or `i` or `j` are out of range.
|
|
*/
|
|
void addToM(int i, int j, scalar_t v);
|
|
/**
|
|
* Adds `v` to `rhs[i]`.
|
|
* @throw Assertion fails if `v` is none or `i` is out of range.
|
|
*/
|
|
void addToRhs(int i, scalar_t v);
|
|
|
|
template <typename derived_t> class OpBase; // Forward declaration needed.
|
|
|
|
/**
|
|
* Class representing an operation on a specific row of the matrix.
|
|
* This row operations can be summed (with no effect) and assigned to, which sets
|
|
* the right hand side. They enable a syntax like
|
|
* `op.lap(i) + 2*op.grad(i, {1, 2}) = 2.3`.
|
|
*/
|
|
class RowOp {
|
|
friend class ImplicitOperators;
|
|
protected:
|
|
ImplicitOperators& op; ///< Reference to underlying operators.
|
|
int row_; ///< Index of the row to which this operation refers. Row offset is not included.
|
|
/// Construct a row operation for a given row.
|
|
RowOp(ImplicitOperators& op, int row) : op(op), row_(row) {}
|
|
/// Return absolute index of this row operation.
|
|
int row() const { return op.row_offset + row_; }
|
|
public:
|
|
/**
|
|
* Add two row operations together. This does nothing except for checking
|
|
* that both operations refer to the same actual matrix row, catching some programming
|
|
* errors.
|
|
* @throws Assertion fails if `*this` and `other` do not refer to the same matrix row.
|
|
*/
|
|
RowOp operator+(const RowOp& other) const {
|
|
assert_msg(row() == other.row(),
|
|
"Cannot add together terms for different matrix rows %d and %d.",
|
|
row(), other.row());
|
|
return *this;
|
|
}
|
|
/// Add normal operation to a row operation by evaluating the former and adding normally.
|
|
template <typename derived_t>
|
|
RowOp operator+(const OpBase<derived_t>& right) { return *this + right.eval(); }
|
|
|
|
/// Assigns given value to the right hand side.
|
|
void operator=(scalar_t value) {
|
|
op.addToRhs(row_, value);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Base class for all elementary implicit operations. This class enables multiplication with
|
|
* scalar, addition and evaluation of implicit operations. The method eval() is the
|
|
* implementation of the actual operation that writes coefficients to the matrix.
|
|
* CRTP is used for compile time polymorphism of `eval()`, so that concrete operations
|
|
* only need to define this method.
|
|
*/
|
|
template <typename derived_t>
|
|
class OpBase {
|
|
protected:
|
|
ImplicitOperators& op; ///< Reference to underlying operators.
|
|
int node; ///< Index of the point for which to apply the operation.
|
|
int row; ///< Matrix row to which the operation should be applied (without offset).
|
|
/// Construct operation for a given node.
|
|
OpBase(ImplicitOperators& op, int node, int row)
|
|
: op(op), node(node), row(row) {}
|
|
public:
|
|
/**
|
|
* Write appropriate coefficients for this operation to the matrix.
|
|
* @param alpha Scalar to multiply the coefficients with.
|
|
* @return A RowOp for the appropriate matrix row.
|
|
*/
|
|
RowOp eval(scalar_t alpha) const {
|
|
return static_cast<const derived_t &>(*this).eval(alpha);
|
|
}
|
|
/// Eval with `alpha = 1.0`.
|
|
RowOp eval() const { return eval(1.0); }
|
|
/// Evaluates `*this` and sets rhs to `x`.
|
|
void operator=(scalar_t x) { eval() = x; }
|
|
/// Multiply this operation by a scalar.
|
|
RowOp operator*(scalar_t alpha) { return eval(alpha); }
|
|
/// Combine this operation with another row operation.
|
|
RowOp operator+(RowOp right) { return eval() + right; }
|
|
/// Multiply this operation by `-1`.
|
|
RowOp operator-() { return eval(-1.0); }
|
|
/// Combine tho operation with another operation. Both operations are evaluated.
|
|
template <typename other_derived_t>
|
|
RowOp operator+(const OpBase<other_derived_t>& right) { return eval() + right.eval(); }
|
|
/// Multiply with scalar from the left.
|
|
friend RowOp operator*(scalar_t alpha, const OpBase& o) { return o.eval(alpha); }
|
|
};
|
|
|
|
/// Macro that inherits appropriate members from parent OpBase class.
|
|
#define USE_BASE(Name) \
|
|
private: \
|
|
using OpBase<Name>::op; \
|
|
using OpBase<Name>::node; \
|
|
using OpBase<Name>::row; \
|
|
using OpBase<Name>::OpBase; \
|
|
friend class ImplicitOperators; \
|
|
public: \
|
|
using OpBase<Name>::eval; \
|
|
using OpBase<Name>::operator=;
|
|
|
|
/// Class representing the "evaluate" operation, i.e.\ the zero-th derivative.
|
|
class ValueOp : public OpBase<ValueOp> {
|
|
USE_BASE(ValueOp)
|
|
/// Evaluate this operator.
|
|
RowOp eval(scalar_t alpha) const {
|
|
op.addToM(row, node, alpha);
|
|
return RowOp(op, row);
|
|
}
|
|
};
|
|
/// Class representing Basic operators i.e. Der1, Der2, Lap or user defined custom operators.
|
|
template <typename op_family_t>
|
|
class BasicOp : public OpBase<BasicOp<op_family_t>> {
|
|
USE_BASE(BasicOp<op_family_t>)
|
|
private:
|
|
typename op_family_t::operator_t o; ///< Basic operator with precomputed shape functions.
|
|
public:
|
|
/// Construct given basic operator 'o'.
|
|
BasicOp(ImplicitOperators& op, int node, int row, typename op_family_t::operator_t o) :
|
|
OpBase<BasicOp<op_family_t>>(op, node, row), o(o) {}
|
|
/// Evaluate this operator.
|
|
RowOp eval(scalar_t alpha) const {
|
|
// There is some room for minor optimization here: the index does not need to be
|
|
// computed for 1-element families. A special Op could be added for that,
|
|
// but is probably not worth the code complexity.
|
|
int idx = op_family_t::index(o);
|
|
for (int i = 0; i < op.ss->supportSize(node); ++i) {
|
|
op.addToM(row, op.ss->support(node, i), alpha *
|
|
op.ss->template get<op_family_t>(idx, node, i));
|
|
}
|
|
return RowOp(op, row);
|
|
}
|
|
};
|
|
/**
|
|
* Class representing the directional derivative (gradient) operation.
|
|
* If @ref vec is denoted by @f$\vec v@f$, then this class represents
|
|
* @f$(\vec v \cdot \nabla)@f$ operator evaluated at node with index @ref node.
|
|
*/
|
|
class GradOp : public OpBase<GradOp> {
|
|
USE_BASE(GradOp)
|
|
vector_t vec; ///< Vector representing the direction of differentiation.
|
|
|
|
/// Construct directional derivative operator with given direction.
|
|
GradOp(ImplicitOperators& op, int node, int row, vector_t vec) :
|
|
OpBase<GradOp>(op, node, row), vec(vec) {}
|
|
public:
|
|
/// Evaluate this operator.
|
|
RowOp eval(scalar_t alpha) const {
|
|
for (int i = 0; i < op.ss->supportSize(node); ++i) {
|
|
scalar_t value = 0;
|
|
for (int var = 0; var < op.dim; ++var) {
|
|
value += vec[var] * op.ss->d1(var, node, i);
|
|
}
|
|
op.addToM(row, op.ss->support(node, i), alpha * value);
|
|
}
|
|
return RowOp(op, row);
|
|
}
|
|
};
|
|
|
|
public:
|
|
/// Default constructor sets offset to `0` and pointers to `nullptr`.
|
|
ImplicitOperators() : ss(nullptr), M(nullptr), rhs(nullptr), row_offset(0), col_offset(0) {}
|
|
/// Set only shape storage, the rest as default constructor.
|
|
explicit ImplicitOperators(const shape_storage_t& ss) : ss(&ss), M(nullptr), rhs(nullptr),
|
|
row_offset(0), col_offset(0) {}
|
|
/**
|
|
* Set shape storage and problem matrix and rhs.
|
|
* @param ss Class storing all computed shapes.
|
|
* @param M Problem matrix. Must have at least `ss->size()` rows and cols.
|
|
* @param rhs Problem right hand side. Must have at least `ss->size()` rows.
|
|
* @param row_offset Instead of counting rows from 0, count them from row `row_offset`.
|
|
* @param col_offset Instead of counting columns from 0, count them from row `col_offset`.
|
|
*
|
|
* @warning Matrices `M` and `rhs` have values only added to them and should be zero
|
|
* initialized by the user. Since this is a common mistake, a warning is printed if this is not
|
|
* the case when in debug mode.
|
|
*
|
|
* This class is usually constructed directly from shape storage using the
|
|
* `implicitOperators()` member function.
|
|
*/
|
|
ImplicitOperators(const shape_storage_t& ss, matrix_t& M, rhs_t& rhs,
|
|
int row_offset = 0, int col_offset = 0);
|
|
|
|
/// Sets current matrix and right hand side.
|
|
void setProblem(matrix_t& M, rhs_t& rhs, int row_offset = 0, int col_offset = 0) {
|
|
ImplicitOperators::M = &M;
|
|
ImplicitOperators::rhs = &rhs;
|
|
setRowOffset(row_offset);
|
|
setColOffset(col_offset);
|
|
}
|
|
/// Sets row offset for given matrix, treating it as is the first row had index `row_offset`.
|
|
void setRowOffset(int row_offset) {
|
|
assert_msg(0 <= row_offset, "Row offset cannot be negative, got %d.", row_offset);
|
|
ImplicitOperators::row_offset = row_offset;
|
|
}
|
|
/// Sets col offset for given matrix, treating it as is the first column had index `col_offset`.
|
|
void setColOffset(int col_offset) {
|
|
assert_msg(0 <= col_offset, "Col offset cannot be negative, got %d.", col_offset);
|
|
ImplicitOperators::col_offset = col_offset;
|
|
}
|
|
|
|
/// Returns `true` if operators have a non-null pointer to storage.
|
|
bool hasShapes() const { return ss != nullptr; }
|
|
/// Returns `true` if operators have a non-null pointer to problem matrix.
|
|
bool hasMatrix() const { return M != nullptr; }
|
|
/// Returns `true` if operators have a non-null pointer to problem right hand side.
|
|
bool hasRhs() const { return rhs != nullptr; }
|
|
|
|
/**
|
|
* Sets implicit equation that value of a field is equal to some other value.
|
|
* The code `alpha*op.value(node) = v` fills the `node`-th row of matrix `M`
|
|
* so that `node`-th row of the equation
|
|
* @f$ M u = v @f$
|
|
* is a good approximation of the equation
|
|
* @f[ \alpha u(p) = v(p), @f]
|
|
* where @f$p@f$ is the `node`-th point.
|
|
*
|
|
* @param node Index of a node from 0 to `N` for which to write the equation for.
|
|
* This means only the `node`-th row of the matrix is changed.
|
|
* User must make sure that the matrix is large enough.
|
|
* @param row Write equation in this specific row. Row with index `node` is chosen by default.
|
|
* @return A class representing this operation for lazy evaluation purposes.
|
|
*/
|
|
ValueOp value(int node, int row) { return ValueOp(*this, node, row); }
|
|
/// Same as @ref value with row equal to current node.
|
|
ValueOp value(int node) { return value(node, node); }
|
|
|
|
/**
|
|
* Sets implicit equation that Laplacian of a field @f$ u@f$ at `node`-th point
|
|
* is equal to some value. The code `alpha*op.lap(node) = v` fills the `node`-th row of
|
|
* matrix `M` so that `node`-th row of the equation
|
|
* @f$ M u = v @f$
|
|
* is a good approximation of the equation
|
|
* @f[ \alpha \nabla^2 u(p) = v(p), @f]
|
|
* where @f$p@f$ is the `node`-th point.
|
|
*
|
|
* @param node Index of a node from 0 to `N` for which to write the equation for.
|
|
* This means only the `node`-th row of the matrix is changed.
|
|
* User must make sure that the matrix is large enough.
|
|
* @param row Write equation in this specific row. Row with index `node` is chosen by default.
|
|
* @return A class representing this operation for lazy evaluation purposes.
|
|
*
|
|
* Example:
|
|
* @snippet ImplicitOperators_test.cpp Implicit laplace 1d example
|
|
*/
|
|
BasicOp<Lap<dim>> lap(int node, int row) { return BasicOp<Lap<dim>>(*this, node, row, {}); }
|
|
/// Same as @ref lap with row equal to current node.
|
|
BasicOp<Lap<dim>> lap(int node) { return lap(node, node); }
|
|
|
|
/**
|
|
* Sets implicit equation that gradient of a field @f$ u@f$ at `node`-th point
|
|
* along `v` is equal to some value.
|
|
* The code `alpha*op.grad(node, v) = r` fills the `node`-th row of
|
|
* matrix `M` so that `node`-th row of the equation
|
|
* @f$ M u = r @f$
|
|
* is a good approximation of the equation
|
|
* @f[ \alpha \vec{v} \cdot (\nabla u)(p) = r(p), @f]
|
|
* where @f$p@f$ is the `node`-th point and @f$\vec{v}@f$ is the vector `v`.
|
|
* Alternatively, this can be viewed as setting the
|
|
* directional derivative along `v` to be equal to `r`.
|
|
*
|
|
* @param node Index of a node from 0 to `N` for which to write the equation for.
|
|
* This means only `node`-th row of the matrix is changed.
|
|
* User must make sure that the matrix is large enough.
|
|
* @param v Vector to multiply the gradient with.
|
|
* @param row Write equation in this specific row. Row with index `node` is chosen by default.
|
|
* @return A class representing this operation for lazy evaluation purposes.
|
|
*
|
|
* Example:
|
|
* @snippet ImplicitOperators_test.cpp Implicit Grad example
|
|
*/
|
|
GradOp grad(int node, const vector_t& v, int row) { return GradOp(*this, node, row, v); }
|
|
/// Same as @ref grad with row equal to current node.
|
|
GradOp grad(int node, const vector_t& v) { return grad(node, v, node); }
|
|
|
|
/**
|
|
* Sets neumann boundary conditions in node `node`. The code
|
|
* `alpha*op.neumann(node, normal) = v`
|
|
* fills the `node`-th row of matrix `M` so that `node`-th row of the equation
|
|
* @f$ M u = v @f$
|
|
* is a good approximation of the equation
|
|
* @f[ \alpha \dpar{u}{\vec{n}}(p) = v(p), @f]
|
|
* where @f$p@f$ is the `node`-th point and @f$\vec{n}@f$ is the `unit_normal`.
|
|
*
|
|
* This is the same as using grad(), but has additional semantic meaning of setting the
|
|
* boundary conditions.
|
|
*
|
|
* @return A class representing this operation for lazy evaluation purposes.
|
|
*
|
|
* @sa grad
|
|
*/
|
|
GradOp neumann(int node, const vector_t& unit_normal, int row) {
|
|
assert_msg(std::abs(unit_normal.norm() - 1.0) < 1e-13,
|
|
"Normal %s is not of unit length, got %.16f.", unit_normal, unit_normal.norm());
|
|
return grad(node, unit_normal, row);
|
|
}
|
|
/// Same as @ref neumann with row equal to current node.
|
|
GradOp neumann(int node, const vector_t& n) { return neumann(node, n, node); }
|
|
|
|
/**
|
|
* Sets implicit equation that derivative of a field @f$ u@f$ at `node`-th point
|
|
* with respect to `var` is equal to some value.
|
|
*
|
|
* The code `alpha*op.der1(node, 0) = v`
|
|
* fills the `node`-th row of matrix `M` so that `node`-th row of the equation
|
|
* @f$ M u = v @f$
|
|
* is a good approximation of the equation
|
|
* @f[ \alpha \dpar{u}{x}(p) = v(p), @f]
|
|
* where @f$p@f$ is the `node`-th point and @f$x@f$ is the `0`-th variable.
|
|
*
|
|
* @param node Index of a node from 0 to `N` for which to write the equation for.
|
|
* This means only `node`-th row of the matrix is changed.
|
|
* User must make sure that the matrix is large enough.
|
|
* @param var Variable with respect to which to derive.
|
|
* @param row Write equation in this specific row. Row with index `node` is chosen by default.
|
|
*
|
|
* @return A class representing this operation for lazy evaluation purposes.
|
|
*/
|
|
BasicOp<Der1s<dim>> der1(int node, int var, int row) {
|
|
return BasicOp<Der1s<dim>>(*this, node, row, {var});
|
|
}
|
|
/// Same as @ref der1 with row equal to current node.
|
|
BasicOp<Der1s<dim>> der1(int node, int var) { return der1(node, var, node); }
|
|
|
|
/**
|
|
* Sets implicit equation that second derivative of a field @f$ u@f$ at `node`-th point
|
|
* with respect to `varmin` and `varmax` is equal to some value.
|
|
*
|
|
* The code `alpha*op.der2(node, 0, 1) = v`
|
|
* fills the `node`-th row of matrix `M` so that `node`-th row of the equation
|
|
* @f$ M u = v @f$
|
|
* is a good approximation of the equation
|
|
* @f[ \alpha \dpar{^2u}{x\partial y}(p) = v(p), @f]
|
|
* where @f$p@f$ is the `node`-th point and @f$x@f$ and @f$y@f$ are the `0`-th
|
|
* and the `1`-st variables.
|
|
*
|
|
* @param node Index of a node from 0 to `N` for which to write the equation for.
|
|
* This means only `node`-th row of the matrix is changed.
|
|
* User must make sure that the matrix is large enough.
|
|
* @param varmin Smaller of the two variables with respect to which to derive.
|
|
* @param varmax Grater of the two variables with respect to which to derive.
|
|
* @param row Write equation in this specific row. Row with index `node` is chosen by default.
|
|
*
|
|
* @return A class representing this operation for lazy evaluation purposes.
|
|
*/
|
|
BasicOp<Der2s<dim>> der2(int node, int varmin, int varmax, int row) {
|
|
return BasicOp<Der2s<dim>>(*this, node, row, {varmin, varmax});
|
|
}
|
|
/// Same as @ref der2 with row equal to current node.
|
|
BasicOp<Der2s<dim>> der2(int node, int varmin, int varmax) {
|
|
return der2(node, varmin, varmax, node);
|
|
}
|
|
|
|
|
|
/**
|
|
* Add the weights for operator `o` to the appropriate elements in the matrix.
|
|
* @param node
|
|
* @param o Operator family
|
|
* @param row Write in this matrix row.
|
|
* @return This function is lazy and returns an "operation" which will write the
|
|
* weights in the matrix, when evaluated.
|
|
*/
|
|
template <typename op_family_t>
|
|
BasicOp<op_family_t> apply(int node, typename op_family_t::operator_t o, int row) {
|
|
return BasicOp<op_family_t>(*this, node, row, o);
|
|
}
|
|
|
|
/// Same as @ref apply with row equal to current node.
|
|
template <typename op_family_t>
|
|
BasicOp<op_family_t> apply(int node, typename op_family_t::operator_t o) {
|
|
return apply<op_family_t>(node, o, node);
|
|
}
|
|
|
|
/// Overload for default-constructible operator.
|
|
template <typename op_family_t>
|
|
BasicOp<op_family_t> apply(int node) { return apply<op_family_t>(node, {}, node); }
|
|
|
|
|
|
/// Output basic info about given operators.
|
|
template <typename S, typename M, typename R>
|
|
friend std::ostream& operator<<(std::ostream& os, const ImplicitOperators<S, M, R>& op);
|
|
};
|
|
|
|
} // namespace mm
|
|
|
|
#endif // MEDUSA_BITS_OPERATORS_IMPLICITOPERATORS_FWD_HPP_
|
|
|