File: UnsaturatedEngine.cpp

package info (click to toggle)
yade 2026.1.0-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 34,448 kB
  • sloc: cpp: 97,645; python: 52,173; sh: 677; makefile: 162
file content (368 lines) | stat: -rw-r--r-- 18,751 bytes parent folder | download | duplicates (3)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
/*************************************************************************
*  Copyright (C) 2012 by Chao Yuan <chao.yuan@3sr-grenoble.fr>           *
*  Copyright (C) 2012 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. *
*************************************************************************/
#ifdef FLOW_ENGINE
#include "TwoPhaseFlowEngine.hpp"
#include <lib/high-precision/Constants.hpp>

//keep this #ifdef for commited versions unless you really have stable version that should be compiled by default
//it will save compilation time for everyone else
#ifdef TWOPHASEFLOW


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


class UnsaturatedEngine : public TwoPhaseFlowEngine {
public:
	double totalCellVolume;
	double computeCellInterfacialArea(CellHandle cell, int j, double rC);

	// 		void computeSolidLine();

	//record and test functions
	void checkLatticeNodeY(double y);
	//temporary functions
	void   initializeCellWindowsID();
	double getWindowsSaturation(int i, bool isSideBoundaryIncluded = false);
	bool   checknoCache() { return solver->noCache; }
	double getInvadeDepth();
	double getSphericalSubdomainSaturation(Vector3r pos, double radius);
	double getCuboidSubdomainSaturation(Vector3r pos1, Vector3r pos2, bool isSideBoundaryIncluded);
	double getCuboidSubdomainPorosity(Vector3r pos1, Vector3r pos2, bool isSideBoundaryIncluded);
	double getSpecificInterfacialArea();

	void printSomething();
	virtual ~UnsaturatedEngine();
	void action() override;

	// clang-format off
		YADE_CLASS_BASE_DOC_ATTRS_INIT_CTOR_PY(UnsaturatedEngine,TwoPhaseFlowEngine,"Preliminary version engine of a drainage model for unsaturated soils. Note:Air reservoir is on the top; water reservoir is on the bottom.(deprecated engine, use TwoPhaseFlowEngine instead)",

		((int, windowsNo, 10,, "Number of genrated windows(or zoomed samples)."))
					,,,
		.def("getSpecificInterfacialArea",&UnsaturatedEngine::getSpecificInterfacialArea,"get specific interfacial area (defined as the amount of fluid-fluid interfacial area per unit volume pf the porous medium).")
		.def("checkLatticeNodeY",&UnsaturatedEngine::checkLatticeNodeY,(boost::python::arg("y")),"Check the slice of lattice nodes for yNormal(y). 0: out of sphere; 1: inside of sphere.")
		.def("getInvadeDepth",&UnsaturatedEngine::getInvadeDepth,"Get NW-phase invasion depth. (the distance from NW-reservoir to front of NW-W interface.)")
		.def("getSphericalSubdomainSaturation",&UnsaturatedEngine::getSphericalSubdomainSaturation,(boost::python::arg("pos"),boost::python::arg("radius")),"Get saturation of spherical subdomain defined by (pos, radius). The subdomain exclude boundary pores.")
		.def("getCuboidSubdomainSaturation",&UnsaturatedEngine::getCuboidSubdomainSaturation,(boost::python::arg("pos1"),boost::python::arg("pos2"),boost::python::arg("isSideBoundaryIncluded")),"Get saturation of cuboid subdomain defined by (pos1,pos2). If isSideBoundaryIncluded=false, the pores of side boundary are excluded in saturation calculating; if isSideBoundaryIncluded=true (only in isInvadeBoundary=true drainage mode), the pores of side boundary are included in saturation calculating.")
		.def("getCuboidSubdomainPorosity",&UnsaturatedEngine::getCuboidSubdomainPorosity,(boost::python::arg("pos1"),boost::python::arg("pos2"),boost::python::arg("isSideBoundaryIncluded")),"Get the porosity of cuboid subdomain defined by (pos1,pos2). If isSideBoundaryIncluded=false, the pores of side boundary are excluded in porosity calculating; if isSideBoundaryIncluded=true (only in isInvadeBoundary=true drainage mode), the pores of side boundary are included in porosity calculating.")
		.def("checknoCache",&UnsaturatedEngine::checknoCache,"check noCache. (temporary function.)")
		.def("getWindowsSaturation",&UnsaturatedEngine::getWindowsSaturation,(boost::python::arg("windowsID"),boost::python::arg("isSideBoundaryIncluded")), "get saturation of subdomain with windowsID. If isSideBoundaryIncluded=false (default), the pores of side boundary are excluded in saturation calculating; if isSideBoundaryIncluded=true (only in isInvadeBoundary=true drainage mode), the pores of side boundary are included in saturation calculating.")
		.def("initializeCellWindowsID",&UnsaturatedEngine::initializeCellWindowsID,"Initialize cell windows index. A temporary function for comparison with experiments, will delete soon")
		.def("printSomething",&UnsaturatedEngine::printSomething,"print debug.")
		)
	// clang-format on
	DECLARE_LOGGER;
};

