def processAlgorithm(self, parameters, context, feedback): try: dir_path = os.path.dirname(os.path.realpath(__file__)) sys.path.insert(0, dir_path) import porepy as pp from flow import Flow_Model1 from tracer import Tracer from fcts import read_network, bc_flag from math import ceil import numpy as np except Exception as e: feedback.reportError( QCoreApplication.translate('Error', '%s' % (e))) feedback.reportError(QCoreApplication.translate('Error', '')) feedback.reportError( QCoreApplication.translate( 'Error', 'Please install porepy dependencies')) return {} #Parameters layer = self.parameterAsLayer(parameters, self.Network, context) h = self.parameterAsDouble(parameters, self.boundSize, context) * pp.METER steps = self.parameterAsInt(parameters, self.steps, context) endv = self.parameterAsInt(parameters, self.end, context) * pp.SECOND tol = 1e-8 * pp.METER d = self.parameterAsInt(parameters, self.Direction, context) lP = parameters[self.lowPressure] * pp.PASCAL hP = parameters[self.highPressure] * pp.PASCAL mu = parameters[self.mu] * pp.PASCAL * pp.SECOND if d == 0: direction = "left_to_right" elif d == 1: direction = "right_to_left" elif d == 2: direction = "bottom_to_top" else: direction = "top_to_bottom" if lP > hP: feedback.reportError( QCoreApplication.translate( 'Error', 'Low pressure value is higher than high pressure value.')) return {} params = {'INPUT': layer, 'OUTPUT': 'memory:'} explode = st.run("native:explodelines", params, context=context, feedback=feedback) layer2 = explode['OUTPUT'] #Create new field to fracture line newFields = [ 'ID', 'Pressure', 'Flux', 'Azimuth', 'Tracer', 'StartTime', 'EndTime' ] if layer.fields().indexFromName('Transmisiv') == -1: feedback.reportError( QCoreApplication.translate( 'Error', 'Please calculate the transmissivity using the Aperture tool or define a new transmissivity field labelled "Transmisiv" in mD.m' )) return {} fields = QgsFields() for field in layer.fields(): if field.name() not in newFields: fields.append(QgsField(field.name(), field.type())) for field in newFields[:-2]: fields.append(QgsField(field, QVariant.Double)) fields.append(QgsField('StartTime', QVariant.DateTime)) fields.append(QgsField('EndTime', QVariant.DateTime)) (writer, dest_id) = self.parameterAsSink(parameters, self.outLine, context, fields, QgsWkbTypes.LineString, layer.sourceCrs()) #Define fracture geometries outDir = os.path.join(tempfile.gettempdir(), 'PorePy') if not os.path.exists(outDir): os.mkdir(outDir) fname = ''.join( random.choice(string.ascii_lowercase) for i in range(10)) outName = os.path.join(outDir, '%s.txt' % (fname)) k, l = {}, { } #k is the pereambility in m2, l is the fracture length in m data = {} P = 1000000 #Tolerance for the point precision feedback.pushInfo( QCoreApplication.translate('Info', 'Reading Fracture Network')) field_check = layer2.fields().indexFromName('origLen') if field_check != -1: feedback.reportError( QCoreApplication.translate( 'Info', 'Warning: Applying the origLen field to calculate fracture length' )) W = False with open(outName, 'w') as f: f.write('ID,startx,starty,endx,endy') f.write('\n') for enum, feature in enumerate(layer2.getFeatures()): try: geom = feature.geometry().asPolyline() except Exception: geom = feature.geometry().asMultiPolyline()[0] start, end = geom[0], geom[-1] startx, endx = ceil(start.x() * P) / P, ceil(end.x() * P) / P starty, endy = ceil(start.y() * P) / P, ceil(end.y() * P) / P t = feature['Transmisiv'] if t == 0: W = True if field_check != -1: lValue = feature['origLen'] if type(lValue) != float: feedback.reportError( QCoreApplication.translate( 'Info', 'Warning: origLen field contains non-float values' )) return {} l[feature.id()] = lValue / feature.geometry().length() else: l[feature.id()] = feature.geometry().length() if type(t) != float: feedback.reportError( QCoreApplication.translate( 'Info', 'Warning: Transmisivity field contains non-float values' )) return {} k[feature.id()] = t * pp.MILLIDARCY * pp.METER row = '%s,%s,%s,%s,%s' % (feature.id(), startx, starty, endx, endy) f.write(row) #ID,startx,starty,endx,endy f.write('\n') rows = [] for field in layer.fields(): if field.name() not in newFields: rows.append(feature[field.name()]) data[feature.id()] = rows if len(data) == 0: feedback.reportError( QCoreApplication.translate( 'Info', 'No fractures found in the input dataset')) return {} elif enum > 2000: feedback.reportError( QCoreApplication.translate( 'Info', 'Warning - Fracture network exceeds 2000 branches. To improve performance consider subsampling and/or simplifying the Fracture Network using the "Simplify Network" tool.' )) if W: feedback.reportError( QCoreApplication.translate( 'Info', 'Warning - Transmisivity value(s) of 0 in the fracture network will not produce flow' )) network, mask, pts_shift = read_network(outName, tol=tol) mesh_args = { "mesh_size_frac": h, "mesh_size_bound": h, 'file_name': outDir } feedback.pushInfo( QCoreApplication.translate('Info', 'Creating Mesh from %s' % (network))) try: gb = network.mesh( mesh_args, dfn=True, tol=tol, ) except Exception as e: feedback.reportError(QCoreApplication.translate('Info', str(e))) feedback.reportError(QCoreApplication.translate('Info', '')) feedback.reportError( QCoreApplication.translate( 'Info', 'Failure creating the fracture network mesh. Please check that NetworkGT was properly configured to use gmsh meshing according to the installation guidelines' )) flow = Flow_Model1(gb) param_flow = { "tol": tol, "k": np.array([k[m] for m in mask]) / mu, "length_ratio": np.array([l[m] for m in mask]), "flow_direction": direction, "low_value": lP, "high_value": hP, "north": np.array([0, 1, 0]) } param_tracer = { "tol": tol, "num_steps": steps, "end_time": endv, "flow_direction": direction, "low_value": lP, "high_value": hP } flow.set_data(param_flow, bc_flag) feedback.pushInfo( QCoreApplication.translate('Info', 'Solving Fluid Flow')) flow.solve() # get the results for qgis if steps > 1: feedback.pushInfo( QCoreApplication.translate('Info', 'Solving Tracer')) tracer = Tracer(gb) tracer.set_data(param_tracer, bc_flag) tracer.solve() feedback.pushInfo( QCoreApplication.translate('Info', 'Creating Feature Layer')) fet = QgsFeature() for g, d in gb: # avoid to consider the 0d grids if g.dim == 0: continue # get the data for the current grid p = d[pp.STATE][flow.pressure] norm_flux = d[pp.STATE][flow.norm_flux] azimuth = d[pp.STATE][flow.azimuth] # get the cell to nodes map cell_nodes = g.cell_nodes() indptr = cell_nodes.indptr indices = cell_nodes.indices # all the data that need to be exported are given as cell_SOMETHING for c in np.arange(g.num_cells): nodes_loc = indices[indptr[c]:indptr[c + 1]] # each column gives a node for the segment # NOTE: the nodes are translated compared to the original network pnt = g.nodes[:2, nodes_loc] + pts_shift # value of the computed fields cell_pressure = p[c] # flux norm cell_norm_flux = norm_flux[c] # the fracture id and data cell_frac_id = mask[g.frac_num] rows = data[cell_frac_id].copy() # value of the azimuth # NOTE: velocity zero gives nan as azimuth angle cell_azimuth = math.degrees(azimuth[c]) if cell_norm_flux == 0: rows.extend([ float(cell_frac_id), float(cell_pressure), float(cell_norm_flux), NULL ]) else: #cell_azimuth %= 360 rows.extend([ float(cell_frac_id), float(cell_pressure), float(cell_norm_flux), float(cell_azimuth) ]) points = [ QgsPointXY(pnt[0][0], pnt[1][0]), QgsPointXY(pnt[0][1], pnt[1][1]) ] geom = QgsGeometry.fromPolylineXY(points) fet.setGeometry(geom) if steps > 1: time = datetime.datetime(1, 1, 1, 0, 0, 0) deltaTime = datetime.timedelta(seconds=endv / steps) for time_step, current_time in enumerate(tracer.all_time): var_name = tracer.variable + "_" + str(time_step) tr = d[pp.STATE][var_name] cell_tracer = tr[c] newRows = rows.copy() newRows.append(float(round(cell_tracer, 6))) newRows.append(str(time)) time += deltaTime newRows.append(str(time)) fet.setAttributes(newRows) writer.addFeature(fet, QgsFeatureSink.FastInsert) else: fet.setAttributes(rows) writer.addFeature(fet, QgsFeatureSink.FastInsert) try: os.remove(outName) #Delete temp csv file except Exception: pass return {self.outLine: dest_id}
def processAlgorithm(self, parameters, context, feedback): try: dir_path = os.path.dirname(os.path.realpath(__file__)) sys.path.insert(0, dir_path) import porepy as pp import numpy as np import scipy.sparse as sps import flow as f from fcts import read_cart_grid, bc_flag, argsort_cart_grid from tracer import Tracer except Exception as e: feedback.reportError( QCoreApplication.translate('Error', '%s' % (e))) feedback.reportError(QCoreApplication.translate('Error', '')) feedback.reportError( QCoreApplication.translate( 'Error', 'Please install porepy dependencies')) return {} #Parameters layer = self.parameterAsLayer(parameters, self.Grid, context) xx = self.parameterAsString(parameters, self.xx, context) xy = self.parameterAsString(parameters, self.xy, context) yy = self.parameterAsString(parameters, self.yy, context) steps = self.parameterAsInt(parameters, self.steps, context) end = self.parameterAsDouble(parameters, self.end, context) * pp.SECOND dV = self.parameterAsInt(parameters, self.Direction, context) tol = 1e-4 * pp.METER lP = parameters[self.lowPressure] * pp.PASCAL hP = parameters[self.highPressure] * pp.PASCAL mu = 1e-3 * pp.PASCAL * pp.SECOND #Define here the dynamic viscosity of the liquid phase in [Pa s] if dV == 0: direction = "left_to_right" elif dV == 1: direction = "right_to_left" elif dV == 2: direction = "bottom_to_top" else: direction = "top_to_bottom" try: field_check = layer.fields().indexFromName('Rotation') if field_check == -1: feedback.reportError( QCoreApplication.translate( 'Error', 'Invalid Contour Grid layer - please run the contour grid tool prior to the 2D Flow tool' )) return {} except Exception: feedback.reportError( QCoreApplication.translate( 'Error', 'No attribute table found. Do not use the "Selected features only" option' )) return {} if lP > hP: feedback.reportError( QCoreApplication.translate( 'Error', 'Low pressure value is higher than high pressure value.')) return {} newFields = [ 'Pressure', 'Flux', 'Azimuth', 'Tracer', 'StartTime', 'EndTime', 'Step' ] fields = QgsFields() for field in layer.fields(): if field.name() not in newFields: fields.append(QgsField(field.name(), field.type())) for field in newFields[:-3]: fields.append(QgsField(field, QVariant.Double)) fields.append(QgsField('StartTime', QVariant.DateTime)) fields.append(QgsField('EndTime', QVariant.DateTime)) fields.append(QgsField('Step', QVariant.Double)) (writer, dest_id) = self.parameterAsSink(parameters, self.outGrid, context, fields, QgsWkbTypes.Polygon, layer.sourceCrs()) #Define xx,yy and xy perm kxx = [] kyy = [] kxy = [] #Get dictionary of features features = { feature['Sample_No_']: feature for feature in layer.selectedFeatures() } if len(features) == 0: features = { feature['Sample_No_']: feature for feature in layer.getFeatures() } extent = layer.extent() else: extent = layer.boundingBoxOfSelected() total = len(features) if total == 0: feedback.reportError( QCoreApplication.translate( 'Error', 'No grid cells found in the input dataset')) return {} c = 0 # Sort data by Sample No features = collections.OrderedDict(sorted(features.items())) W = False for FID, feature in features.items(): c += 1 if total != -1: feedback.setProgress(int(c * total)) xxV, yyV, xyV = feature[xx], feature[yy], feature[xy] if xxV == 0 and yyV == 0 and xyV == 0: feedback.reportError( QCoreApplication.translate( 'Info', 'Warning: Grid sample no. %s contains a pereambility of 0 for XX, XY and YY' % (FID))) W = True kxx.append(xxV * pp.MILLIDARCY) kyy.append(yyV * pp.MILLIDARCY) kxy.append(xyV * pp.MILLIDARCY) if type(xxV) != float or type(xyV) != float or type(yyV) != float: feedback.reportError( QCoreApplication.translate( 'Info', 'Warning: Grid sample no. %s contains non-float values for pereambility measurements' % (FID))) W = True if W: feedback.reportError( QCoreApplication.translate( 'Info', 'Invalid permeability measurements created an empty 2D flow grid!' )) return {} kxx, kyy, kxy = np.array(kxx), np.array(kyy), np.array(kxy) rotation = feature['Rotation'] spacing = feature['Spacing'] P = 10 #Precision #Read grid geometry extentGeom = QgsGeometry.fromRect(extent) extentGeom = extentGeom.orientedMinimumBoundingBox() dWidth = round(extentGeom[4], P) dHeight = round(extentGeom[3], P) #Domain width and height Ny = round(dHeight / spacing) Nx = round(dWidth / spacing) count = Nx * Ny if count != c: feedback.reportError( QCoreApplication.translate( 'Warning', 'Warning: Selected contour grid does not appear to be a rectangle.' )) feedback.reportError(QCoreApplication.translate('Warning', '')) # Read the grid gb = read_cart_grid(Nx, Ny, dWidth, dHeight) feedback.pushInfo( QCoreApplication.translate( 'Output', 'Constructing grid with %s columns and %s rows a domain size (width x height) of %s x %s.' % (Nx, Ny, dWidth, dHeight))) # mask that map the permeability from qgis to pp, and vice-versa mask, inv_mask = argsort_cart_grid(Nx, Ny) param_flow = { "tol": tol, "kxx": kxx[mask] / mu, "kyy": kyy[mask] / mu, "kxy": kxy[mask] / mu, "flow_direction": direction, "low_value": lP, "high_value": hP, "north": np.array([0, 1, 0]), } try: flow = f.Flow(gb) flow.set_data(param_flow, bc_flag) flow.solve() except Exception as e: feedback.reportError(QCoreApplication.translate('Error', str(e))) return {} if steps > 1: param_tracer = { "tol": tol, "num_steps": steps, "end_time": end, "flow_direction": direction, "low_value": lP, "high_value": hP } tracer = Tracer(gb) tracer.set_data(param_tracer, bc_flag) tracer.solve() t = [] for g, d in gb: p = d[pp.STATE][flow.pressure] v = d[pp.STATE][flow.norm_flux] a = d[pp.STATE][flow.azimuth] if steps > 1: for time_step, current_time in enumerate(tracer.all_time): var_name = tracer.variable + "_" + str(time_step) traceD = d[pp.STATE][var_name] traceD = traceD[inv_mask] t.append(traceD) #Reshape the output data p = p[inv_mask] v = v[inv_mask] a = a[inv_mask] feedback.pushInfo( QCoreApplication.translate('Output', 'Updating Feature Layer')) #Update the dataset fet = QgsFeature() for enum, FID in enumerate(features): feature = features[FID] FID = feature.id() geom = feature.geometry() if total != -1: feedback.setProgress(int(enum * total)) rows = [] for field in layer.fields(): if field.name() not in newFields: rows.append(feature[field.name()]) aV = math.degrees(float(a[enum])) + rotation if type(aV) == float: aV %= 360 if dV < 2: if aV < 180: aV += 180 else: aV -= 180 rows.extend([ round(float(p[enum]), P), round(float(v[enum]), P), round(float(aV), 2) ]) if steps > 1: time = datetime.datetime(1, 1, 1, 0, 0, 0) deltaTime = datetime.timedelta(seconds=end / steps) for n in range(len(t)): newRows = rows.copy() newRows.append(round(float(t[n][enum]), P)) newRows.append(str(time)) time += deltaTime newRows.append(str(time)) newRows.append(int(n)) fet.setGeometry(geom) fet.setAttributes(newRows) writer.addFeature(fet, QgsFeatureSink.FastInsert) else: fet.setGeometry(geom) fet.setAttributes(rows) writer.addFeature(fet, QgsFeatureSink.FastInsert) return {self.outGrid: dest_id}