// DO NOT EDIT ! // This file is generated using the MantaFlow preprocessor (prep generate). /****************************************************************************** * * MantaFlow fluid solver framework * Copyright 2011 Tobias Pfaff, Nils Thuerey * * This program is free software, distributed under the terms of the * Apache License, Version 2.0 * http://www.apache.org/licenses/LICENSE-2.0 * * Plugins for pressure correction: solve_pressure, and ghost fluid helpers * ******************************************************************************/ #include "vectorbase.h" #include "grid.h" #include "kernel.h" #include "conjugategrad.h" #include "rcmatrix.h" using namespace std; namespace Manta { // only supports a single blur size for now, globals stored here bool gBlurPrecomputed = false; int gBlurKernelRadius = -1; Matrix gBlurKernel; // ***************************************************************************** // Helper functions for fluid guiding //! creates a 1D (horizontal) Gaussian blur kernel of size n and standard deviation sigma Matrix get1DGaussianBlurKernel(const int n, const int sigma) { Matrix x(n), y(n); for (int j = 0; j < n; j++) { x.add_to_element(0, j, -(n - 1) * 0.5); y.add_to_element(0, j, j - (n - 1) * 0.5); } Matrix G(n); Real sumG = 0; for (int j = 0; j < n; j++) { G.add_to_element(0, j, 1 / (2 * M_PI * sigma * sigma) * exp(-(x(0, j) * x(0, j) + y(0, j) * y(0, j)) / (2 * sigma * sigma))); sumG += G(0, j); } G = G * (1.0 / sumG); return G; } //! convolves in with 1D kernel (centred at the kernel's midpoint) in the x-direction //! (out must be a grid of zeros) struct apply1DKernelDirX : public KernelBase { apply1DKernelDirX(const MACGrid &in, MACGrid &out, const Matrix &kernel) : KernelBase(&in, 0), in(in), out(out), kernel(kernel) { runMessage(); run(); } inline void op(int i, int j, int k, const MACGrid &in, MACGrid &out, const Matrix &kernel) const { int nx = in.getSizeX(); int kn = kernel.n; int kCentre = kn / 2; for (int m = 0, ind = kn - 1, ii = i - kCentre; m < kn; m++, ind--, ii++) { if (ii < 0) continue; else if (ii >= nx) break; else out(i, j, k) += in(ii, j, k) * kernel(0, ind); } } inline const MACGrid &getArg0() { return in; } typedef MACGrid type0; inline MACGrid &getArg1() { return out; } typedef MACGrid type1; inline const Matrix &getArg2() { return kernel; } typedef Matrix type2; void runMessage() { debMsg("Executing kernel apply1DKernelDirX ", 3); debMsg("Kernel range" << " x " << maxX << " y " << maxY << " z " << minZ << " - " << maxZ << " ", 4); }; void operator()(const tbb::blocked_range &__r) const { const int _maxX = maxX; const int _maxY = maxY; if (maxZ > 1) { for (int k = __r.begin(); k != (int)__r.end(); k++) for (int j = 0; j < _maxY; j++) for (int i = 0; i < _maxX; i++) op(i, j, k, in, out, kernel); } else { const int k = 0; for (int j = __r.begin(); j != (int)__r.end(); j++) for (int i = 0; i < _maxX; i++) op(i, j, k, in, out, kernel); } } void run() { if (maxZ > 1) tbb::parallel_for(tbb::blocked_range(minZ, maxZ), *this); else tbb::parallel_for(tbb::blocked_range(0, maxY), *this); } const MACGrid ∈ MACGrid &out; const Matrix &kernel; }; //! convolves in with 1D kernel (centred at the kernel's midpoint) in the y-direction //! (out must be a grid of zeros) struct apply1DKernelDirY : public KernelBase { apply1DKernelDirY(const MACGrid &in, MACGrid &out, const Matrix &kernel) : KernelBase(&in, 0), in(in), out(out), kernel(kernel) { runMessage(); run(); } inline void op(int i, int j, int k, const MACGrid &in, MACGrid &out, const Matrix &kernel) const { int ny = in.getSizeY(); int kn = kernel.n; int kCentre = kn / 2; for (int m = 0, ind = kn - 1, jj = j - kCentre; m < kn; m++, ind--, jj++) { if (jj < 0) continue; else if (jj >= ny) break; else out(i, j, k) += in(i, jj, k) * kernel(0, ind); } } inline const MACGrid &getArg0() { return in; } typedef MACGrid type0; inline MACGrid &getArg1() { return out; } typedef MACGrid type1; inline const Matrix &getArg2() { return kernel; } typedef Matrix type2; void runMessage() { debMsg("Executing kernel apply1DKernelDirY ", 3); debMsg("Kernel range" << " x " << maxX << " y " << maxY << " z " << minZ << " - " << maxZ << " ", 4); }; void operator()(const tbb::blocked_range &__r) const { const int _maxX = maxX; const int _maxY = maxY; if (maxZ > 1) { for (int k = __r.begin(); k != (int)__r.end(); k++) for (int j = 0; j < _maxY; j++) for (int i = 0; i < _maxX; i++) op(i, j, k, in, out, kernel); } else { const int k = 0; for (int j = __r.begin(); j != (int)__r.end(); j++) for (int i = 0; i < _maxX; i++) op(i, j, k, in, out, kernel); } } void run() { if (maxZ > 1) tbb::parallel_for(tbb::blocked_range(minZ, maxZ), *this); else tbb::parallel_for(tbb::blocked_range(0, maxY), *this); } const MACGrid ∈ MACGrid &out; const Matrix &kernel; }; //! convolves in with 1D kernel (centred at the kernel's midpoint) in the z-direction //! (out must be a grid of zeros) struct apply1DKernelDirZ : public KernelBase { apply1DKernelDirZ(const MACGrid &in, MACGrid &out, const Matrix &kernel) : KernelBase(&in, 0), in(in), out(out), kernel(kernel) { runMessage(); run(); } inline void op(int i, int j, int k, const MACGrid &in, MACGrid &out, const Matrix &kernel) const { int nz = in.getSizeZ(); int kn = kernel.n; int kCentre = kn / 2; for (int m = 0, ind = kn - 1, kk = k - kCentre; m < kn; m++, ind--, kk++) { if (kk < 0) continue; else if (kk >= nz) break; else out(i, j, k) += in(i, j, kk) * kernel(0, ind); } } inline const MACGrid &getArg0() { return in; } typedef MACGrid type0; inline MACGrid &getArg1() { return out; } typedef MACGrid type1; inline const Matrix &getArg2() { return kernel; } typedef Matrix type2; void runMessage() { debMsg("Executing kernel apply1DKernelDirZ ", 3); debMsg("Kernel range" << " x " << maxX << " y " << maxY << " z " << minZ << " - " << maxZ << " ", 4); }; void operator()(const tbb::blocked_range &__r) const { const int _maxX = maxX; const int _maxY = maxY; if (maxZ > 1) { for (int k = __r.begin(); k != (int)__r.end(); k++) for (int j = 0; j < _maxY; j++) for (int i = 0; i < _maxX; i++) op(i, j, k, in, out, kernel); } else { const int k = 0; for (int j = __r.begin(); j != (int)__r.end(); j++) for (int i = 0; i < _maxX; i++) op(i, j, k, in, out, kernel); } } void run() { if (maxZ > 1) tbb::parallel_for(tbb::blocked_range(minZ, maxZ), *this); else tbb::parallel_for(tbb::blocked_range(0, maxY), *this); } const MACGrid ∈ MACGrid &out; const Matrix &kernel; }; //! Apply separable Gaussian blur in 2D void applySeparableKernel2D(MACGrid &grid, const FlagGrid &flags, const Matrix &kernel) { // int nx = grid.getSizeX(), ny = grid.getSizeY(); // int kn = kernel.n; // int kCentre = kn / 2; FluidSolver *parent = grid.getParent(); MACGrid orig = MACGrid(parent); orig.copyFrom(grid); MACGrid gridX = MACGrid(parent); apply1DKernelDirX(grid, gridX, kernel); MACGrid gridXY = MACGrid(parent); apply1DKernelDirY(gridX, gridXY, kernel); grid.copyFrom(gridXY); FOR_IJK(grid) { if ((i > 0 && flags.isObstacle(i - 1, j, k)) || (j > 0 && flags.isObstacle(i, j - 1, k)) || flags.isObstacle(i, j, k)) { grid(i, j, k).x = orig(i, j, k).x; grid(i, j, k).y = orig(i, j, k).y; grid(i, j, k).z = orig(i, j, k).z; } } } //! Apply separable Gaussian blur in 3D void applySeparableKernel3D(MACGrid &grid, const FlagGrid &flags, const Matrix &kernel) { // int nx = grid.getSizeX(), ny = grid.getSizeY(), nz = grid.getSizeZ(); // int kn = kernel.n; // int kCentre = kn / 2; FluidSolver *parent = grid.getParent(); MACGrid orig = MACGrid(parent); orig.copyFrom(grid); MACGrid gridX = MACGrid(parent); apply1DKernelDirX(grid, gridX, kernel); MACGrid gridXY = MACGrid(parent); apply1DKernelDirY(gridX, gridXY, kernel); MACGrid gridXYZ = MACGrid(parent); apply1DKernelDirZ(gridXY, gridXYZ, kernel); grid.copyFrom(gridXYZ); FOR_IJK(grid) { if ((i > 0 && flags.isObstacle(i - 1, j, k)) || (j > 0 && flags.isObstacle(i, j - 1, k)) || (k > 0 && flags.isObstacle(i, j, k - 1)) || flags.isObstacle(i, j, k)) { grid(i, j, k).x = orig(i, j, k).x; grid(i, j, k).y = orig(i, j, k).y; grid(i, j, k).z = orig(i, j, k).z; } } } //! Apply separable Gaussian blur in 2D or 3D depending on input dimensions void applySeparableKernel(MACGrid &grid, const FlagGrid &flags, const Matrix &kernel) { if (!grid.is3D()) applySeparableKernel2D(grid, flags, kernel); else applySeparableKernel3D(grid, flags, kernel); } //! Compute r-norm for the stopping criterion Real getRNorm(const MACGrid &x, const MACGrid &z) { MACGrid r = MACGrid(x.getParent()); r.copyFrom(x); r.sub(z); return r.getMaxAbs(); } //! Compute s-norm for the stopping criterion Real getSNorm(const Real rho, const MACGrid &z, const MACGrid &z_prev) { MACGrid s = MACGrid(z_prev.getParent()); s.copyFrom(z_prev); s.sub(z); s.multConst(rho); return s.getMaxAbs(); } //! Compute primal eps for the stopping criterion Real getEpsPri(const Real eps_abs, const Real eps_rel, const MACGrid &x, const MACGrid &z) { Real max_norm = max(x.getMaxAbs(), z.getMaxAbs()); Real eps_pri = sqrt(x.is3D() ? 3.0 : 2.0) * eps_abs + eps_rel * max_norm; return eps_pri; } //! Compute dual eps for the stopping criterion Real getEpsDual(const Real eps_abs, const Real eps_rel, const MACGrid &y) { Real eps_dual = sqrt(y.is3D() ? 3.0 : 2.0) * eps_abs + eps_rel * y.getMaxAbs(); return eps_dual; } //! Create a spiral velocity field in 2D as a test scene (optionally in 3D) void getSpiralVelocity(const FlagGrid &flags, MACGrid &vel, Real strength = 1.0, bool with3D = false) { int nx = flags.getSizeX(), ny = flags.getSizeY(), nz = 1; if (with3D) nz = flags.getSizeZ(); Real midX = 0.5 * (Real)(nx - 1); Real midY = 0.5 * (Real)(ny - 1); for (int i = 0; i < nx; i++) { for (int j = 0; j < ny; j++) { for (int k = 0; k < nz; k++) { int idx = flags.index(i, j, k); Real diffX = midX - i; Real diffY = midY - j; Real hypotenuse = sqrt(diffX * diffX + diffY * diffY); if (hypotenuse > 0) { vel[idx].x = diffY / hypotenuse; vel[idx].y = -diffX / hypotenuse; } } } } vel.multConst(strength); } static PyObject *_W_0(PyObject *_self, PyObject *_linargs, PyObject *_kwds) { try { PbArgs _args(_linargs, _kwds); FluidSolver *parent = _args.obtainParent(); bool noTiming = _args.getOpt("notiming", -1, 0); pbPreparePlugin(parent, "getSpiralVelocity", !noTiming); PyObject *_retval = 0; { ArgLocker _lock; const FlagGrid &flags = *_args.getPtr("flags", 0, &_lock); MACGrid &vel = *_args.getPtr("vel", 1, &_lock); Real strength = _args.getOpt("strength", 2, 1.0, &_lock); bool with3D = _args.getOpt("with3D", 3, false, &_lock); _retval = getPyNone(); getSpiralVelocity(flags, vel, strength, with3D); _args.check(); } pbFinalizePlugin(parent, "getSpiralVelocity", !noTiming); return _retval; } catch (std::exception &e) { pbSetError("getSpiralVelocity", e.what()); return 0; } } static const Pb::Register _RP_getSpiralVelocity("", "getSpiralVelocity", _W_0); extern "C" { void PbRegister_getSpiralVelocity() { KEEP_UNUSED(_RP_getSpiralVelocity); } } //! Set the guiding weight W as a gradient in the y-direction void setGradientYWeight( Grid &W, const int minY, const int maxY, const Real valAtMin, const Real valAtMax) { FOR_IJK(W) { if (minY <= j && j <= maxY) { Real val = valAtMin; if (valAtMax != valAtMin) { Real ratio = (Real)(j - minY) / (Real)(maxY - minY); val = ratio * valAtMax + (1.0 - ratio) * valAtMin; } W(i, j, k) = val; } } } static PyObject *_W_1(PyObject *_self, PyObject *_linargs, PyObject *_kwds) { try { PbArgs _args(_linargs, _kwds); FluidSolver *parent = _args.obtainParent(); bool noTiming = _args.getOpt("notiming", -1, 0); pbPreparePlugin(parent, "setGradientYWeight", !noTiming); PyObject *_retval = 0; { ArgLocker _lock; Grid &W = *_args.getPtr>("W", 0, &_lock); const int minY = _args.get("minY", 1, &_lock); const int maxY = _args.get("maxY", 2, &_lock); const Real valAtMin = _args.get("valAtMin", 3, &_lock); const Real valAtMax = _args.get("valAtMax", 4, &_lock); _retval = getPyNone(); setGradientYWeight(W, minY, maxY, valAtMin, valAtMax); _args.check(); } pbFinalizePlugin(parent, "setGradientYWeight", !noTiming); return _retval; } catch (std::exception &e) { pbSetError("setGradientYWeight", e.what()); return 0; } } static const Pb::Register _RP_setGradientYWeight("", "setGradientYWeight", _W_1); extern "C" { void PbRegister_setGradientYWeight() { KEEP_UNUSED(_RP_setGradientYWeight); } } // ***************************************************************************** // More helper functions for fluid guiding //! Apply Gaussian blur (either 2D or 3D) in a separable way void applySeparableGaussianBlur(MACGrid &grid, const FlagGrid &flags, const Matrix &kernel1D) { assertMsg(gBlurPrecomputed, "Error - blue kernel not precomputed"); applySeparableKernel(grid, flags, kernel1D); } //! Precomputation performed before the first PD iteration void ADMM_precompute_Separable(int blurRadius) { if (gBlurPrecomputed) { assertMsg(gBlurKernelRadius == blurRadius, "More than a single blur radius not supported at the moment."); return; } int kernelSize = 2 * blurRadius + 1; gBlurKernel = get1DGaussianBlurKernel(kernelSize, kernelSize); gBlurPrecomputed = true; gBlurKernelRadius = blurRadius; } //! Apply approximate multiplication of inverse(M) void applyApproxInvM(MACGrid &v, const FlagGrid &flags, const MACGrid &invA) { MACGrid v_new = MACGrid(v.getParent()); v_new.copyFrom(v); v_new.mult(invA); applySeparableGaussianBlur(v_new, flags, gBlurKernel); applySeparableGaussianBlur(v_new, flags, gBlurKernel); v_new.multConst(2.0); v_new.mult(invA); v.mult(invA); v.sub(v_new); } //! Precompute Q, a reused quantity in the PD iterations //! Q = 2*G*G*(velT-velC)-sigma*velC void precomputeQ(MACGrid &Q, const FlagGrid &flags, const MACGrid &velT_region, const MACGrid &velC, const Matrix &gBlurKernel, const Real sigma) { Q.copyFrom(velT_region); Q.sub(velC); applySeparableGaussianBlur(Q, flags, gBlurKernel); applySeparableGaussianBlur(Q, flags, gBlurKernel); Q.multConst(2.0); Q.addScaled(velC, -sigma); } //! Precompute inverse(A), a reused quantity in the PD iterations //! A = 2*S^2 + p*I, invA = elementwise 1/A void precomputeInvA(MACGrid &invA, const Grid &weight, const Real sigma) { FOR_IJK(invA) { Real val = 2 * weight(i, j, k) * weight(i, j, k) + sigma; if (val < 0.01) val = 0.01; Real invVal = 1.0 / val; invA(i, j, k).x = invVal; invA(i, j, k).y = invVal; invA(i, j, k).z = invVal; } } //! proximal operator of f , guiding void prox_f(MACGrid &v, const FlagGrid &flags, const MACGrid &Q, const MACGrid &velC, const Real sigma, const MACGrid &invA) { v.multConst(sigma); v.add(Q); applyApproxInvM(v, flags, invA); v.add(velC); } // ***************************************************************************** // re-uses main pressure solve from pressure.cpp void solvePressure(MACGrid &vel, Grid &pressure, const FlagGrid &flags, Real cgAccuracy = 1e-3, const Grid *phi = 0, const Grid *perCellCorr = 0, const MACGrid *fractions = 0, const MACGrid *obvel = 0, Real gfClamp = 1e-04, Real cgMaxIterFac = 1.5, bool precondition = true, int preconditioner = 1, bool enforceCompatibility = false, bool useL2Norm = false, bool zeroPressureFixing = false, const Grid *curv = NULL, const Real surfTens = 0.0, Grid *retRhs = NULL); //! Main function for fluid guiding , includes "regular" pressure solve void PD_fluid_guiding(MACGrid &vel, MACGrid &velT, Grid &pressure, FlagGrid &flags, Grid &weight, int blurRadius = 5, Real theta = 1.0, Real tau = 1.0, Real sigma = 1.0, Real epsRel = 1e-3, Real epsAbs = 1e-3, int maxIters = 200, Grid *phi = 0, Grid *perCellCorr = 0, MACGrid *fractions = 0, MACGrid *obvel = 0, Real gfClamp = 1e-04, Real cgMaxIterFac = 1.5, Real cgAccuracy = 1e-3, int preconditioner = 1, bool zeroPressureFixing = false, const Grid *curv = NULL, const Real surfTens = 0.) { FluidSolver *parent = vel.getParent(); // initialize dual/slack variables MACGrid velC = MACGrid(parent); velC.copyFrom(vel); MACGrid x = MACGrid(parent); MACGrid y = MACGrid(parent); MACGrid z = MACGrid(parent); MACGrid x0 = MACGrid(parent); MACGrid z0 = MACGrid(parent); // precomputation ADMM_precompute_Separable(blurRadius); MACGrid Q = MACGrid(parent); precomputeQ(Q, flags, velT, velC, gBlurKernel, sigma); MACGrid invA = MACGrid(parent); precomputeInvA(invA, weight, sigma); // loop int iter = 0; for (iter = 0; iter < maxIters; iter++) { // x-update x0.copyFrom(x); x.multConst(1.0 / sigma); x.add(y); prox_f(x, flags, Q, velC, sigma, invA); x.multConst(-sigma); x.addScaled(y, sigma); x.add(x0); // z-update z0.copyFrom(z); z.addScaled(x, -tau); Real cgAccuracyAdaptive = cgAccuracy; solvePressure(z, pressure, flags, cgAccuracyAdaptive, phi, perCellCorr, fractions, obvel, gfClamp, cgMaxIterFac, true, preconditioner, false, false, zeroPressureFixing, curv, surfTens); // y-update y.copyFrom(z); y.sub(z0); y.multConst(theta); y.add(z); // stopping criterion bool stop = (iter > 0 && getRNorm(z, z0) < getEpsDual(epsAbs, epsRel, z)); if (stop || (iter == maxIters - 1)) break; } // vel_new = z vel.copyFrom(z); debMsg("PD_fluid_guiding iterations:" << iter, 1); } static PyObject *_W_2(PyObject *_self, PyObject *_linargs, PyObject *_kwds) { try { PbArgs _args(_linargs, _kwds); FluidSolver *parent = _args.obtainParent(); bool noTiming = _args.getOpt("notiming", -1, 0); pbPreparePlugin(parent, "PD_fluid_guiding", !noTiming); PyObject *_retval = 0; { ArgLocker _lock; MACGrid &vel = *_args.getPtr("vel", 0, &_lock); MACGrid &velT = *_args.getPtr("velT", 1, &_lock); Grid &pressure = *_args.getPtr>("pressure", 2, &_lock); FlagGrid &flags = *_args.getPtr("flags", 3, &_lock); Grid &weight = *_args.getPtr>("weight", 4, &_lock); int blurRadius = _args.getOpt("blurRadius", 5, 5, &_lock); Real theta = _args.getOpt("theta", 6, 1.0, &_lock); Real tau = _args.getOpt("tau", 7, 1.0, &_lock); Real sigma = _args.getOpt("sigma", 8, 1.0, &_lock); Real epsRel = _args.getOpt("epsRel", 9, 1e-3, &_lock); Real epsAbs = _args.getOpt("epsAbs", 10, 1e-3, &_lock); int maxIters = _args.getOpt("maxIters", 11, 200, &_lock); Grid *phi = _args.getPtrOpt>("phi", 12, 0, &_lock); Grid *perCellCorr = _args.getPtrOpt>("perCellCorr", 13, 0, &_lock); MACGrid *fractions = _args.getPtrOpt("fractions", 14, 0, &_lock); MACGrid *obvel = _args.getPtrOpt("obvel", 15, 0, &_lock); Real gfClamp = _args.getOpt("gfClamp", 16, 1e-04, &_lock); Real cgMaxIterFac = _args.getOpt("cgMaxIterFac", 17, 1.5, &_lock); Real cgAccuracy = _args.getOpt("cgAccuracy", 18, 1e-3, &_lock); int preconditioner = _args.getOpt("preconditioner", 19, 1, &_lock); bool zeroPressureFixing = _args.getOpt("zeroPressureFixing", 20, false, &_lock); const Grid *curv = _args.getPtrOpt>("curv", 21, NULL, &_lock); const Real surfTens = _args.getOpt("surfTens", 22, 0., &_lock); _retval = getPyNone(); PD_fluid_guiding(vel, velT, pressure, flags, weight, blurRadius, theta, tau, sigma, epsRel, epsAbs, maxIters, phi, perCellCorr, fractions, obvel, gfClamp, cgMaxIterFac, cgAccuracy, preconditioner, zeroPressureFixing, curv, surfTens); _args.check(); } pbFinalizePlugin(parent, "PD_fluid_guiding", !noTiming); return _retval; } catch (std::exception &e) { pbSetError("PD_fluid_guiding", e.what()); return 0; } } static const Pb::Register _RP_PD_fluid_guiding("", "PD_fluid_guiding", _W_2); extern "C" { void PbRegister_PD_fluid_guiding() { KEEP_UNUSED(_RP_PD_fluid_guiding); } } //! reset precomputation void releaseBlurPrecomp() { gBlurPrecomputed = false; gBlurKernelRadius = -1; gBlurKernel = 0.f; } static PyObject *_W_3(PyObject *_self, PyObject *_linargs, PyObject *_kwds) { try { PbArgs _args(_linargs, _kwds); FluidSolver *parent = _args.obtainParent(); bool noTiming = _args.getOpt("notiming", -1, 0); pbPreparePlugin(parent, "releaseBlurPrecomp", !noTiming); PyObject *_retval = 0; { ArgLocker _lock; _retval = getPyNone(); releaseBlurPrecomp(); _args.check(); } pbFinalizePlugin(parent, "releaseBlurPrecomp", !noTiming); return _retval; } catch (std::exception &e) { pbSetError("releaseBlurPrecomp", e.what()); return 0; } } static const Pb::Register _RP_releaseBlurPrecomp("", "releaseBlurPrecomp", _W_3); extern "C" { void PbRegister_releaseBlurPrecomp() { KEEP_UNUSED(_RP_releaseBlurPrecomp); } } } // namespace Manta