/*************************************************************************
*  Copyright (C) 2009 by Emanuele Catalano <catalano@grenoble-inp.fr>    *
*  Copyright (C) 2009 by Bruno Chareyre <bruno.chareyre@grenoble-inp.fr> *
*                                                                        *
*  This program is free software; it is licensed under the terms of the  *
*  GNU General Public License v2 or later. See file LICENSE for details. *
*************************************************************************/

/*!

FlowEngine is an interface between Yade and the fluid solvers defined in pkg/pfv and using the PFV scheme.
There are also a few non-trivial functions defined here, such has building triangulation and computating elements volumes.
The code strongly relies on CGAL library for building triangulations on the top of sphere packings.
CGAL's trangulations introduce the data associated to each element of the triangulation through template parameters (Cell_info and Vertex_info).
FlowEngine is thus defined via the template class TemplateFlowEngine(Cellinfo,VertexInfo), for easier addition of variables in various couplings.
PeriodicFlowEngine is a variant for periodic boundary conditions.

Which solver will be actually used internally to obtain pore pressure will depend partly on compile time flags (libcholmod available and #define LINSOLV),
and on runtime settings (useSolver=0: Gauss-Seidel (iterative), 0:Gauss-Seidel, 3: Cholesky factorization (via Eigen3 interface), 4:multicore CPU or GPU accelerated CHOLMOD (without Eigen3), 1-2: undefined).

The files defining lower level classes are in yade/lib. The code uses CGAL::Triangulation3 for managing the mesh and storing data. Eigen3::Sparse, suitesparse::cholmod, and metis are used for solving the linear systems with a direct method (Cholesky). An iterative method (Gauss-Seidel) is implemented directly in Yade and can be used as a fallback (see FlowEngine::useSolver).

Most classes in lib/triangulation are templates, and are therefore completely defined in header files.
A pseudo hpp/cpp split is reproduced for clarity, with hpp/ipp extensions (but again, in the end they are all include files).
The files are
- RegularTriangulation.h : declaration of info types embeded in the mesh (template parameters of CGAL::Triangulation3), and instanciation of RTriangulation classes
- Tesselation.h/ipp : encapsulate RegularTriangulation and adds functions to manipulate the dual (Voronoi) graph of the triangulation
- Network.hpp/ipp : specialized for PFV model (the two former are used independently by TesselationWrapper), a set of functions to determine volumes and surfaces of intersections between spheres and various subdomains. Contains two triangulations for smooth transitions while remeshing - e.g. interpolating values in the new mesh using the previous one.
- FlowBoundingSphere.hpp/ipp and PeriodicFlow.hpp/ipp + LinSolv variants: implement the solver in itself (mesh, boundary conditions, solving, defining fluid-particles interactions)
- FlowEngine.hpp/hpp.in/ipp.in (this file) define a generic flow engine, instantiations of which are FlowEngine and variants (FlowEngine.cpp, PeriodicFlowEngine.cpp,...)


*/

#pragma once
#include<core/PartialEngine.hpp>
#include<pkg/dem/TriaxialCompressionEngine.hpp>
// #include<pkg/dem/TesselationWrapper.hpp>
#include<pkg/pfv/FlowBoundingSphere.hpp>
#include<pkg/pfv//PeriodicFlow.hpp>
#ifdef PARTIALSAT
#include<pkg/pfv/PartialSatLinSolv.hpp>
#endif
#include<pkg/pfv/FlowEngine.hpp>
#include<pkg/common/Sphere.hpp>
#include<core/Clump.hpp>
#include<preprocessing/dem/Shop.hpp>

namespace yade { // Cannot have #include directive inside.

// This declaration leads to compile errors for multiple definitions,
// FlowEngine.cpp.in leads to several clones with same re-declaration (uncomment and see the error)
// Another problem is that not all these derived engines necessarily use useSolver with the same meaning so currently this enum is
// a bit overdesigned 

//#ifndef USE_FLOW_SOLVER_ENUM // Note: In case if later you wanted this enum back, this #ifndef will solve the multiple defition problem. Then also uncomment YADE_ENUM macro in FlowEngine.cpp
//#define USE_FLOW_SOLVER_ENUM	
// 0:Gauss-Seidel, 3: Cholesky factorization (via Eigen3 interface), 4:multicore CPU or GPU accelerated CHOLMOD (without Eigen3)
// enum /* class */ UseFlowSolver {
// 	// TODO: use `enum class` here instead, but need to replace `== 0` with `== UseFlowSolver::GaussSeidel` in all other places.
// 	//       extra benefit of YADE_ENUM: trying to assign 1 or 2 will throw an exception :)
// 	GaussSeidel           = 0,
// 	CholeskyFactorization = 3,
// 	MulticoreCPUorGPU     = 4
// };
//#endif

template<class _CellInfo, class _VertexInfo, class _Tesselation=CGT::_Tesselation<CGT::TriangulationTypes<_VertexInfo,_CellInfo> >, class solverT=DEFAULTSOLVER >
class TemplateFlowEngine_@TEMPLATE_FLOW_NAME@ : public PartialEngine
{
	public :
		typedef solverT									FlowSolver;
		typedef FlowSolver								Solver;//FIXME: useless alias, search/replace then remove
		typedef typename FlowSolver::Tesselation					Tesselation;
		typedef typename FlowSolver::RTriangulation					RTriangulation;
		typedef typename FlowSolver::FiniteVerticesIterator                  	  	FiniteVerticesIterator;
		typedef typename FlowSolver::FiniteCellsIterator				FiniteCellsIterator;
		typedef typename FlowSolver::FiniteFacetsIterator				FiniteFacetsIterator;
		typedef typename FlowSolver::Facet					Facet;
		typedef typename FlowSolver::CellHandle						CellHandle;
		typedef typename FlowSolver::FiniteEdgesIterator				FiniteEdgesIterator;
		typedef typename FlowSolver::VertexHandle                    			VertexHandle;
		typedef typename RTriangulation::Triangulation_data_structure::Cell::Info       CellInfo;
		typedef typename RTriangulation::Triangulation_data_structure::Vertex::Info     VertexInfo;
		typedef typename FlowSolver::AlphaShape											AlphaShape;
		//typedef typename FlowSolver::Alpha_iterator										Alpha_iterator;

// 	protected:
		shared_ptr<FlowSolver> solver;
		shared_ptr<FlowSolver> backgroundSolver;
		volatile bool backgroundCompleted;
		Cell cachedCell;
		struct posData {Body::id_t id; Vector3r pos; Real radius; bool isSphere; bool isClump; bool exists; posData(){exists=0; isClump=0;}};
		vector<posData> positionBufferCurrent;//reflect last known positions before we start computations
		vector<posData> positionBufferParallel;//keep the positions from a given step for multithread factorization
		//copy positions in a buffer for faster and/or parallel access
		virtual void setPositionsBuffer(bool current);
		virtual void addAlphaToPositionsBuffer(bool current);
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wpragmas"
#pragma GCC diagnostic ignored "-Woverloaded-virtual"
		virtual void trickPermeability(Solver* /*flow*/) {};
#pragma GCC diagnostic pop

