NeoN
A framework for CFD software
Loading...
Searching...
No Matches
expression.hpp
Go to the documentation of this file.
1// SPDX-FileCopyrightText: 2023 - 2026 NeoN authors
2//
3// SPDX-License-Identifier: MIT
4
5#pragma once
6
7#include <algorithm>
8#include <vector>
9
10#include "NeoN/core/error.hpp"
14#include "NeoN/fields/field.hpp"
21#ifdef NF_WITH_MPI_SUPPORT
23#endif
24
27
28namespace NeoN::dsl
29{
30
31template<typename VectorType, typename IndexType>
33{
34 virtual ~PostAssemblyBase() = default;
35 virtual void
37 const {};
38
48};
49
65template<typename ValueType, typename IndexType = localIdx>
66class SetReference : public PostAssemblyBase<ValueType, IndexType>
67{
68public:
69
70 SetReference(localIdx refCell, ValueType refValue) : refCell_(refCell), refValue_(refValue) {}
71
73 ) const override
74 {
75#ifdef NF_WITH_MPI_SUPPORT
76 // For distributed systems, only the rank owning refCell applies the constraint.
77 // For non-distributed systems (each rank holds a full copy), every rank applies it.
78 if (!ls.commPattern().sendCounts.empty())
79 {
80 mpi::Environment mpiEnv;
81 if (mpiEnv.isInitialized() && mpiEnv.rank() != 0) return;
82 }
83#endif
84 auto lsView = ls.view();
85 const auto ma = ls.faceToMatrixAddress()->view(ls.matrix().sparsity()->rowOffs().view());
86 auto refVal = refValue_;
87 auto refCell = refCell_;
89 ls.exec(),
90 {refCell, refCell + 1},
91 NEON_LAMBDA(const localIdx celli) {
92 auto dIdx = ma.diagIdx(celli);
93 auto diagVal = lsView.matrix.values[dIdx];
94 lsView.rhs[celli] += diagVal * refVal;
95 lsView.matrix.values[dIdx] += diagVal;
96 },
97 "SetReference"
98 );
99 }
100
105 scalar,
106 ValueType,
108 la::COOMatrix<scalar, IndexType>>& ls) const override
109 {
110#ifdef NF_WITH_MPI_SUPPORT
111 // For distributed systems, only the rank owning refCell applies the constraint.
112 // For non-distributed systems (each rank holds a full copy), every rank applies it.
113 if (!ls.commPattern().sendCounts.empty())
114 {
115 mpi::Environment mpiEnv;
116 if (mpiEnv.isInitialized() && mpiEnv.rank() != 0) return;
117 }
118#endif
119 auto lsView = ls.view();
120 const auto ma = ls.faceToMatrixAddress()->view(ls.matrix().sparsity()->rowOffs().view());
121 auto refVal = refValue_;
122 auto refCell = refCell_;
124 ls.exec(),
125 {refCell, refCell + 1},
126 NEON_LAMBDA(const localIdx celli) {
127 auto dIdx = ma.diagIdx(celli);
128 auto diagVal = lsView.matrix.values[dIdx];
129 lsView.rhs[celli] += diagVal * refVal;
130 lsView.matrix.values[dIdx] += diagVal;
131 },
132 "SetReference"
133 );
134 }
135
136private:
137
138 localIdx refCell_;
139 ValueType refValue_;
140};
141
142
173template<typename ValueType, typename IndexType = localIdx>
174class FixedValueConstraints : public PostAssemblyBase<ValueType, IndexType>
175{
176 static_assert(
177 std::is_same_v<ValueType, scalar>,
178 "FixedValueConstraints only supports scalar fields. "
179 "For non-scalar fields implement applyScalarMatrix()."
180 );
181
182public:
183
185 : mask_(mask), values_(values), nCells_(nCells)
186 {}
187
189 ) const override
190 {
191 auto lsView = ls.view();
192 const auto rowOffs = ls.matrix().sparsity()->rowOffs().view();
193 const auto colIdxs = ls.matrix().sparsity()->colIdxs().view();
194 auto matrixValues = lsView.matrix.values;
195 auto rhs = lsView.rhs;
196 auto mask = mask_;
197 auto vals = values_;
198 // Sweep EVERY row (not only the pinned ones): a pinned row drops its off-diagonals, while
199 // a NON-pinned row that couples into a pinned column relocates that term to its own source
200 // and zeros it (OpenFOAM's column cut). Parallelising over rows means each row owns its own
201 // rhs entry and its own off-diagonal slots, so no atomics are needed.
203 ls.exec(),
204 {0, nCells_},
205 NEON_LAMBDA(const localIdx row) {
206 const bool rowPinned = mask[row] != scalar(0);
207 ValueType diagVal = zero<ValueType>();
208 for (auto o = rowOffs[row]; o < rowOffs[row + 1]; ++o)
209 {
210 const auto col = colIdxs[o];
211 if (col == row)
212 {
213 diagVal = matrixValues[o];
214 continue;
215 }
216 if (rowPinned)
217 {
218 // pinned cell's own row: decouple it entirely
219 matrixValues[o] = zero<ValueType>();
220 }
221 else if (mask[col] != scalar(0))
222 {
223 // neighbour row coupling INTO a pinned cell: move the term to this row's
224 // source as a constant, then drop the coefficient (OF setValues column cut)
225 rhs[row] -= matrixValues[o] * vals[col];
226 matrixValues[o] = zero<ValueType>();
227 }
228 }
229 if (rowPinned)
230 {
231 // row now reads A[c,c]*x_c = A[c,c]*value[c] => x_c = value[c]
232 rhs[row] = diagVal * vals[row];
233 }
234 },
235 "FixedValueConstraints"
236 );
237 // Zero offDiagonalMatrix entries for pinned rows so that proc-boundary
238 // couplings do not contribute to the residual of constrained cells.
239 auto& offDiag = ls.offDiagonalMatrix();
240 const localIdx nnz = offDiag.nNonZeros();
241 if (nnz > 0)
242 {
243 const auto offRowIdxs = offDiag.sparsity()->rowIdxs().view();
244 auto offValues = offDiag.values().view();
246 ls.exec(),
247 {0, nnz},
248 NEON_LAMBDA(const localIdx i) {
249 if (mask[offRowIdxs[i]] != scalar(0)) offValues[i] = zero<ValueType>();
250 },
251 "FixedValueConstraints::offDiag"
252 );
253 }
254 }
255
256private:
257
258 View<const scalar> mask_;
259 View<const ValueType> values_;
260 localIdx nCells_;
261};
262
263
264template<typename ValueType, typename IndexType = localIdx>
266{
267public:
268
269 using ExpressionValueType = ValueType;
270
271 Expression(const Executor& exec) : exec_(exec), temporalOperators_(), spatialOperators_() {}
272
274 : exec_(exp.exec_), temporalOperators_(exp.temporalOperators_),
275 spatialOperators_(exp.spatialOperators_)
276 {}
277
279 : exec_(oper.exec()), temporalOperators_(), spatialOperators_()
280 {
281 spatialOperators_.push_back(oper);
282 }
283
285 {
286 if (this == &exp)
287 {
288 return *this;
289 }
290 NF_ASSERT(exec_ == exp.exec_, "Executors are not the same");
291 temporalOperators_ = exp.temporalOperators_;
292 spatialOperators_ = exp.spatialOperators_;
293 return *this;
294 }
295
296
298 : exec_(oper.exec()), temporalOperators_(), spatialOperators_()
299 {
300 temporalOperators_.push_back(oper);
301 }
302
303 /* @brief dispatch read call to operator */
304 void read(const Dictionary& input)
305 {
306 for (auto& op : temporalOperators_)
307 {
308 op.read(input);
309 }
310 for (auto& op : spatialOperators_)
311 {
312 op.read(input);
313 }
314 }
315
316 /* @brief perform all explicit operation and accumulate the result */
318 {
319 Vector<ValueType> source(exec_, nCells, zero<ValueType>());
320 return explicitOperation(source);
321 }
322
323 /* @brief perform all explicit operation and accumulate the result */
325 {
326 for (auto& op : spatialOperators_)
327 {
328 if (op.getType() == Operator::Type::Explicit)
329 {
330 op.explicitOperation(source);
331 }
332 }
333 return source;
334 }
335
337 {
338 for (auto& op : temporalOperators_)
339 {
340 if (op.getType() == Operator::Type::Explicit)
341 {
342 op.explicitOperation(source, t, dt);
343 }
344 }
345 return source;
346 }
347
349 template<typename AssemblyType = ValueType>
351 {
352 for (auto& op : spatialOperators_)
353 {
354 if (op.getType() == Operator::Type::Implicit)
355 {
356 op.implicitOperation(ls);
357 }
358 }
359 }
360
364 template<typename AssemblyType = ValueType>
367 ) const
368 {
369 for (auto& op : temporalOperators_)
370 {
371 if (op.getType() == Operator::Type::Implicit)
372 {
373 op.implicitOperation(ls, t, dt);
374 }
375 }
376 }
377
378 /*@brief subtract explicit source terms from the linear system rhs, scaled by cell volumes */
379 template<typename AssemblyType = ValueType>
382 ) const
383 {
384 auto expTmp = explicitOperation(static_cast<localIdx>(mesh.nCells()));
385 auto [vol, expSource, rhs] = views(mesh.cellVolumes(), expTmp, ls.rhs());
387 ls.exec(),
388 {0, static_cast<localIdx>(rhs.size())},
389 NEON_LAMBDA(const localIdx i) { rhs[i] -= expSource[i] * vol[i]; }
390 );
391 }
392
398 template<typename AssemblyType = ValueType>
400 const UnstructuredMesh& mesh,
401 scalar t,
402 scalar dt,
403 std::vector<const PostAssemblyBase<ValueType, IndexType>*> ps = {}
404 ) const
405 {
406 auto ls = la::createEmptyLinearSystem<AssemblyType, ValueType>(mesh);
407 assemble<AssemblyType>(t, dt, ls, mesh, ps);
408 return ls;
409 }
410
415 template<typename AssemblyType = ValueType>
417 scalar t,
418 scalar dt,
420 const UnstructuredMesh& mesh,
421 std::vector<const PostAssemblyBase<ValueType, IndexType>*> ps = {}
422 ) const
423 {
424 assemble<AssemblyType>(t, dt, ls, ps);
425 assembleExplicitSource(ls, mesh);
426 }
427
428 /* @brief assemble into a given linear system (implicit operators only, no explicit sources)
429 *
430 * @param ps post-assembly functors applied to the system after assembly
431 */
432 template<typename AssemblyType = ValueType>
434 scalar t,
435 scalar dt,
437 std::vector<const PostAssemblyBase<ValueType, IndexType>*> ps = {}
438 ) const
439 {
440 assembleSpatialOperator(ls); // add spatial operator
441 assembleTemporalOperator(ls, t, dt); // add temporal operators
442
443 // Post-assembly functors apply on the same-type form via operator(); the segregated
444 // scalar-matrix / ValueType-rhs form dispatches to applyScalarMatrix instead.
445 if constexpr (std::is_same_v<AssemblyType, ValueType>)
446 {
447 for (const auto* p : ps)
448 {
449 (*p)(ls);
450 }
451 }
452 else if constexpr (std::is_same_v<AssemblyType, scalar>)
453 {
454 for (const auto* p : ps)
455 {
456 p->applyScalarMatrix(ls);
457 }
458 }
459 }
460
461 void addOperator(const SpatialOperator<ValueType>& oper) { spatialOperators_.push_back(oper); }
462
464 {
465 temporalOperators_.push_back(oper);
466 }
467
468 void addExpression(const Expression& equation)
469 {
470 for (auto& op : equation.temporalOperators_)
471 {
472 temporalOperators_.push_back(op);
473 }
474 for (auto& op : equation.spatialOperators_)
475 {
476 spatialOperators_.push_back(op);
477 }
478 }
479
481 template<typename OperatorType, Operator::Type Type>
482 bool hasOperatorOfType(const std::string& name) const
483 {
484 auto opType = Type;
485 auto matchNameAndType = [name, opType](const auto& op)
486 { return op.getName() == name && op.getType() == opType; };
487 if constexpr (std::is_same_v<OperatorType, SpatialOperator<ValueType>>)
488 {
489 return std::ranges::any_of(spatialOperators_, matchNameAndType);
490 }
491 else if constexpr (std::is_same_v<OperatorType, TemporalOperator<ValueType>>)
492 {
493 return std::ranges::any_of(temporalOperators_, matchNameAndType);
494 }
495 return false;
496 }
497
499 template<Operator::Type Type>
500 bool hasOperator(const std::string& name) const
501 {
502 return hasOperatorOfType<SpatialOperator<ValueType>, Type>(name)
504 }
505
507 template<typename OperatorType, Operator::Type Type>
508 OperatorType& getOperator(const std::string& name)
509 {
510 if (!hasOperatorOfType<OperatorType, Type>(name))
511 {
512 throw std::runtime_error {"No operator with given name and type found"};
513 }
514 auto opType = Type;
515 auto matchNameAndType = [name, opType](const auto& op)
516 { return op.getName() == name && op.getType() == opType; };
517 if constexpr (std::is_same_v<OperatorType, SpatialOperator<ValueType>>)
518 {
519 return *std::ranges::find_if(spatialOperators_, matchNameAndType);
520 }
521 else if constexpr (std::is_same_v<OperatorType, TemporalOperator<ValueType>>)
522 {
523 return *std::ranges::find_if(temporalOperators_, matchNameAndType);
524 }
525 throw std::runtime_error {"Unknown operator type"};
526 // should never be reached, shut up compiler warning
527 return spatialOperators_[0];
528 }
529
531 template<Operator::Type Type>
532 void dropOperator(const std::string& name)
533 {
534 if (!hasOperator<Type>(name))
535 {
536 throw std::runtime_error {"No operator with given name and type found"};
537 }
538 auto opType = Type;
539 auto matchNameAndType = [name, opType](const auto& op)
540 { return op.getName() == name && op.getType() == opType; };
542 {
543 std::erase_if(spatialOperators_, matchNameAndType);
544 }
545 else
546 {
547 std::erase_if(temporalOperators_, matchNameAndType);
548 }
549 }
550
551 /* @brief getter for the total number of terms in the equation */
553 {
554 return static_cast<localIdx>(temporalOperators_.size() + spatialOperators_.size());
555 }
556
557 // getters
558 const std::vector<TemporalOperator<ValueType>>& temporalOperators() const
559 {
560 return temporalOperators_;
561 }
562
563 const std::vector<SpatialOperator<ValueType>>& spatialOperators() const
564 {
565 return spatialOperators_;
566 }
567
568 std::vector<TemporalOperator<ValueType>>& temporalOperators() { return temporalOperators_; }
569
570 std::vector<SpatialOperator<ValueType>>& spatialOperators() { return spatialOperators_; }
571
572 const Executor& exec() const { return exec_; }
573
574private:
575
576 const Executor exec_;
577
578 std::vector<TemporalOperator<ValueType>> temporalOperators_;
579
580 std::vector<SpatialOperator<ValueType>> spatialOperators_;
581};
582
583template<typename ValueType>
584[[nodiscard]] inline Expression<ValueType>
586{
587 lhs.addExpression(rhs);
588 return lhs;
589}
590
591template<typename ValueType>
592[[nodiscard]] inline Expression<ValueType>
594{
595 lhs.addOperator(rhs);
596 return lhs;
597}
598
599template<typename leftOperator, typename rightOperator>
600[[nodiscard]] inline Expression<typename leftOperator::VectorValueType>
601operator+(leftOperator lhs, rightOperator rhs)
602{
603 using ValueType = typename leftOperator::VectorValueType;
604 Expression<ValueType> expr(lhs.exec());
605 expr.addOperator(lhs);
606 expr.addOperator(rhs);
607 return expr;
608}
609
610template<typename ValueType>
611[[nodiscard]] inline Expression<ValueType> operator*(scalar scale, const Expression<ValueType>& es)
612{
613 Expression<ValueType> expr(es.exec());
614 for (const auto& oper : es.temporalOperators())
615 {
616 expr.addOperator(scale * oper);
617 }
618 for (const auto& oper : es.spatialOperators())
619 {
620 expr.addOperator(scale * oper);
621 }
622 return expr;
623}
624
625
626template<typename ValueType>
627[[nodiscard]] inline Expression<ValueType>
629{
630 lhs.addExpression(-1.0 * rhs);
631 return lhs;
632}
633
634template<typename ValueType>
635[[nodiscard]] inline Expression<ValueType>
637{
638 lhs.addOperator(-1.0 * rhs);
639 return lhs;
640}
641
642template<typename leftOperator, typename rightOperator>
643[[nodiscard]] inline Expression<typename leftOperator::VectorValueType>
644operator-(leftOperator lhs, rightOperator rhs)
645{
646 using ValueType = typename leftOperator::VectorValueType;
647 Expression<ValueType> expr(lhs.exec());
648 expr.addOperator(lhs);
649 expr.addOperator(Coeff(-1) * rhs);
650 return expr;
651}
652
653
654} // namespace dsl
A class representing a dictionary that stores key-value pairs.
Represents an unstructured mesh in NeoN.
localIdx nCells() const
Get the number of cells in the mesh.
const scalarVector & cellVolumes() const
Get the field of cell volumes in the mesh.
A class to contain the data and executors for a field and define some basic operations.
Definition vector.hpp:27
A class that represents a coefficient for the NeoN dsl.
Definition coeff.hpp:24
Expression & operator=(const Expression &exp)
void assemble(scalar t, scalar dt, la::LinearSystem< AssemblyType, ValueType > &ls, std::vector< const PostAssemblyBase< ValueType, IndexType > * > ps={}) const
la::LinearSystem< AssemblyType, ValueType > assemble(const UnstructuredMesh &mesh, scalar t, scalar dt, std::vector< const PostAssemblyBase< ValueType, IndexType > * > ps={}) const
construct a linear system and force assembly including explicit source terms
std::vector< TemporalOperator< ValueType > > & temporalOperators()
Expression(const SpatialOperator< ValueType > &oper)
void assembleTemporalOperator(la::LinearSystem< AssemblyType, ValueType > &ls, scalar t, scalar dt) const
compute matrix coefficients based on all temporal operators assemble directly into linear system
Vector< ValueType > explicitOperation(Vector< ValueType > &source) const
Expression(const TemporalOperator< ValueType > &oper)
std::vector< SpatialOperator< ValueType > > & spatialOperators()
Expression(const Expression &exp)
bool hasOperatorOfType(const std::string &name) const
returns operator of given type and name exists
const std::vector< TemporalOperator< ValueType > > & temporalOperators() const
void dropOperator(const std::string &name)
removes operator of given name
void assembleSpatialOperator(la::LinearSystem< AssemblyType, ValueType > &ls) const
compute matrix coefficients based on all spatial operators
void read(const Dictionary &input)
void addOperator(const TemporalOperator< ValueType > &oper)
localIdx size() const
void assemble(scalar t, scalar dt, la::LinearSystem< AssemblyType, ValueType > &ls, const UnstructuredMesh &mesh, std::vector< const PostAssemblyBase< ValueType, IndexType > * > ps={}) const
assemble into a given linear system including explicit source terms
OperatorType & getOperator(const std::string &name)
returns operator of given type and name
bool hasOperator(const std::string &name) const
returns whether the expression contains an operator with a given name
Vector< ValueType > explicitOperation(localIdx nCells) const
void addExpression(const Expression &equation)
void assembleExplicitSource(la::LinearSystem< AssemblyType, ValueType > &ls, const UnstructuredMesh &mesh) const
Vector< ValueType > explicitOperation(Vector< ValueType > &source, scalar t, scalar dt) const
void addOperator(const SpatialOperator< ValueType > &oper)
const Executor & exec() const
const std::vector< SpatialOperator< ValueType > > & spatialOperators() const
ValueType ExpressionValueType
Expression(const Executor &exec)
Post-assembly functor that pins a set of cells to prescribed values.
void operator()(la::LinearSystem< ValueType, ValueType, la::CSRMatrix< ValueType, IndexType > > &ls) const override
FixedValueConstraints(View< const scalar > mask, View< const ValueType > values, localIdx nCells)
Post-assembly functor that pins one cell's value to a reference, removing the constant null space tha...
SetReference(localIdx refCell, ValueType refValue)
void applyScalarMatrix(la::LinearSystem< scalar, ValueType, la::CSRMatrix< scalar, IndexType >, la::COOMatrix< scalar, IndexType > > &ls) const override
Segregated scalar-matrix / ValueType-rhs form. The scalar diagonal scales the ValueType reference val...
void operator()(la::LinearSystem< ValueType, ValueType, la::CSRMatrix< ValueType, IndexType > > &ls) const override
A class representing a linear system of equations.
Vector< RHSValueType > & rhs()
const Executor & exec() const
Sparse matrix class with compact storage by row (CSR) format.
Definition matrix.hpp:71
#define NF_ASSERT(condition, message)
Macro for asserting a condition and printing an error message if the condition is false.
Definition error.hpp:118
Expression< ValueType > operator+(Expression< ValueType > lhs, const Expression< ValueType > &rhs)
Coeff operator*(const Coeff &lhs, const Coeff &rhs)
Definition coeff.hpp:61
Expression< ValueType > operator-(Expression< ValueType > lhs, const Expression< ValueType > &rhs)
int32_t localIdx
Definition label.hpp:50
std::variant< SerialExecutor, CPUExecutor, GPUExecutor > Executor
Definition executor.hpp:20
float scalar
Definition scalar.hpp:17
void parallelFor(const ExecutorType &, std::pair< localIdx, localIdx > range, const Kernel &kernel, std::string name)
const std::string & name(const NeoN::Document &doc)
Retrieves the name of a Document.
auto views(Types &... args)
Unpacks all views of the passed classes.
Definition view.hpp:107
#define NEON_LAMBDA
virtual void operator()(la::LinearSystem< VectorType, VectorType, la::CSRMatrix< VectorType, IndexType > > &) const
virtual ~PostAssemblyBase()=default
virtual void applyScalarMatrix(la::LinearSystem< scalar, VectorType, la::CSRMatrix< scalar, IndexType >, la::COOMatrix< scalar, IndexType > > &) const
Apply to the segregated scalar-matrix / VectorType-rhs form (a scalar coefficient matrix with a Vecto...