REGISTER_SERIALIZABLE(UnsaturatedEngine);
YADE_PLUGIN((UnsaturatedEngine));

UnsaturatedEngine::~UnsaturatedEngine() { }

/*void UnsaturatedEngine::initialDrainage()
{
	cout<<"This is UnsaturatedEngine test program"<<endl;
	RTriangulation& tri = solver->T[solver->currentTes].Triangulation();
	if (tri.number_of_vertices()==0) {
		cout<< "triangulation is empty: building a new one" << endl;
		scene = Omega::instance().getScene().get();//here define the pointer to Yade's scene
		setPositionsBuffer(true);//copy sphere positions in a buffer...
		buildTriangulation(bndCondValue[2],*solver);//create a triangulation and initialize pressure in the elements, everything will be contained in "solver"
		initialReservoirs();
		initializeCellIndex();//initialize cell index
		computePoreThroatRadius();//save all pore radii before drainage
		computeTotalPoresVolume();//save total volume of porous medium, considering different invading boundaries condition (isInvadeBoundary==True or False), aiming to calculate specific interfacial area.
		computePoreBodyVolume();//save capillary volume of all cells, for fast calculating saturation
		computeSolidLine();//save cell->info().solidLine[j][y]
	}
	solver->noCache = true;
}*/

void UnsaturatedEngine::action()
{
/*
  //the drainage is in quasi-static regime, so it can work outside Omega.  
    if ( !isActivated ) return;
    RTriangulation& tri = solver->T[solver->currentTes].Triangulation();
    if ( (tri.number_of_vertices()==0) || (updateTriangulation) ) {
        cout<< "triangulation is empty: building a new one" << endl;
        scene = Omega::instance().getScene().get();//here define the pointer to Yade's scene
        setPositionsBuffer(true);//copy sphere positions in a buffer...
        buildTriangulation(bndCondValue[2],*solver);//create a triangulation and initialize pressure in the elements, everything will be contained in "solver"
	initialReservoirs();
        initializeCellIndex();//initialize cell index
        computePoreThroatRadius();//save all pore radii before drainage
        computeTotalPoresVolume();//save total volume of porous medium, considering different invading boundaries condition (isInvadeBoundary==True or False), aiming to calculate specific interfacial area.
        computePoreBodyVolume();//save capillary volume of all cells, for calculating saturation
        computeSolidLine();//save cell->info().solidLine[j][y]
        solver->noCache = true;
    }
    ///compute drainage
    if (pressureForce) { drainage();}
    
    ///compute force
    if(computeForceActivated){
    computeCapillaryForce();
    Vector3r force;
    FiniteVerticesIterator vertices_end = solver->T[solver->currentTes].Triangulation().finite_vertices_end();
    for ( FiniteVerticesIterator V_it = solver->T[solver->currentTes].Triangulation().finite_vertices_begin(); V_it !=  vertices_end; V_it++ ) {
        force = pressureForce ? Vector3r ( V_it->info().forces[0],V_it->info().forces[1],V_it->info().forces[2] ): Vector3r(0,0,0);
        scene->forces.addForce ( V_it->info().id(), force); }}
*/}