	public :
		int retriangulationLastIter;
		enum {wall_xmin, wall_xmax, wall_ymin, wall_ymax, wall_zmin, wall_zmax};
		Vector3r normal [6];
		bool fluxChanged;  // flag that keeps multithreading working when a flux is imposed
		bool metisForced;
		Real epsVolCumulative;
		int ReTrg;
		int ellapsedIter;
		int permUpdateIters;
		virtual void initSolver (FlowSolver& flow);
		#ifdef LINSOLV
		void setForceMetis (bool force);
		bool getForceMetis () const;
		void updateLinearSystem (Solver& flow);
		void pyResetLinearSystem() { solver->resetLinearSystem(); }
		#endif
		void triangulate (Solver& flow);
		void addBoundary (Solver& flow);
		virtual void buildTriangulation (Real pZero, Solver& flow);
		void buildTriangulation (Solver& flow);
		void updateVolumes (Solver& flow);
		void applyForces (Solver& flow);
		void initializeVolumes (Solver& flow);
		void boundaryConditions(Solver& flow);
		void thermalBoundaryConditions(Solver& flow);
		void remeshForFreshlyBrokenBonds ();
		void pyInitializeVolumes () {if (solver) initializeVolumes(*solver); else LOG_WARN("Solver not initialized");}
		void pyUpdateVolumes () {if (solver) updateVolumes(*solver); else LOG_WARN("Solver not initialized");}

		void updateBCs () {
			if (solver->tesselation().maxId>0) boundaryConditions(*solver);//avoids crash at iteration 0, when the packing is not bounded yet
			else LOG_ERROR("updateBCs not applied");
			solver->pressureChanged=true;}

		bool checkMaxId(unsigned id) const {//used in other functions
			if (id>=solver->T[solver->currentTes].cellHandles.size()) {
				LOG_ERROR("id out of range, max value is "<<solver->T[solver->currentTes].cellHandles.size());
				return false;}
			return true;}

		boost::python::list getIncidentCells(unsigned vertexId) const {
			RTriangulation& Tri = solver->tesselation().Triangulation();
			unsigned& id = vertexId;
			if (vertexId >= solver->tesselation().vertexHandles.size()) LOG_ERROR("vertexId exceeds number of vertices");
			boost::python::list incidentCells;
			vector<CellHandle> tmpCells;
			tmpCells.resize(10000);
			typename FlowSolver::VCellIterator cells_it = tmpCells.begin();
			typename FlowSolver::VCellIterator cells_end = Tri.incident_cells(solver->tesselation().vertexHandles[id],cells_it);
			for (typename FlowSolver::VCellIterator it = tmpCells.begin(); it != cells_end; it++) incidentCells.append((*it)->info().id);
			return incidentCells;
		}


		void imposeFlux(Vector3r pos, Real flux);
		unsigned int imposePressure(Vector3r pos, Real p);
		unsigned int imposePressureFromId(unsigned long id, Real p);
		void setImposedPressure(unsigned int cond, Real p);
		void clearImposedPressure();
		void clearImposedFlux();
		unsigned int imposeCavity(Vector3r pos);
		void computeViscousForces(Solver& flow);
		Real getCellFlux(unsigned int cond) const;
		Real getCellFluxFromId(unsigned long id) const;
		std::vector<Real> getCellVelocity(Vector3r pos) const {return solver->getCellVelocity(pos[0], pos[1], pos[2]);}
		Real getCellVolume(Vector3r pos) const {return solver->getCellVolume(pos[0], pos[1], pos[2]);}
		Real getBoundaryFlux(unsigned int boundary) const {return solver->boundaryFlux(boundary);}
		Real getBoundaryVolume(unsigned int boundary, Real dt2) const {return -1.0*solver->boundaryFlux(boundary)*dt2;}
		Real getCellInVolumeFromId(unsigned long id, Real dt2) const {return -1.0*getCellFluxFromId(id)*dt2;}
		Real getBoundaryArea(unsigned int boundary) const {return solver->boundaryArea(boundary);}
		std::vector<std::vector<Real>> getBoundaryVel(unsigned int boundary) const {return solver->boundaryVel(boundary);}
		Vector3r fluidForce(unsigned int idSph) const { const CGT::CVector& f=solver->lastSolution().vertex(idSph)->info().forces; return Vector3r(f[0],f[1],f[2]);}
		Vector3r averageVelocity();

		Vector3r shearLubForce(unsigned int id_sph) const {
			return (solver->shearLubricationForces.size()>id_sph)?solver->shearLubricationForces[id_sph]:Vector3r::Zero();}
		Vector3r shearLubTorque(unsigned int id_sph) const {
			return (solver->shearLubricationTorques.size()>id_sph)?solver->shearLubricationTorques[id_sph]:Vector3r::Zero();}
		Vector3r pumpLubTorque(unsigned int id_sph) const {
			return (solver->pumpLubricationTorques.size()>id_sph)?solver->pumpLubricationTorques[id_sph]:Vector3r::Zero();}
		Vector3r twistLubTorque(unsigned int id_sph) const {
			return (solver->twistLubricationTorques.size()>id_sph)?solver->twistLubricationTorques[id_sph]:Vector3r::Zero();}
		Vector3r normalLubForce(unsigned int id_sph) const {
			return (solver->normalLubricationForce.size()>id_sph)?solver->normalLubricationForce[id_sph]:Vector3r::Zero();}
		Matrix3r bodyShearLubStress(unsigned int id_sph) const {
			return (solver->shearLubricationBodyStress.size()>id_sph)?solver->shearLubricationBodyStress[id_sph]:Matrix3r::Zero();}
		Matrix3r bodyNormalLubStress(unsigned int id_sph) const {
			return (solver->normalLubricationBodyStress.size()>id_sph)?solver->normalLubricationBodyStress[id_sph]:Matrix3r::Zero();}
		Vector3r shearVelocity(unsigned int interaction) const {
			return (solver->deltaShearVel[interaction]);}
		Vector3r normalVelocity(unsigned int interaction) const {
			return (solver->deltaNormVel[interaction]);}
		Matrix3r normalStressInteraction(unsigned int interaction) const {
			return (solver->normalStressInteraction[interaction]);}
		Matrix3r shearStressInteraction(unsigned int interaction) const {
			return (solver->shearStressInteraction[interaction]);}
		Vector3r normalVect(unsigned int interaction) const {
			return (solver->normalV[interaction]);}
		Real surfaceDistanceParticle(unsigned int interaction) {
			return (solver->surfaceDistance[interaction]);}
		Real edgeSize() const {
			return (solver->edgeIds.size());}
		Real getEquivalentCompressibility() const {return (solver->equivalentCompressibility);}
		Real getCavityDensity() const {return (solver->cavityFluidDensity);}
		void printVertices() {solver->printVertices();}
		Real OSI() {
			return (solver->onlySpheresInteractions.size());}
		int onlySpheresInteractions(unsigned int interaction) {
			return (solver->onlySpheresInteractions[interaction]);}
		boost::python::list getConstrictions(bool all) const {
			vector<Real> csd=solver->getConstrictions(); boost::python::list pycsd;
			for (unsigned int k=0;k<csd.size();k++) if ((all && csd[k]!=0) || csd[k]>0) pycsd.append(csd[k]); return pycsd;}
		boost::python::list getConstrictionsFull(bool all) const {
			vector<Constriction> csd=solver->getConstrictionsFull(); boost::python::list pycsd;
			for (unsigned int k=0;k<csd.size();k++) if ((all && csd[k].second[0]!=0) || csd[k].second[0]>0) {
				boost::python::list cons;
				cons.append(csd[k].first.first);
				cons.append(csd[k].first.second);
				cons.append(csd[k].second[0]);
				cons.append(csd[k].second[1]);
				cons.append(csd[k].second[2]);
				cons.append(csd[k].second[3]);
				pycsd.append(cons);}
			return pycsd;}
		boost::python::list getNeighbors(unsigned int id) const { // Temporary function to allow for simulations in Python, can be easily accessed in c++
		        boost::python::list ids2;
		        const RTriangulation& Tri = solver->tesselation().Triangulation();
		        if (id>=solver->tesselation().cellHandles.size()) {LOG_ERROR("id out of range, max value is "<<solver->T[solver->currentTes].cellHandles.size()); return ids2;}
		        for (unsigned int i=0;i<4;i++) {
			        const CellHandle& neighbourCell = solver->tesselation().cellHandles[id]->neighbor(i);
			        if (!Tri.is_infinite(neighbourCell)) ids2.append(neighbourCell->info().id);}
		        return ids2;}

