Files
LaGriT/PyLaGriT/examples/stratigraphic_hex_mesh_tutorial.py
2025-12-17 11:00:57 +08:00

480 lines
15 KiB
Python
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# -*- coding: utf-8
# This script is the PyLaGriT version of LaGriT tutorial example at
# https://lanl.github.io/LaGriT/pages/tutorial/stratigraphy/index.html.
# Written by Guoyan Jiang (gyjiang@whu.edu.cn) with technical support
# from Dylan Harp (dharp@lanl.gov) and Terry Miller (tamiller@lanl.gov).
# Import PyLaGriT class from pylagrit module
from pylagrit import PyLaGriT
import numpy
# Variables
maxX = 4000 # Max value in x direction
maxY = 4000 # Max value in y direction
maxZ = 3000 # Max value in z direction
numX = 51 # Number of points in x direction
numY = 51 # Number of points in y direction
numZ = 26 # Number of points in z direction
# Create PyLaGriT object
# This assumes that pylagritrc is being used so that lagrit_exe option does not need to be specified
lg = PyLaGriT()
#********************************************
# 01 Built HEX Mesh
#********************************************
# Create mesh object
mo = lg.create_hex()
mo.createpts_brick_xyz((numX, numY, numZ), (0,0,0), (maxX, maxY, maxZ))
# Save the mesh object
mo.dump('tmp_hex_01.inp')
# Set vertices (imt) and cells (itetlcr) to 1
mo.setatt('imt', 1)
mo.setatt('itetclr', 1)
# Set node type from connectivity of mesh
mo.resetpts_itp()
#********************************************
# 02 Use psets to identify (for setting boundary conditions,
# initial conditions, etc.) a set of vertices on the top
# surface of the mesh
#********************************************
# Create a pset named p_top, which contains all nodes (stride = 1 0 0)
# where the nodes Z value (zic) is greater than or equal to (ge) the top of the mesh (maxZ)
pset0 = mo.pset_attribute('zic', maxZ, 'ge', (1,0,0), 'p_top')
# Define three cylindrical objects
pset1 = mo.pset_geom((0,0,-1), (1100,360,10000), (1500,1500,0), 'rtz', (1,0,0), 'p_circle1')
pset2 = mo.pset_geom((0,0,-1), (1100,360,10000), (2500,2500,0), 'rtz', (1,0,0), 'p_circle2')
pset3 = mo.pset_geom((0,0,-1), (1100,360,10000), (2500,1500,0), 'rtz', (1,0,0), 'p_circle3')
# Intersect four psets, points belonging to the union of all given sets are preserved into the pset p_region
pset4 = mo.pset_inter([pset0, pset1, pset2, pset3], 'p_region')
# Map psets to an attribute
mo.addatt('id_top_region', vtype='vint', rank='scalar') # Creat a node-based attribute id_top_region within the mesh object
mo.setatt('id_top_region', 1) #Fill the entire attribute with 1
pset1.setatt('id_top_region', 2) #Color all nodes in the pset p_circle1 with the value 2
pset2.setatt('id_top_region', 3)
pset3.setatt('id_top_region', 4)
pset4.setatt('id_top_region', 5)
# Release the psets from memory
pset0.delete()
pset1.delete()
pset2.delete()
pset3.delete()
pset4.delete()
#********************************************
# 03 Build some surfaces to define stratigraphy.
# In a real model, the surfaces would come from some geologic framework model
# and would define geologic or hydro-geologic horizons and topography.
#********************************************
mosurf1 = lg.create_qua() # Create the top surface
p1 = (-20, -20, 1000)
p2 = (4020, -20, 1500)
p3 = (4020, 4020, 2500)
p4 = (-20, 4020, 500)
pts = [p1, p2, p3, p4]
nnodes = (numX, numY, 1)
mosurf1.quadxy(nnodes, pts)
#mosurf1.paraview()
mosurf1.minmax_xyz()
mosurf1.dump('tmp_surf1_quad.inp')
mosurf2 = lg.create_qua() # Create the bottom surface
p1 = (-20, -20, 1800)
p2 = (4020, -20, 2100)
p3 = (4020, 4020, 2800)
p4 = (-20, 4020, 800)
pts = [p1, p2, p3, p4]
nnodes = (numX, numY, 1)
mosurf2.quadxy(nnodes, pts)
#mosurf2.paraview()
mosurf2.minmax_xyz()
mosurf2.dump('tmp_surf2_quad.inp')
#********************************************
# 04 Use the surfaces to define regions and set
# vertex and cell ids
#********************************************
# Define Regions
sf1 = mosurf1.surface('sf1')
sf2 = mosurf2.surface('sf2')
r1 = mo.region('le ' + str(sf1))
r2 = mo.region('gt ' + str(sf1) + ' and ' + 'le ' + str(sf2))
r3 = mo.region('gt ' + str(sf2))
mosurf1.delete()
mosurf2.delete()
# Create Eltsets and PSets from Regions
pset1 = mo.pset_region(r1)
pset2 = mo.pset_region(r2)
pset3 = mo.pset_region(r3)
eltset1 = mo.eltset_region(r1)
eltset2 = mo.eltset_region(r2)
eltset3 = mo.eltset_region(r3)
#Set Attributes from Eltsets and PSets
pset1.setatt('imt', 1)
pset2.setatt('imt', 2)
pset3.setatt('imt', 3)
eltset1.setatt('itetclr', 1)
eltset2.setatt('itetclr', 2)
eltset3.setatt('itetclr', 3)
#********************************************
# 05 Build a fault surface and define stratigraphy
# on each side of the fault
#********************************************
# Create fault surface and surfaces to either side of fault
mosurf1_fminus = lg.create_qua()
p1 = (-20, -20, 1000)
p2 = (4020, -20, 1500)
p3 = (4020, 4020, 2500)
p4 = (-20, 4020, 500)
pts = [p1, p2, p3, p4]
nnodes = (numX, numY, 1)
mosurf1_fminus .quadxy(nnodes, pts)
#mosurf1_fminus .paraview()
mosurf1_fminus .minmax_xyz()
mosurf1_fminus .dump('tmp_s1_fm.inp')
mosurf2_fminus = lg.create_qua()
p1 = (-20, -20, 1800)
p2 = (4020, -20, 2100)
p3 = (4020, 4020, 2800)
p4 = (-20, 4020, 800)
pts = [p1, p2, p3, p4]
nnodes = (numX, numY, 1)
mosurf2_fminus.quadxy(nnodes, pts)
#mosurf2_fminus.paraview()
mosurf2_fminus.minmax_xyz()
mosurf2_fminus.dump('tmp_s2_fm.inp')
mosurf1_fplus = lg.create_qua()
p1 = (-20, -20, 1400)
p2 = (4020, -20, 1900)
p3 = (4020, 4020, 2900)
p4 = (-20, 4020, 900)
pts = [p1, p2, p3, p4]
nnodes = (numX, numY, 1)
mosurf1_fplus.quadxy(nnodes, pts)
#mosurf1_fplus.paraview()
mosurf1_fplus.minmax_xyz()
mosurf1_fplus.dump('mosurf1_fplus.inp')
mosurf2_fplus = lg.create_qua()
p1 = (-20, -20, 2200)
p2 = (4020, -20, 2500)
p3 = (4020, 4020, 3200)
p4 = (-20, 4020, 1200)
pts = [p1, p2, p3, p4]
nnodes = (numX, numY, 1)
mosurf2_fplus.quadxy(nnodes, pts)
#mosurf2_fplus.paraview()
mosurf2_fplus.minmax_xyz()
mosurf2_fplus.dump('mosurf2_fplus.inp')
mosurf_fault = lg.create_qua()
p1 = (-20, -20, -1.e4)
p2 = (4020, -20, -1.e4)
p3 = (4020, 4020, 1.e4)
p4 = (-20, 4020, 1.e4)
pts = [p1, p2, p3, p4]
nnodes = (numX, numY, 1)
mosurf_fault.quadxy(nnodes, pts)
#mosurf_fault.paraview()
mosurf_fault.minmax_xyz()
mosurf_fault.dump('mosurf_fault.inp')
# Define geometry of hydrostratigraphic model
sf1_fm = mosurf1_fminus.surface('sf1_fm')
sf2_fm = mosurf2_fminus.surface('sf2_fm')
sf1_fp = mosurf1_fplus.surface('sf1_fp')
sf2_fp = mosurf2_fplus.surface('sf2_fp')
sf_f = mosurf_fault.surface('sf_f')
r1_fm = mo.region('le ' + str(sf1_fm) + ' and ' + 'le ' + str(sf_f))
r2_fm = mo.region('gt ' + str(sf1_fm) + ' and ' + 'le ' + str(sf2_fm) + ' and ' + 'le ' + str(sf_f))
r3_fm = mo.region('gt ' + str(sf2_fm) + ' and ' + 'le ' + str(sf_f))
r1_fp = mo.region('le ' + str(sf1_fp) + ' and ' + 'gt ' + str(sf_f))
r2_fp = mo.region('gt ' + str(sf1_fp) + ' and ' + 'le ' + str(sf2_fp) + ' and ' + 'gt ' + str(sf_f))
r3_fp = mo.region('gt ' + str(sf2_fp) + ' and ' + 'gt ' + str(sf_f))
mosurf1_fminus.delete()
mosurf2_fminus.delete()
mosurf1_fplus.delete()
mosurf2_fplus.delete()
mosurf_fault.delete()
# Set fault node and element materials
pset1_fm = mo.pset_region(r1_fm)
pset2_fm = mo.pset_region(r2_fm)
pset3_fm = mo.pset_region(r3_fm)
pset1_fp = mo.pset_region(r1_fp)
pset2_fp = mo.pset_region(r2_fp)
pset3_fp = mo.pset_region(r3_fp)
eltset1_fm = mo.eltset_region(r1_fm)
eltset2_fm = mo.eltset_region(r2_fm)
eltset3_fm = mo.eltset_region(r3_fm)
eltset1_fp = mo.eltset_region(r1_fp)
eltset2_fp = mo.eltset_region(r2_fp)
eltset3_fp = mo.eltset_region(r3_fp)
#Set Attributes from Eltsets and PSets
mo.setatt('imt', 7)
mo.setatt('itetclr', 7)
pset1_fm.setatt('imt', 1)
pset2_fm.setatt('imt', 2)
pset3_fm.setatt('imt', 3)
pset1_fp.setatt('imt', 4)
pset2_fp.setatt('imt', 5)
pset3_fp.setatt('imt', 6)
eltset1_fm.setatt('itetclr', 1)
eltset2_fm.setatt('itetclr', 2)
eltset3_fm.setatt('itetclr', 3)
eltset1_fp.setatt('itetclr', 4)
eltset2_fp.setatt('itetclr', 5)
eltset3_fp.setatt('itetclr', 6)
#********************************************
# 06 Define a polyline and truncate the exterior boundary of the mesh with the polyline
#********************************************
# Read boundary polygon file
mobndry = lg.read('basin_bnd_ply_rescale.inp')
# Extrude the polyline into a vertical surface
mofence = mobndry.extrude(3200, 'const', 'volume', [0, 0, -1])
mobndry.minmax_xyz()
mofence.minmax_xyz()
# Translate the extrusion to make it cover the vertical extent of the hex mesh
mofence.trans((0, 0, -3100), (0, 0 ,0))
mofence.minmax_xyz()
#mofence.paraview()
#mofence.dump('3D_vertical_surface.inp')
#mo.dump('cube.inp')
# Truncate mesh
sf_bndry = mofence.surface('sf_bndry')
r_bndry = mo.region('ge ' + str(sf_bndry))
pset_bndry = mo.pset_region(r_bndry)
mobndry.delete()
mofence.delete()
# Method 1: Only remove a cell if ALL vertices are outside
e_delete1 = pset_bndry.eltset('exclusive')
# Method 2: Remove a cell if the centroid (average of all vertices) is outside
e_delete2 = mo.eltset_region(r_bndry)
# Method 3: Remove a cell if one or more vertices are outside
e_delete3 = pset_bndry.eltset('inclusive')
#mo.addatt('id_in_out_bndry', vtype='vint', rank='scalar', length='nelements')
mo.add_element_attribute('id_in_out_bndry', vtype='vint')
mo.setatt('id_in_out_bndry', 4) #Fill the entire attribute with 4
e_delete3.setatt('id_in_out_bndry', 3)
e_delete2.setatt('id_in_out_bndry', 2)
e_delete1.setatt('id_in_out_bndry', 1)
eltset4 = mo.eltset_attribute('id_in_out_bndry', 4, 'eq')
eltset3 = mo.eltset_attribute('id_in_out_bndry', 3, 'eq')
#eltset2 = mo.eltset_attribute('id_in_out_bndry', 2, 'eq')
#eltset1 = mo.eltset_attribute('id_in_out_bndry', 1, 'eq')
mo.rmpoint_eltset(eltset4, False, False)
mo.rmpoint_eltset(eltset3, True, True)
#********************************************
# 07 Refine the mesh around the fault
#********************************************
f_zone = mo.intersect_elements(sf_f, 'f_zone')
fz_i = mo.eltset_attribute('f_zone', 0, 'gt') #Non-zero indicates intersection
fz_i.refine()
mo.delatt('f_zone')
mo.status (brief=True)
#sf_f.delete()
#sf1_fm.delete()
#sf2_fm.delete()
#sf1_fp.delete()
#sf1_fp.delete()
#sf_bndry.delete()
#********************************************
# 08 Insert a couple of 'wells' by refining the mesh and identifying a line of nodes
# that will be the well source/sink for boundary conditions.
#********************************************
Well1X = 1234.56
Well1Y = 1987.65
Well2X = 2243.21
Well2Y = 1212.34
Radius = 25
NRadius = 2
#Well 1
mowell1 = lg.create_tet()
mowell1.createpts_rtz((NRadius, 9, numZ), (0, 0, 3100), (Radius, 360, 1500)) #Create a cylindrical point cloud
mowell1.filter() # Filter (delete) points that are too close ( default distance <=1.e-16) or duplicate points
mowell1.rmpoint_compress() # Remove all marked nodes and correct the itet array
mowell1.setatt('imt', 1)
mowell1.connect() # Connect the point cloud
mowell1.resetpts_itp()
mowell1.minmax_xyz()
mowell1.trans((0, 0, 0), (Well1X, Well1Y, 0))
mowell1.minmax_xyz()
#mowell1.paraview()
mowell1.dump('tmp_well1.inp')
#Well 2
mowell2 = lg.create_tet()
mowell2.createpts_rtz((NRadius, 9, numZ), (0, 0, 3100), (Radius, 360, 2200))
mowell2.filter()
mowell2.rmpoint_compress()
mowell2.setatt('imt', 1)
mowell2.connect()
mowell2.resetpts_itp()
mowell2.minmax_xyz()
mowell2.trans((0, 0, 0), (Well2X, Well2Y, 0))
mowell2.minmax_xyz()
#mowell2.paraview()
mowell2.dump('tmp_well2.inp')
# Join the two distinct wells into a single mesh object
mowells = lg.merge([mowell1, mowell2])
mowells.dump('tmp_wells.inp')
#mowells.paraview()
# Refine the mo around the wells
# First pass refinement
w_zone = mo.intersect_elements(mowells, 'w_zone')
wz_i = mo.eltset_attribute('w_zone', 0, 'gt') #Non-zero indicates intersection
wz_i.refine()
mo.setatt('w_zone', 0)
#wz_i.delete()
# Second pass refinement
w_zone = mo.intersect_elements(mowells, 'w_zone')
wz_i = mo.eltset_attribute('w_zone', 0, 'gt') #Non-zero indicates intersection
wz_i.refine()
mo.setatt('w_zone', 0)
#wz_i.delete()
mohex = mo.grid2grid_tree_to_fe() #Quadtree or octree grid to grid
#mo.status (brief=True)
# Identify the column of vertices closest to the well center.
#Well1
mo_pts1 = lg.create()
mo_pts1.createpts_rtz((2, 2, 1000), (0, 0, 3100), (Radius, 360, 2200))
mo_pts1.trans((0, 0, 0), (Well1X, Well1Y, 0))
#Well2
mo_pts2 = lg.create()
mo_pts2.createpts_rtz((2, 2, 1000), (0, 0, 3100), (Radius, 360, 2200))
mo_pts2.trans((0, 0, 0), (Well2X, Well2Y, 0))
mo_pts = lg.merge([mo_pts1, mo_pts2])
mo_pts.filter()
mo_pts.rmpoint_compress()
# Compute a distance field attribute
mo.compute_distance(mo_pts, option='distance_field', attname='dfield_well')
mo_pts1.delete()
mo_pts2.delete()
mo_pts.delete()
mowell1.delete()
mowell2.delete()
mowells.delete()
# Describe all nodes within 32, 16, 8, 4, 2 and 1 meters of the wells.
pwell = mo.pset_attribute('dfield_well', 1.0, 'le', (1,0,0), 'pwell1')
pwell.dump('zone_radius_01.0.zone')
pwell = mo.pset_attribute('dfield_well', 2.0, 'le', (1,0,0), 'pwell2')
pwell.dump('zone_radius_02.0.zone')
pwell = mo.pset_attribute('dfield_well', 4.0, 'le', (1,0,0), 'pwell4')
pwell.dump('zone_radius_04.0.zone')
pwell = mo.pset_attribute('dfield_well', 8.0, 'le', (1,0,0), 'pwell8')
pwell.dump('zone_radius_08.0.zone')
pwell = mo.pset_attribute('dfield_well', 16.0, 'le', (1,0,0), 'pwell16')
pwell.dump('zone_radius_16.0.zone')
pwell = mo.pset_attribute('dfield_well', 32.0, 'le', (1,0,0), 'pwell32')
pwell.dump('zone_radius_32.0.zone')
mo.dump('Hex_mesh.inp')
#********************************************
# 09 Convert hex mesh to tet mesh
#********************************************
motet = mohex.copypts()
motet.setatt('imt', 1)
motet.setatt('itp', 0)
motet.connect(option1='check_interface')
motet.resetpts_itp()
motet.interpolate_voronoi('imt', mohex, 'imt')
motet.interpolate_map('itetclr', mohex, 'itetclr')
#Remove all nodes and elements with imt and itetclr values of 7
motet.rmmat(7)
#pset7 = motet.pset_attribute('imt', 7, 'eq', (1,0,0), 'pset7')
#motet.rmpoint_pset(pset7)
#eltset7 = motet.eltset_attribute('itetclr', 7, 'eq')
#motet.rmpoint_eltset(eltset7, True, True)
motet.rmpoint_compress()
motet.resetpts_itp()
# Visualize connected mesh using ParaView
# This assumes that pylagritrc is being used so that exe option does not need to be specified
#motet.paraview()
motet.dump('Tet_mesh.inp')
#********************************************
# 10 Write tet mesh files for FEHM
# FEHM uses node based materials and properties
#********************************************
#motet.resetpts_parent()
motet.filter()
motet.rmpoint_compress()
motet.resetpts_itp()
motet.minmax('imt')
motet.setatt('itetclr', 1)
#motet. tri_mesh_output_prep()
motet.dump_fehm('Example3')