double UnsaturatedEngine::getSpecificInterfacialArea()
{
	RTriangulation&     tri = solver->T[solver->currentTes].Triangulation();
	FiniteCellsIterator cellEnd = tri.finite_cells_end();
	double              interfacialArea = 0;

	for (FiniteCellsIterator cell = tri.finite_cells_begin(); cell != cellEnd; cell++) {
		//             if (cell->info().Pcondition==true) continue;//NOTE:reservoirs cells interfacialArea should not be included.
		if (cell->info().isFictious) continue;
		if (cell->info().isNWRes == true) {
			for (int facet = 0; facet < 4; facet++) {
				if (tri.is_infinite(cell->neighbor(facet))) continue;
				if (cell->neighbor(facet)->info().Pcondition == true) continue;
				if ((cell->neighbor(facet)->info().isFictious) && (!isInvadeBoundary)) continue;
				if (cell->neighbor(facet)->info().isNWRes == false)
					interfacialArea = interfacialArea + computeCellInterfacialArea(cell, facet, cell->info().poreThroatRadius[facet]);
			}
		}
	}
	//     cerr<<"InterArea:"<<interfacialArea<<"  totalCellVolume:"<<totalCellVolume<<endl;
	return interfacialArea / totalCellVolume;
}

double UnsaturatedEngine::computeCellInterfacialArea(CellHandle cell, int j, double rC)
{
	double     rInscribe = std::abs(solver->computeEffectiveRadius(cell, j));
	CellHandle cellh = CellHandle(cell);
	int        facetNFictious = solver->detectFacetFictiousVertices(cellh, j);

	if (facetNFictious == 0) {
		RTriangulation& tri = solver->T[solver->currentTes].Triangulation();
		if (tri.is_infinite(cell->neighbor(j))) return 0;

		Vector3r pos[3];    //solid pos
		double   r[3];      //solid radius
		double   rRc[3];    //r[i] + rC (rC: capillary radius)
		double   e[3];      //edges of triangulation
		double   rad[4][3]; //angle in radian

		for (int i = 0; i < 3; i++) {
			pos[i] = makeVector3r(cell->vertex(facetVertices[j][i])->point().point());
			r[i] = sqrt(cell->vertex(facetVertices[j][i])->point().weight());
			rRc[i] = r[i] + rC;
		}

		e[0] = (pos[1] - pos[2]).norm();
		e[1] = (pos[2] - pos[0]).norm();
		e[2] = (pos[1] - pos[0]).norm();

		rad[3][0] = acos(((pos[1] - pos[0]).dot(pos[2] - pos[0])) / (e[2] * e[1]));
		rad[3][1] = acos(((pos[2] - pos[1]).dot(pos[0] - pos[1])) / (e[0] * e[2]));
		rad[3][2] = acos(((pos[0] - pos[2]).dot(pos[1] - pos[2])) / (e[1] * e[0]));

		rad[0][0] = computeTriRadian(e[0], rRc[1], rRc[2]);
		rad[0][1] = computeTriRadian(rRc[2], e[0], rRc[1]);
		rad[0][2] = computeTriRadian(rRc[1], rRc[2], e[0]);

		rad[1][0] = computeTriRadian(rRc[2], e[1], rRc[0]);
		rad[1][1] = computeTriRadian(e[1], rRc[0], rRc[2]);
		rad[1][2] = computeTriRadian(rRc[0], rRc[2], e[1]);

		rad[2][0] = computeTriRadian(rRc[1], e[2], rRc[0]);
		rad[2][1] = computeTriRadian(rRc[0], rRc[1], e[2]);
		rad[2][2] = computeTriRadian(e[2], rRc[0], rRc[1]);

		double sW0 = 0.5 * rRc[1] * rRc[2] * sin(rad[0][0]) - 0.5 * rad[0][0] * pow(rC, 2) - 0.5 * rad[0][1] * pow(r[1], 2)
		        - 0.5 * rad[0][2] * pow(r[2], 2);
		double sW1 = 0.5 * rRc[2] * rRc[0] * sin(rad[1][1]) - 0.5 * rad[1][1] * pow(rC, 2) - 0.5 * rad[1][2] * pow(r[2], 2)
		        - 0.5 * rad[1][0] * pow(r[0], 2);
		double sW2 = 0.5 * rRc[0] * rRc[1] * sin(rad[2][2]) - 0.5 * rad[2][2] * pow(rC, 2) - 0.5 * rad[2][0] * pow(r[0], 2)
		        - 0.5 * rad[2][1] * pow(r[1], 2);
		double sW = sW0 + sW1 + sW2;
		double sVoid = sqrt(cell->info().facetSurfaces[j].squared_length()) * cell->info().facetFluidSurfacesRatio[j];
		double sInterface = sVoid - sW;
		return sInterface;
	} else {
		return Mathr::PI * pow(rInscribe, 2);
	}
}