		template<class Cellhandle>
		Real volumeCellSingleFictious (Cellhandle cell);
		template<class Cellhandle>
		Real volumeCellDoubleFictious (Cellhandle cell);
		template<class Cellhandle>
		Real volumeCellTripleFictious (Cellhandle cell);
		template<class Cellhandle>
		Real volumeCell (Cellhandle cell);
		Real surfaceSolidThroatInPore(int cellId, int throatIndex) const {return solver->surfaceSolidThroatInPore(solver->T[solver->currentTes].cellHandles[cellId], throatIndex, false, false);}
		template<class Cellhandle>
		Vector3r cellBarycenterFromHandle (Cellhandle cell) const {return makeVector3r(solver->cellBarycenter(cell));}
		Vector3r cellBarycenterFromId (unsigned long id) {
			if (not checkMaxId(id)) return Vector3r(0,0,0);
			return cellBarycenterFromHandle(solver->T[solver->currentTes].cellHandles[id]);}
		void Oedometer_Boundary_Conditions();
		void averageRealCellVelocity();
		bool isCellNeighbor(unsigned int cell1, unsigned int cell2);
		void saveVtk(const char* folder, bool withBoundaries) {solver->saveVtk(folder,withBoundaries);}
		vector<Real> avFlVelOnSph(unsigned int idSph) const {return solver->averageFluidVelocityOnSphere(idSph);}

// 		void setBoundaryVel(Vector3r vel) {topBoundaryVelocity=vel; updateTriangulation=true;}
		void pressureProfile(Real wallUpY, Real wallDownY) {return solver->measurePressureProfile(wallUpY,wallDownY);}
		Real getPorePressure(Vector3r pos) const {return solver->getPorePressure(pos[0], pos[1], pos[2]);}
		Real getPoreTemperature(Vector3r pos) const {return solver->getPoreTemperature(pos[0], pos[1], pos[2]);}
		int getCell(Real posX, Real posY, Real posZ) const {return solver->getCell(posX, posY, posZ);}
		Real getCavityFlux() const {return solver->getCavityFlux();}
		unsigned int nCells() const {return solver->T[solver->currentTes].cellHandles.size();}
		CELL_VECTOR_GETTER(,cellCenter)
		CELL_VECTOR_GETTER(.averageCellVelocity,cellVelocity)
		CELL_SCALAR_GETTER(Real,.p(),cellPressure)
		CELL_SCALAR_GETTER(Real,.temp(),cellTemperature)
		CELL_SCALAR_SETTER_EXTRA(double,.p(),setCellPressure,solver->resetRHS())
		CELL_SCALAR_SETTER(double,.temp(),setCellTemperature)
		CELL_SCALAR_GETTER(Real,.invVoidV,invInitVoidVolume)
		CELL_SCALAR_GETTER(bool,.Pcondition,cellPImposed)
		CELL_SCALAR_GETTER(bool,.Tcondition,cellTImposed)
		CELL_SCALAR_SETTER_EXTRA(bool,.Pcondition,setCellPImposed,solver->resetLinearSystem())
		CELL_SCALAR_SETTER(bool,.Tcondition,setCellTImposed)
		boost::python::list getVertices(unsigned int id) const {
			boost::python::list ids2;
			if (not checkMaxId(id)) return ids2;
			for (unsigned int i=0;i<4;i++) ids2.append(solver->T[solver->currentTes].cellHandles[id]->vertex(i)->info().id());
			return ids2;
		}
		void blockCell(unsigned int id, bool blockPressure){
			if (not checkMaxId(id)) return;
			solver->T[solver->currentTes].cellHandles[id]->info().blocked=true;
			solver->T[solver->currentTes].cellHandles[id]->info().Pcondition=blockPressure;
		}
		Real getDiffusionCoeff(unsigned cellId, unsigned throat) const {
			if (not checkMaxId(cellId))  return 0;
			const CellHandle& cell = solver->tesselation().cellHandles[cellId];
			Real length = 	sqrt (  (cell->info()
					- cell->neighbor(throat)->info()).squared_length() );
			return cell->info().fluidArea(throat) / length;
		}
		Real getConductivity(unsigned cell1, unsigned facetIdx) const {
			const CellHandle& pore1 = solver->tesselation().cellHandles[cell1];
			return pore1->info().kNorm()[facetIdx];
		}

		Vector3r getBoundaryNormal(int index) const {
		  if (index<0 || index>5) LOG_ERROR("index out of range (0-5)"); return normal[math::max(0,math::min(5,index))];}
		void setBoundaryNormal(int index, Vector3r v) {
		  if (index<0 || index>5) LOG_ERROR("index out of range (0-5)"); normal[math::max(0,math::min(5,index))]=v;}

		Real averageSlicePressure(Real posY) const {return solver->averageSlicePressure(posY);}
		Real averagePressure() const {return solver->averagePressure();}

		virtual void emulateAction(){
			scene = Omega::instance().getScene().get();
			action();}
		#ifdef LINSOLV
		void	exportMatrix(string filename) {if (useSolver==3) solver->exportMatrix(filename.c_str());
				else cerr<<"available for Cholmod solver (useSolver==3)"<<endl;}
		void	exportTriplets(string filename) {if (useSolver==3) solver->exportTriplets(filename.c_str());
				else cerr<<"available for Cholmod solver (useSolver==3)"<<endl;}
		void	cholmodStats() {
					cerr << cholmod_print_common((char*)string("PFV Cholmod factorization").c_str(),&(solver->eSolver.cholmod()))<<endl;
					cerr << "cholmod method:" << solver->eSolver.cholmod().selected<<endl;
					cerr << "METIS called:"<<solver->eSolver.cholmod().called_nd<<endl;}
		bool	metisUsed() const {return bool(solver->eSolver.cholmod().called_nd);}
		#endif

		virtual ~TemplateFlowEngine_@TEMPLATE_FLOW_NAME@();
		void action() override;
		virtual void backgroundAction();

		//commodities
		void compTessVolumes() {
			solver->T[solver->currentTes].compute();
			solver->T[solver->currentTes].computeVolumes();
		}
		Real getVolume (Body::id_t id) {
			if (solver->T[solver->currentTes].Max_id() <= 0) {emulateAction(); /*LOG_WARN("Not triangulated yet, emulating action");*/}
			if (solver->T[solver->currentTes].Volume(id) == -1) {compTessVolumes();/* LOG_WARN("Computing all volumes now, as you did not request it explicitely.");*/}
			return (solver->T[solver->currentTes].Max_id() >= id) ? solver->T[solver->currentTes].Volume(id) : -1;}

		YADE_CLASS_PYCLASS_BASE_DOC_ATTRS_DEPREC_INIT_CTOR_PY(TemplateFlowEngine_@TEMPLATE_FLOW_NAME@,@TEMPLATE_FLOW_NAME@,PartialEngine,"A generic engine from wich more specialized engines can inherit. It is defined for the sole purpose of inserting the right data classes CellInfo and VertexInfo in the triangulation, and it should not be used directly. Instead, look for specialized engines, e.g. :yref:`FlowEngine`, :yref:`PeriodicFlowEngine`, or :yref:`DFNFlowEngine`.",
		((bool,isActivated,true,,"Activates Flow Engine"))
		((bool,first,true,,"Controls the initialization/update phases"))
		((int,idOffset,0,,"If the bounding walls of the fluid mesh are not walls of the scene (i.e. are not elements of O.bodies), the offset should be set equal to the size of O.bodies. If the bounding walls are bodies of the scene but are not numbered as 0-5 then offset should be the number of bodies comming before the walls. Set offset<0 to get it set equal to O.bodies.size(), it will also update :yref:`FlowEngine::wallIds`."))
		((bool,convertClumps,true,,"If true the clumps will be temptatively converted into equivalent spheres in the triangulation, and clump members are skipped. Else clumps are ignored and spherical clump members are triangulated as independent bodies."))
		((bool,doInterpolate,false,,"Force the interpolation of cell's info while remeshing. By default, interpolation would be done only for compressible fluids. It can be forced with this flag."))
		((Real, fluidBulkModulus, 0.,,"Bulk modulus of fluid (inverse of compressibility) K=-dP*V/dV [Pa]. Flow is compressible if fluidBulkModulus > 0, else incompressible."))
		((Real, fluidRho, 1000.,,"Density of fluid (for thermalEngine)."))
		((Real, fluidCp, 4181.3,,"Heat capacity of fluid (for thermalEngine)."))
		((Real, dt, 0,,"timestep [s]"))
		((bool,permeabilityMap,false,,"Enable/disable stocking of average permeability scalar in cell infos."))
		((bool, slipBoundary, true,, "Controls friction condition on lateral walls"))
		((bool,waveAction, false,, "Allow sinusoidal pressure condition to simulate ocean waves"))
		((Real, sineMagnitude, 0,, "Pressure value (amplitude) when sinusoidal pressure is applied (p )"))
		((Real, sineAverage, 0,,"Pressure value (average) when sinusoidal pressure is applied"))
		((bool, debug, false,,"Activate debug messages"))
		((Real, wallThickness,0,,"Walls thickness"))
		((Real,pZero,0,,"The value used for initializing pore pressure. It is useless for incompressible fluid, but important for compressible model."))
		((Real,tZero,0,,"The value used for initializing pore temperatures in thermalEngine."))
		((Real,tolerance,1e-06,,"Gauss-Seidel tolerance"))
		((Real,relax,1.9,,"Gauss-Seidel relaxation"))
		((bool, updateTriangulation, 0,,"If true the medium is retriangulated. Can be switched on to force retriangulation after some events (else it will be true periodicaly based on :yref:`FlowEngine::defTolerance` and :yref:`FlowEngine::meshUpdateInterval`. Of course, it costs CPU time. Note that the new triangulation will start to be effectively used only after one iteration (i.e. O.run(2) gives a result with the new one, O.run(1) does not)."))
		((int,meshUpdateInterval,1000,,"Maximum number of timesteps between re-triangulation events (a negative value will never re-triangulate). See also :yref:`FlowEngine::defTolerance`."))
		((int,breakControlledRemesh,0,,"If true, remesh will occur everytime a break occurs in JCFpmPhys. Designed to increase accuracy and efficiency in hydraulic fracture simulations."))
		((Real, epsVolMax, 0,(Attr::readonly),"Maximal absolute volumetric strain computed at each iteration. |yupdate|"))
		((Real, defTolerance,0,,"Cumulated deformation threshold for which retriangulation of pore space is performed. If negative, the triangulation update will occure with a fixed frequency on the basis of :yref:`FlowEngine::meshUpdateInterval`"))
		((Real, porosity, 0,(Attr::readonly),"Porosity computed at each retriangulation |yupdate|"))
		((bool,meanKStat,false,,"report the local permeabilities' correction"))
		((bool,clampKValues,true,,"If true, clamp local permeabilities in [minKdivKmean,maxKdivKmean]*globalK. This clamping can avoid singular values in the permeability matrix and may reduce numerical errors in the solve phase. It will also hide junk values if they exist, or bias all values in very heterogeneous problems. So, use this with care."))
		((Real,minKdivKmean,0.0001,,"define the min K value (see :yref:`FlowEngine::clampKValues`)"))
		((Real,maxKdivKmean,100,,"define the max K value (see :yref:`FlowEngine::clampKValues`)"))
		((Real,permeabilityFactor,1.0,,"Permability multiplier ($m$): $m=1$ (default) attempts to predicty the actual hydraulic conductivity using a Poiseuille equation; $m>0$ multiplies the default values by $m$; $m<0$ defines the conductivity independently of particle size and viscosity as if the material was a homogeneous continuum of conductivity $-m$"))
		((Real,cavityFactor,1.,,"Permeability/viscosity for cavity cell neighbors (arbitrarily high to model triangulated fluid filled cavity)."))
		((Real,volumeFactor,1.,,"Factor used for simulating low porosity (for thermal considerations only) in high porosity DEM packings."))
		((Real,flatThreshold,-1,,"If >=0, pore volumes below flatThreshold value are blocked from flow calc. Useful for compressible flow involving odd triangulations with some very flat pores."))
		((Real,viscosity,1.0,,"viscosity of the fluid"))
		((bool,tempDependentViscosity,false,,"boolean to vary viscosity (ultimately cell permeability) with cell temperature. Linear model for viscosity b/w 20-70 degC. If true, kFactor must also be set negative, and becomes the darcy permeability."))
		((Real,desiredPorosity,0,,"Correct the cell volumes to reflect this desired porosity (not active by default (0))."))
		((Real,volumeCorrection,1,,"Volume correction factor (not user controlled. auto computed if :yref:`FlowEngine::desiredPorosity` != 0)"))
		((Real,stiffness, 10000,,"equivalent contact stiffness used in the lubrication model"))
		((unsigned, useSolver, 3,, "Solver to use. 0:Gauss-Seidel, 3: Cholesky factorization (via Eigen3 interface), 4:multicore CPU or GPU accelerated CHOLMOD (without Eigen3), 1-2: undefined."))
		((int, xmin,0,(Attr::readonly),"Index of the boundary $x_{min}$. This index is not equal the the id of the corresponding body in general, it may be used to access the corresponding attributes (e.g. flow.bndCondValue[flow.xmin], flow.wallId[flow.xmin],...)."))
		((int, xmax,1,(Attr::readonly),"See :yref:`FlowEngine::xmin`."))
		((int, ymin,2,(Attr::readonly),"See :yref:`FlowEngine::xmin`."))
		((int, ymax,3,(Attr::readonly),"See :yref:`FlowEngine::xmin`."))
		((int, zmin,4,(Attr::readonly),"See :yref:`FlowEngine::xmin`."))
		((int, zmax,5,(Attr::readonly),"See :yref:`FlowEngine::xmin`."))

		((vector<bool>, bndCondIsPressure, vector<bool>(6,false),,"defines the type of boundary condition for each side. True if pressure is imposed, False for no-flux. Indexes can be retrieved with :yref:`FlowEngine::xmin` and friends."))
		((vector<bool>, bndCondIsTemperature, vector<bool>(6,false),,"defines the type of boundary condition for each side of triangulation (used with :yref:`ThermalEngine` only). True if temperature is imposed, False for no heat-flux. Indexes can be retrieved with :yref:`FlowEngine::xmin` and friends."))
		((vector<Real>, bndCondValue, vector<Real>(6,0),,"Imposed value of a boundary condition. Only applies if the boundary condition is imposed pressure, else the imposed flux is always zero presently (may be generalized to non-zero imposed fluxes in the future)."))
		((vector<Real>, thermalBndCondValue, vector<Real>(6,0),,"Imposed temperature boundary condition value for the cells in the triangulation (used with :yref:`ThermalEngine` only)."))
		((vector<Vector3r>, boundaryVelocity, vector<Vector3r>(6,Vector3r::Zero()),, "velocity on top boundary, only change it using :yref:`FlowEngine::setBoundaryVel`"))
		((int, ignoredBody,-1,,"DEPRECATED, USE MASK - Id of a sphere to exclude from the triangulation.)"))
		((int,mask,0,,"If mask defined, only bodies with corresponding groupMask will be affected by this engine. If 0, all bodies will be affected."))
		((vector<int>, wallIds,vector<int>(6),,"body ids of the boundaries (default values are ok only if aabbWalls are appended before spheres, i.e. numbered 0,...,5)"))
		((vector<bool>, boundaryUseMaxMin, vector<bool>(6,true),,"If true (default value) bounding sphere is added as function of max/min sphere coord, if false as function of yade wall position"))
		((bool, viscousShear, false,,"compute viscous shear terms as developped by Donia Marzougui (FIXME: ref.)"))
		((bool, shearLubrication, false,,"compute shear lubrication force as developped by Brule (FIXME: ref.) "))
		((bool, pumpTorque, false,,"Compute pump torque applied on particles "))
		((bool, twistTorque, false,,"Compute twist torque applied on particles "))
		((Real, eps, 0.00001,,"roughness defined as a fraction of particles size, giving the minimum distance between particles in the lubrication model."))
		((bool, pressureForce, true,,"compute the pressure field and associated fluid forces. WARNING: turning off means fluid flow is not computed at all."))
		((bool, normalLubrication, false,,"compute normal lubrication force as developped by Brule"))
		((bool, viscousNormalBodyStress, false,,"compute normal viscous stress applied on each body"))
		((bool, viscousShearBodyStress, false,,"compute shear viscous stress applied on each body"))
		((bool, multithread, false,,"Build triangulation and factorize in the background (multi-thread mode)"))
		((bool, decoupleForces, false,,"If true, viscous and pressure forces are not imposed on particles. Useful for speeding up simulations in ultra-stiff cohesive materials."))
		((bool, getCHOLMODPerfTimings, false,,"Print CHOLMOD build, analyze, and factorize timings"))
		#ifdef LINSOLV
		((int, numSolveThreads, 1,,"number of openblas threads in the solve phase."))
		((int, numFactorizeThreads, 1,,"number of openblas threads in the factorization phase"))
		#endif
		((vector<Real>, boundaryPressure,vector<Real>(),,"values defining pressure along x-axis for the top surface. See also :yref:`FlowEngine::boundaryXPos`"))
		((vector<Real>, boundaryXPos,vector<Real>(),,"values of the x-coordinate for which pressure is defined. See also :yref:`FlowEngine::boundaryPressure`"))
		((string,blockHook,"",,"Python command to be run when remeshing. Anticipated usage: define blocked cells (see also :yref:`FlowEngine.blockCell`), or apply exotic types of boundary conditions which need to visit the newly built mesh"))
		((int, fixTriUpdatePermInt, -1,,"If positive, triangulation is fixed and DFNFlow trickPermeability is run according to the interval. Activating this automatically sets :yref:`FlowEngine::meshUpdateInt` and :yref:`FlowEngine::defTolerance` to -1 (never retriangulate)."))
		((bool, thermalEngine, false,,"activate thermalEngine within FlowEngine."))
		((bool, iniVoidVolumes, false,,"activate the computation of the inverse of the initial void volumes in each cell when pore volumes are initialized."))
		((bool, controlCavityPressure, false,,"use full cavity flux and fluidbulkmodulus to control cavity as dynamic pressure (dirichlet) boundary condition."))
		((bool, controlCavityVolumeChange, false,,"cavity imposes a volume change on neighbor cells (shouldnt be used with controlCavityPressure)"))
		((Real, phiZero, 0,,"if >0, considers water aircontent impact on fluid compressibility."))
		((Real, cavityFlux, 0,,"For adding flux to pressuremanaged cavity model (:yref:`FlowEngine::controlCavityPressure`). Negavite influx, postive outflux."))
		((Real, cavityFluidDensity, 0,,">0 means cavity compressibility model considers density changes instead of volume changes."))
		((Real, thermalPorosity, 0,,">0 means the void volume space will be factored by thermalPorosity for pore internal energy considerations."))
		((bool, averageCavityPressure, false,,"true means the pressure in the cavity will be averaged each iteration."))
		((Real, alphaBound,-1,,"if 0, use an alphaBoundary condition where CGAL finds minimum alpha necessary for a single solid object. Any positive value will be used for the alpha. All negative values deactivate the functionality."))
		((Real, alphaBoundValue, 0,,"value of alpha constant pressure condition"))
		((bool, fixedAlpha, false,,"If true, a constant-sized alpha vertex will be placed exactly above each facet. If false, logic is used to improve cell sizes in concave regions."))
		((Real, minimumPorosity, 0,,"value used to limit the allowable minimum porosity for pore volume calculations. Particularly useful if very small volumes are impacting stability"))
		,
		/*deprec*/
		((meanK_opt,clampKValues,"the name changed"))
		,,
		timingDeltas=shared_ptr<TimingDeltas>(new TimingDeltas);
		for (int i=0; i<6; ++i){normal[i]=Vector3r::Zero(); wallIds[i]=i;}
		normal[wall_ymin].y()=normal[wall_xmin].x()=normal[wall_zmin].z()=1;
		normal[wall_ymax].y()=normal[wall_xmax].x()=normal[wall_zmax].z()=-1;
		solver = shared_ptr<FlowSolver> (new FlowSolver);
		first=true;
		epsVolMax=epsVolCumulative=retriangulationLastIter=permUpdateIters=0;
		ReTrg=1;
		backgroundCompleted=true;
		ellapsedIter=0;
		metisForced=false;
		,
		.def("initializeVolumes",&TemplateFlowEngine_@TEMPLATE_FLOW_NAME@::pyInitializeVolumes,"initialize pore volumes.")
		.def("updateVolumes",&TemplateFlowEngine_@TEMPLATE_FLOW_NAME@::pyUpdateVolumes,"update rates of volume change")
		.def("imposeFlux",&TemplateFlowEngine_@TEMPLATE_FLOW_NAME@::imposeFlux,(boost::python::arg("pos"),boost::python::arg("p")),"Impose a flux in cell located  at 'pos' (i.e. add a source term in the flow problem). Outflux positive, influx negative.")
		.def("imposePressureFromId",&TemplateFlowEngine_@TEMPLATE_FLOW_NAME@::imposePressureFromId,(boost::python::arg("id"),boost::python::arg("p")),"Impose pressure in cell of index 'id' (after remeshing the same condition will apply for the same location, regardless of what the new cell index is at this location). The index of the condition itself is returned (for multiple imposed pressures at different points).")
		.def("imposePressure",&TemplateFlowEngine_@TEMPLATE_FLOW_NAME@::imposePressure,(boost::python::arg("pos"),boost::python::arg("p")),"Impose pressure in cell of location 'pos'. The index of the condition is returned (for multiple imposed pressures at different points).")
		.def("imposeCavity",&TemplateFlowEngine_@TEMPLATE_FLOW_NAME@::imposeCavity,(boost::python::arg("pos")),"Cell with location 'pos' participates in a cavity (high conductivity and no volume factoring). The index of the condition is returned (for multiple imposed pressures at different points).")
		.def("setImposedPressure",&TemplateFlowEngine_@TEMPLATE_FLOW_NAME@::setImposedPressure,(boost::python::arg("cond"),boost::python::arg("p")),"Set pressure value at the point indexed 'cond'.")
		.def("clearImposedPressure",&TemplateFlowEngine_@TEMPLATE_FLOW_NAME@::clearImposedPressure,"Clear the list of points with pressure imposed.")
		.def("clearImposedFlux",&TemplateFlowEngine_@TEMPLATE_FLOW_NAME@::clearImposedFlux,"Clear the list of points with flux imposed.")
		.def("getCellFlux",&TemplateFlowEngine_@TEMPLATE_FLOW_NAME@::getCellFlux,(boost::python::arg("cond")),"Get influx in cell associated to an imposed P (indexed using 'cond').")
		.def("getCellFluxFromId",&TemplateFlowEngine_@TEMPLATE_FLOW_NAME@::getCellFluxFromId,(boost::python::arg("id")),"Get influx in cell.")
		.def("getBoundaryFlux",&TemplateFlowEngine_@TEMPLATE_FLOW_NAME@::getBoundaryFlux,(boost::python::arg("boundary")),"Get total flux through boundary defined by its body id.\n\n.. note:: The flux may be not zero even for no-flow condition. This artifact comes from cells which are incident to two or more boundaries (along the edges of the sample, typically). Such flux evaluation on impermeable boundary is just irrelevant, it does not imply that the boundary condition is not applied properly.")
		.def("getBoundaryVolume",&TemplateFlowEngine_@TEMPLATE_FLOW_NAME@::getBoundaryVolume,(boost::python::arg("boundary")),(boost::python::arg("dt")),"Get total volume flowing through boundary defined by its body id in current timestep dt.\n\n.. note:: The volume may be not zero even for no-flow condition. This artifact comes from cells which are incident to two or more boundaries (along the edges of the sample, typically). Such volume evaluation on impermeable boundary is just irrelevant, it does not imply that the boundary condition is not applied properly.")
		.def("getBoundaryFluidArea",&TemplateFlowEngine_@TEMPLATE_FLOW_NAME@::getBoundaryArea,(boost::python::arg("boundary")),"Get total fluid area associated with boundary defined by its body id.")
		.def("getBoundaryVel",&TemplateFlowEngine_@TEMPLATE_FLOW_NAME@::getBoundaryVel,(boost::python::arg("boundary")),"Get total avg cell velocity associated with boundary defined by its body id.")
		.def("getCellVelocity", &TemplateFlowEngine_@TEMPLATE_FLOW_NAME@::getCellVelocity,(boost::python::arg("pos")),"Get relative cell velocity at position pos[0] pos [1] pos[2].")
		.def("getCellVolume", &TemplateFlowEngine_@TEMPLATE_FLOW_NAME@::getCellVolume,(boost::python::arg("pos")),"Get volume of cell at position pos[0] pos [1] pos[2].")
		.def("getConstrictions",&TemplateFlowEngine_@TEMPLATE_FLOW_NAME@::getConstrictions,(boost::python::arg("all")=true),"Get the list of constriction radii (inscribed circle) for all finite facets (if all==True) or all facets not incident to a virtual bounding sphere (if all==False).  When all facets are returned, negative radii denote facet incident to one or more fictious spheres.")
		.def("getConstrictionsFull",&TemplateFlowEngine_@TEMPLATE_FLOW_NAME@::getConstrictionsFull,(boost::python::arg("all")=true),"Get the list of constrictions (inscribed circle) for all finite facets (if all==True), or all facets not incident to a fictious bounding sphere (if all==False). When all facets are returned, negative radii denote facet incident to one or more fictious spheres. The constrictions are returned in the format {{cell1,cell2}{rad,nx,ny,nz}}")
		.def("edgeSize",&TemplateFlowEngine_@TEMPLATE_FLOW_NAME@::edgeSize,"Return the number of interactions.")
		.def("getEquivalentCompressibility",&TemplateFlowEngine_@TEMPLATE_FLOW_NAME@::getEquivalentCompressibility,"Return the equivalent compressibility used for modeling air water mixture in cavity.")
		.def("getCavityDensity",&TemplateFlowEngine_@TEMPLATE_FLOW_NAME@::getCavityDensity,"Return the density of cavity fluid.")
		.def("getCavityFlux",&TemplateFlowEngine_@TEMPLATE_FLOW_NAME@::getCavityFlux,"Return the flux through the edge of the cavity.")
		.def("surfaceSolidThroatInPore",&TemplateFlowEngine_@TEMPLATE_FLOW_NAME@::surfaceSolidThroatInPore,(boost::python::arg("cellId"),boost::python::arg("throatIndex")),"returns solid area in the throat (index 0-3), keeping only that part of the throat in cell.")
		.def("OSI",&TemplateFlowEngine_@TEMPLATE_FLOW_NAME@::OSI,"Return the number of interactions only between spheres.")
		.def("saveVtk",&TemplateFlowEngine_@TEMPLATE_FLOW_NAME@::saveVtk,(boost::python::arg("folder")="./VTK",boost::python::arg("withBoundaries")=false),"Save pressure field in vtk format. Specify a folder name for output. The cells adjacent to the bounding spheres are generated conditionally based on :yref:`FlowEngine::withBoundaries` (not compatible with periodic boundaries)")
		.def("avFlVelOnSph",&TemplateFlowEngine_@TEMPLATE_FLOW_NAME@::avFlVelOnSph,(boost::python::arg("idSph")),"compute a sphere-centered average fluid velocity")
		.def("fluidForce",&TemplateFlowEngine_@TEMPLATE_FLOW_NAME@::fluidForce,(boost::python::arg("idSph")),"Return the fluid force on sphere idSph.")
		.def("shearLubForce",&TemplateFlowEngine_@TEMPLATE_FLOW_NAME@::shearLubForce,(boost::python::arg("idSph")),"Return the shear lubrication force on sphere idSph.")
		.def("shearLubTorque",&TemplateFlowEngine_@TEMPLATE_FLOW_NAME@::shearLubTorque,(boost::python::arg("idSph")),"Return the shear lubrication torque on sphere idSph.")
		.def("normalLubForce",&TemplateFlowEngine_@TEMPLATE_FLOW_NAME@::normalLubForce,(boost::python::arg("idSph")),"Return the normal lubrication force on sphere idSph.")
		.def("bodyShearLubStress",&TemplateFlowEngine_@TEMPLATE_FLOW_NAME@::bodyShearLubStress,(boost::python::arg("idSph")),"Return the shear lubrication stress on sphere idSph.")
		.def("bodyNormalLubStress",&TemplateFlowEngine_@TEMPLATE_FLOW_NAME@::bodyNormalLubStress,(boost::python::arg("idSph")),"Return the normal lubrication stress on sphere idSph.")
		.def("shearVelocity",&TemplateFlowEngine_@TEMPLATE_FLOW_NAME@::shearVelocity,(boost::python::arg("idSph")),"Return the shear velocity of the interaction.")
		.def("normalVelocity",&TemplateFlowEngine_@TEMPLATE_FLOW_NAME@::normalVelocity,(boost::python::arg("idSph")),"Return the normal velocity of the interaction.")
		.def("normalVect",&TemplateFlowEngine_@TEMPLATE_FLOW_NAME@::normalVect,(boost::python::arg("idSph")),"Return the normal vector between particles.")
		.def("surfaceDistanceParticle",&TemplateFlowEngine_@TEMPLATE_FLOW_NAME@::surfaceDistanceParticle,(boost::python::arg("interaction")),"Return the distance between particles.")
		.def("onlySpheresInteractions",&TemplateFlowEngine_@TEMPLATE_FLOW_NAME@::onlySpheresInteractions,(boost::python::arg("interaction")),"Return the id of the interaction only between spheres.")
		.def("pressureProfile",&TemplateFlowEngine_@TEMPLATE_FLOW_NAME@::pressureProfile,(boost::python::arg("wallUpY"),boost::python::arg("wallDownY")),"Measure pore pressure in 6 equally-spaced points along the height of the sample")
		.def("getPorePressure",&TemplateFlowEngine_@TEMPLATE_FLOW_NAME@::getPorePressure,(boost::python::arg("pos")),"Measure pore pressure in position pos[0],pos[1],pos[2]")
		.def("getPoreTemperature",&TemplateFlowEngine_@TEMPLATE_FLOW_NAME@::getPoreTemperature,(boost::python::arg("pos")),"Measure pore pressure in position pos[0],pos[1],pos[2]")
		.def("averageSlicePressure",&TemplateFlowEngine_@TEMPLATE_FLOW_NAME@::averageSlicePressure,(boost::python::arg("posY")),"Measure slice-averaged pore pressure at height posY")
		.def("averagePressure",&TemplateFlowEngine_@TEMPLATE_FLOW_NAME@::averagePressure,"Measure averaged pore pressure in the entire volume, the cells adjacent to the boundaries are ignored if includeBoundaries=False")
		.def("updateBCs",&TemplateFlowEngine_@TEMPLATE_FLOW_NAME@::updateBCs,"Update the boundary condition to reflect changes of boundary pressure (needed typically after changing :yref:`FlowEngine::bndCondValue`). It is not sufficient to reflect changes of the type of boundary condition (:yref:`FlowEngine::bndCondIsPressure`), in such case re-triangulation or at least updating the linear system is needed (see :yref:`FlowEngine::updateTriangulation` and :yref:`FlowEngine::updateLinearSystem`). Conversely, the update is not necessary for changing the value of point-wise imposed pressure (:yref:`FlowEngine::imposePressure`)")
		.def("resetLinearSystem",&TemplateFlowEngine_@TEMPLATE_FLOW_NAME@::pyResetLinearSystem,"trigger rebuild of the linear system while keeping the same triangulation")
		.def("emulateAction",&TemplateFlowEngine_@TEMPLATE_FLOW_NAME@::emulateAction,"get scene and run action (may be used to manipulate an engine outside the timestepping loop).")
		.def("getCell",&TemplateFlowEngine_@TEMPLATE_FLOW_NAME@::getCell,(boost::python::args("X","Y","Z")),"get id of the cell containing (X,Y,Z).")
		.def("getIncidentCells",&TemplateFlowEngine_@TEMPLATE_FLOW_NAME@::getIncidentCells,(boost::python::arg("vertexId")),"get ids of all cells of which vertexId is a vertex. Typical usage is for getting cells indident to a boundary.")
		.def("getCellBarycenter",&TemplateFlowEngine_@TEMPLATE_FLOW_NAME@::cellBarycenterFromId,(boost::python::arg("id")),"get barycenter of cell 'id'.")
		.def("getCellCenter",&TemplateFlowEngine_@TEMPLATE_FLOW_NAME@::cellCenter,(boost::python::arg("id")),"get voronoi center of cell 'id'.")
		.def("getCellInvVoidVolume",&TemplateFlowEngine_@TEMPLATE_FLOW_NAME@::invInitVoidVolume,(boost::python::arg("id")),"get the inverse of the cell volume for cell 'id' after pore volumes have been initialized and :yref:`FlowEngine:iniVoidVolumes` = True, or compressibility scheme active with :yref:`FlowEngine::fluidBulkModulus`.")
		.def("getCellPressure",&TemplateFlowEngine_@TEMPLATE_FLOW_NAME@::cellPressure,(boost::python::arg("id")),"get pressure by cell 'id'. Note: getting pressure at position (x,y,z) might be more usefull, see :yref`FlowEngine::getPorePressure`:")
		.def("getCellTemperature",&TemplateFlowEngine_@TEMPLATE_FLOW_NAME@::cellTemperature,(boost::python::arg("id")),"get pressure in cell 'id'.")
		.def("setCellPressure",&TemplateFlowEngine_@TEMPLATE_FLOW_NAME@::setCellPressure,(boost::python::arg("id"),boost::python::arg("pressure")),"set pressure in cell 'id'.")
		.def("setCellTemperature",&TemplateFlowEngine_@TEMPLATE_FLOW_NAME@::setCellTemperature,(boost::python::arg("id"),boost::python::arg("temperature")),"set temperature in cell 'id'.")
		.def("getCellPImposed",&TemplateFlowEngine_@TEMPLATE_FLOW_NAME@::cellPImposed,(boost::python::arg("id")),"get the status of cell 'id' wrt imposed pressure.")
		.def("setCellPImposed",&TemplateFlowEngine_@TEMPLATE_FLOW_NAME@::setCellPImposed,(boost::python::arg("id"),boost::python::arg("pImposed")),"make cell 'id' assignable with imposed pressure.")
		.def("getCellTImposed",&TemplateFlowEngine_@TEMPLATE_FLOW_NAME@::cellTImposed,(boost::python::arg("id")),"get the status of cell 'id' wrt imposed temperature.")
		.def("setCellTImposed",&TemplateFlowEngine_@TEMPLATE_FLOW_NAME@::setCellTImposed,(boost::python::arg("id"),boost::python::arg("tImposed")),"make cell 'id' assignable with imposed temperature.")
		.def("nCells",&TemplateFlowEngine_@TEMPLATE_FLOW_NAME@::nCells,"get the total number of finite cells in the triangulation.")
		.def("blockCell",&TemplateFlowEngine_@TEMPLATE_FLOW_NAME@::blockCell,(boost::python::arg("id"),boost::python::arg("blockPressure")),"block cell 'id'. The cell will be excluded from the fluid flow problem and the conductivity of all incident facets will be null. If blockPressure=False, deformation is reflected in the pressure, else it is constantly 0.")
		.def("getVertices",&TemplateFlowEngine_@TEMPLATE_FLOW_NAME@::getVertices,(boost::python::arg("id")),"get the vertices of a cell")
		.def("getNeighbors",&TemplateFlowEngine_@TEMPLATE_FLOW_NAME@::getNeighbors,"get 4 neigboring cells")
		.def("getDiffusionCoeff",&TemplateFlowEngine_@TEMPLATE_FLOW_NAME@::getDiffusionCoeff,(boost::python::arg("cellId"),boost::python::arg("throat")),"get the ratio of throat cross-sectional area and distance between two cells")
		.def("getConductivity",&TemplateFlowEngine_@TEMPLATE_FLOW_NAME@::getConductivity,(boost::python::arg("cellId"),boost::python::arg("throat")),"get conductivity from cell and throat, with throat between 0 and 3 (same ordering as incident cells)")
		#ifdef LINSOLV
		.def("exportMatrix",&TemplateFlowEngine_@TEMPLATE_FLOW_NAME@::exportMatrix,(boost::python::arg("filename")="matrix"),"Export system matrix to a file with all entries (even zeros will displayed).")
		.def("exportTriplets",&TemplateFlowEngine_@TEMPLATE_FLOW_NAME@::exportTriplets,(boost::python::arg("filename")="triplets"),"Export system matrix to a file with only non-zero entries.")
		.def("cholmodStats",&TemplateFlowEngine_@TEMPLATE_FLOW_NAME@::cholmodStats,"get statistics of cholmod solver activity")
		.def("metisUsed",&TemplateFlowEngine_@TEMPLATE_FLOW_NAME@::metisUsed,"check wether metis lib is effectively used")
		.add_property("forceMetis",&TemplateFlowEngine_@TEMPLATE_FLOW_NAME@::getForceMetis,&TemplateFlowEngine_@TEMPLATE_FLOW_NAME@::setForceMetis,"If true, METIS is used for matrix preconditioning, else Cholmod is free to choose the best method (which may be METIS to, depending on the matrix). See ``nmethods`` in Cholmod documentation")
		#endif
		.def("compTessVolumes",&TemplateFlowEngine_@TEMPLATE_FLOW_NAME@::compTessVolumes,"Like TesselationWrapper::computeVolumes()")
		.def("volume",&TemplateFlowEngine_@TEMPLATE_FLOW_NAME@::getVolume,(boost::python::arg("id")=0),"Returns the volume of Voronoi's cell of a sphere.")
		.def("averageVelocity",&TemplateFlowEngine_@TEMPLATE_FLOW_NAME@::averageVelocity,"measure the mean velocity in the period")
		.def("setBoundaryNormal",&TemplateFlowEngine_@TEMPLATE_FLOW_NAME@::setBoundaryNormal,"define the unit outward-pointing normal of a boundary (0<=index<=5).")
		.def("isCellNeighbor",&TemplateFlowEngine_@TEMPLATE_FLOW_NAME@::isCellNeighbor,(boost::python::arg("cell1_ID"), boost::python::arg("cell2_ID")),"check if cell1 and cell2 are neigbors.")
		.def("printVertices",&TemplateFlowEngine_@TEMPLATE_FLOW_NAME@::printVertices,"Export vertex positions and types")
		)
};


class FlowCellInfo_@TEMPLATE_FLOW_NAME@ : public CGT::SimpleCellInfo {
	public:
	//For vector storage of all cells
	unsigned int index;
	int volumeSign;
	bool Pcondition;
	bool blocked;//exclude cell from the fluid domain
	Real invVoidV;
	Real t;
	int fict;
 	Real volumeVariation;
	Real pression;
	 //average relative (fluid - facet translation) velocity defined for a single cell as 1/Volume * SUM_ON_FACETS(x_average_facet*average_facet_flow_rate)
	CVector averageCellVelocity;
	// Surface vectors of facets, pointing from outside toward inside the cell
	std::vector<CVector> facetSurfaces;
	//Ratio between fluid surface and facet surface
	std::vector<Real> facetFluidSurfacesRatio;
	// Reflects the geometrical property of the cell, so that the force by cell fluid on grain "i" is pressure*unitForceVectors[i]
	std::vector<CVector> unitForceVectors;
	// Store the area of triangle-sphere intersections for each facet (used in forces definition)
	std::vector<CVector> facetSphereCrossSections;
	std::vector<CVector> cellForce;
	std::vector<Real> rayHydr;
	std::vector<Real> modulePermeability;
	std::vector<Real> sphericalVertexSurface;
	// Partial surfaces of spheres in the Real-tetrahedron linking two voronoi centers. [i][j] is for sphere facet "i" and sphere facetVertices[i][j]. Last component for 1/sum_surfaces in the facet.doInterpolate
	Real solidSurfaces [4][4];
	Real temperature;
	Real tempVariation;
	Real internalEnergy;
	bool Tcondition;
	bool isCavity;
	Real Reynolds;
	Real NutimesFluidK;
	Real stabilityCoefficient;