// ------------------for checking----------
void UnsaturatedEngine::checkLatticeNodeY(double y)
{
	RTriangulation& tri = solver->T[solver->currentTes].Triangulation();
	if ((y < solver->yMin) || (y > solver->yMax)) {
		cerr << "y is out of range! "
		     << "pleas set y between " << solver->yMin << " and " << solver->yMax << endl;
	} else {
		int                N = 100; // the default Node number for each slice is 100X100
		std::ofstream      file;
		std::ostringstream fileNameStream(".txt");
		fileNameStream << "LatticeNodeY_" << y;
		std::string fileName = fileNameStream.str();
		file.open(fileName.c_str());
		//     file << "#Slice Of LatticeNodes: 0: out of sphere; 1: inside of sphere  \n";
		double deltaX = (solver->xMax - solver->xMin) / N;
		double deltaZ = (solver->zMax - solver->zMin) / N;
		for (int j = 0; j < N + 1; j++) {
			for (int k = 0; k < N + 1; k++) {
				double   x = solver->xMin + j * deltaX;
				double   z = solver->zMin + k * deltaZ;
				int      M = 0;
				Vector3r LatticeNode = Vector3r(x, y, z);
				for (FiniteVerticesIterator V_it = tri.finite_vertices_begin(); V_it != tri.finite_vertices_end(); V_it++) {
					if (V_it->info().isFictious) continue;
					Vector3r SphereCenter = makeVector3r(V_it->point().point());
					if ((LatticeNode - SphereCenter).squaredNorm() < V_it->point().weight()) {
						M = 1;
						break;
					}
				}
				file << M;
			}
			file << "\n";
		}
		file.close();
	}
}

void UnsaturatedEngine::printSomething()
{
	RTriangulation& Tri = solver->T[solver->currentTes].Triangulation();
	//    FiniteEdgesIterator ed_it;
	for (FiniteEdgesIterator ed_it = Tri.finite_edges_begin(); ed_it != Tri.finite_edges_end(); ed_it++) {
		const VertexInfo& vi1 = (ed_it->first)->vertex(ed_it->second)->info();
		const VertexInfo& vi2 = (ed_it->first)->vertex(ed_it->third)->info();
		const int&        id1 = vi1.id();
		const int&        id2 = vi2.id();
		cerr << id1 << " " << id2 << endl;
	}
}


//----------temporary functions for comparison with experiment-----------------------
void UnsaturatedEngine::initializeCellWindowsID()
{
	RTriangulation&     tri = solver->T[solver->currentTes].Triangulation();
	FiniteCellsIterator cellEnd = tri.finite_cells_end();
	for (FiniteCellsIterator cell = tri.finite_cells_begin(); cell != cellEnd; cell++) {
		for (int i = 1; i < (windowsNo + 1); i++) {
			if ((cell->info()[1] > (solver->yMin + (i - 1) * (solver->yMax - solver->yMin) / windowsNo))
			    && (cell->info()[1] < (solver->yMin + i * (solver->yMax - solver->yMin) / windowsNo))) {
				cell->info().windowsID = i;
				break;
			}
		}
	}
}

double UnsaturatedEngine::getWindowsSaturation(int i, bool isSideBoundaryIncluded)
{
	if ((!isInvadeBoundary) && (isSideBoundaryIncluded)) cerr << "In isInvadeBoundary=false drainage, isSideBoundaryIncluded can't set true." << endl;
	double              poresVolume = 0.0; //total pores volume
	double              wVolume = 0.0;     //W-phase volume
	RTriangulation&     tri = solver->T[solver->currentTes].Triangulation();
	FiniteCellsIterator cellEnd = tri.finite_cells_end();

	for (FiniteCellsIterator cell = tri.finite_cells_begin(); cell != cellEnd; cell++) {
		if (cell->info().Pcondition) continue;
		if ((cell->info().isFictious) && (!isSideBoundaryIncluded)) continue;
		if (cell->info().windowsID != i) continue;
		poresVolume = poresVolume + cell->info().poreBodyVolume;
		if (cell->info().saturation > 0.0) { wVolume = wVolume + cell->info().poreBodyVolume * cell->info().saturation; }
	}
	return wVolume / poresVolume;
}

double UnsaturatedEngine::getCuboidSubdomainSaturation(Vector3r pos1, Vector3r pos2, bool isSideBoundaryIncluded)
{
	if ((!isInvadeBoundary) && (isSideBoundaryIncluded)) cerr << "In isInvadeBoundary=false drainage, isSideBoundaryIncluded can't set true." << endl;
	double              poresVolume = 0.0; //total pores volume
	double              wVolume = 0.0;     //W-phase volume
	RTriangulation&     tri = solver->T[solver->currentTes].Triangulation();
	FiniteCellsIterator cellEnd = tri.finite_cells_end();

	for (FiniteCellsIterator cell = tri.finite_cells_begin(); cell != cellEnd; cell++) {
		if (cell->info().Pcondition) continue;
		if ((cell->info().isFictious) && (!isSideBoundaryIncluded)) continue;
		if (((pos1[0] - cell->info()[0]) * (pos2[0] - cell->info()[0]) < 0) && ((pos1[1] - cell->info()[1]) * (pos2[1] - cell->info()[1]) < 0)
		    && ((pos1[2] - cell->info()[2]) * (pos2[2] - cell->info()[2]) < 0)) {
			poresVolume = poresVolume + cell->info().poreBodyVolume;
			if (cell->info().saturation > 0.0) { wVolume = wVolume + cell->info().poreBodyVolume * cell->info().saturation; }
		}
	}
	return wVolume / poresVolume;
}

double UnsaturatedEngine::getCuboidSubdomainPorosity(Vector3r pos1, Vector3r pos2, bool isSideBoundaryIncluded)
{
	if ((!isInvadeBoundary) && (isSideBoundaryIncluded)) cerr << "In isInvadeBoundary=false drainage, isSideBoundaryIncluded can't set true." << endl;
	double              totalCellVolume2 = 0.0;
	double              totalVoidVolume = 0.0;
	RTriangulation&     tri = solver->T[solver->currentTes].Triangulation();
	FiniteCellsIterator cellEnd = tri.finite_cells_end();

	for (FiniteCellsIterator cell = tri.finite_cells_begin(); cell != cellEnd; cell++) {
		if (cell->info().Pcondition) continue;
		if ((cell->info().isFictious) && (!isSideBoundaryIncluded)) continue;
		if (((pos1[0] - cell->info()[0]) * (pos2[0] - cell->info()[0]) < 0) && ((pos1[1] - cell->info()[1]) * (pos2[1] - cell->info()[1]) < 0)
		    && ((pos1[2] - cell->info()[2]) * (pos2[2] - cell->info()[2]) < 0)) {
			totalCellVolume2 = totalCellVolume2 + std::abs(cell->info().volume());
			totalVoidVolume = totalVoidVolume + cell->info().poreBodyVolume;
		}
	}
	if (totalVoidVolume == 0 || totalCellVolume2 == 0) cerr << "subdomain too small!" << endl;
	return totalVoidVolume / totalCellVolume2;
}

double UnsaturatedEngine::getInvadeDepth()
{
	double              yPosMax = -1e50;
	double              yPosMin = 1e50;
	RTriangulation&     tri = solver->T[solver->currentTes].Triangulation();
	FiniteCellsIterator cellEnd = tri.finite_cells_end();
	for (FiniteCellsIterator cell = tri.finite_cells_begin(); cell != cellEnd; cell++) {
		if (cell->info().isNWRes) {
			yPosMax = std::max(yPosMax, cell->info()[1]);
			yPosMin = std::min(yPosMin, cell->info()[1]);
		}
	}
	return std::abs(yPosMax - yPosMin);
}

double UnsaturatedEngine::getSphericalSubdomainSaturation(Vector3r pos, double radius)
{
	double              poresVolume = 0.0;
	double              wVolume = 0.0;
	RTriangulation&     Tri = solver->T[solver->currentTes].Triangulation();
	FiniteCellsIterator cellEnd = Tri.finite_cells_end();
	for (FiniteCellsIterator cell = Tri.finite_cells_begin(); cell != cellEnd; cell++) {
		Vector3r cellPos = makeVector3r(cell->info());
		double   dist = (pos - cellPos).norm();
		if (dist > radius) continue;
		if (cell->info().isFictious) {
			cerr << "The radius of subdomain is too large, or the center of subdomain is out of packing. Please reset subdomain again." << endl;
			return -1;
		}
		poresVolume = poresVolume + cell->info().poreBodyVolume;
		if (cell->info().saturation > 0.0) { wVolume = wVolume + cell->info().poreBodyVolume * cell->info().saturation; }
	}
	return wVolume / poresVolume;
}

//--------------end of comparison with experiment----------------------------

} // namespace yade

#endif //TWOPHASEFLOW
#endif //FLOW_ENGINE