	FlowCellInfo_@TEMPLATE_FLOW_NAME@ (void)
	{
		index=0;
		modulePermeability.resize(4, 0);
		cellForce.resize(4,CGAL::NULL_VECTOR);
		facetSurfaces.resize(4,CGAL::NULL_VECTOR);
		facetFluidSurfacesRatio.resize(4,0);
		facetSphereCrossSections.resize(4,CGAL::NULL_VECTOR);
		unitForceVectors.resize(4,CGAL::NULL_VECTOR);
		for (int k=0; k<4;k++) for (int l=0; l<3;l++) solidSurfaces[k][l]=0;
		sphericalVertexSurface.resize(4,0);
		rayHydr.resize(4, 0);
		invSumK=0;s=0;
		volumeVariation=pression=invVoidV=fict=index=volumeSign=0;
		temperature=tempVariation=Reynolds=NutimesFluidK=stabilityCoefficient=0;
		isFictious=false; Pcondition = false; isGhost = false; Tcondition= false; isAlpha=false;
		isvisited = false; isCavity=false;
		isGhost=false;
		blocked=false;
	}
	bool isGhost;
	Real invSumK;
	bool isvisited;

	inline Real& volume (void) {return t;}
	inline const Real& invVoidVolume (void) const {return invVoidV;}
	inline Real& invVoidVolume (void) {return invVoidV;}
	inline Real& dv (void) {return volumeVariation;}
	inline Real& dtemp (void) {return tempVariation;}
	inline int& fictious (void) {return fict;}
	inline Real& p (void) {return pression;}
	inline Real& temp (void) {return temperature;}
	inline Real shiftedP (void) const {return pression;} //For compatibility with the periodic case
	inline const std::vector<Real>& kNorm (void) const {return modulePermeability;}
	inline std::vector<Real>& kNorm (void) {return modulePermeability;}
	inline std::vector< CVector >& facetSurf (void) {return facetSurfaces;}
	inline std::vector<CVector>& force (void) {return cellForce;}
	inline std::vector<Real>& Rh (void) {return rayHydr;}
	inline CVector& averageVelocity (void) {return averageCellVelocity;}
	//used for transfering values between two triangulations, overload with more variables in derived classes (see e.g. SoluteFlow)
	inline void getInfo(const FlowCellInfo_@TEMPLATE_FLOW_NAME@& otherCellInfo) {p()=otherCellInfo.shiftedP();}
	inline Real fluidArea (unsigned throat) {return sqrt(facetSurfaces[throat].squared_length());}
};

class FlowVertexInfo_@TEMPLATE_FLOW_NAME@ : public CGT::SimpleVertexInfo {
	CVector grainVelocity;
	Real volumeIncidentCells;
public:
	CVector forces;
	bool isGhost;
	FlowVertexInfo_@TEMPLATE_FLOW_NAME@ (void) {isGhost=false;}
	inline CVector& force (void) {return forces;}
	inline CVector& vel (void) {return grainVelocity;}
	inline Real& volCells (void) {return volumeIncidentCells;}
	inline const CVector ghostShift (void) const {return CGAL::NULL_VECTOR;}
};

} // namespace yade

// Implementation of functions in this separate file for clarity
#include "FlowEngine_@TEMPLATE_FLOW_NAME@.ipp"
