def PVS_simulation(args): """ Solve the flow and tracer transport in the PVS, assumption flat plates : Outputs : - a logfile with information about the simulation - .pvd files with the u, p and c field at specified args.toutput time period Return : u, p, c 1D array of the u, p, c fields on the middle line """ # output folder name outputfolder = args.output_folder + '/' + args.job_name + '/' if not os.path.exists(outputfolder): os.makedirs(outputfolder) if not os.path.exists(outputfolder + '/profiles'): os.makedirs(outputfolder + '/profiles') if not os.path.exists(outputfolder + '/fields'): os.makedirs(outputfolder + '/fields') # Create output files #txt files csv_p = open(outputfolder + 'profiles' + '/pressure.txt', 'w') csv_u = open(outputfolder + 'profiles' + '/velocity.txt', 'w') csv_c = open(outputfolder + 'profiles' + '/concentration.txt', 'w') csv_rv = open(outputfolder + 'profiles' + '/radius.txt', 'w') #pvd files uf_out, pf_out = File(outputfolder + 'fields' + '/uf.pvd'), File(outputfolder + 'fields' + '/pf.pvd') c_out = File(outputfolder + 'fields' + '/c.pvd') facets_out = File(outputfolder + 'fields' + '/facets.pvd') # Create logger logger = logging.getLogger() logger.setLevel(logging.INFO) # log to a file now = datetime.now().strftime("%Y%m%d_%H%M%S") filename = os.path.join(outputfolder + '/', 'PVS_info.log') file_handler = logging.FileHandler(filename, mode='w') file_handler.setLevel(logging.INFO) #formatter = logging.Formatter("%(asctime)s %(filename)s, %(lineno)d, %(funcName)s: %(message)s") #file_handler.setFormatter(formatter) logger.addHandler(file_handler) # log to the console console_handler = logging.StreamHandler() level = logging.INFO console_handler.setLevel(level) logger.addHandler(console_handler) # initialise logging logging.info( ' ______ ______ _ _ _ _ \n' ) logging.info( '| _ \ \ / / ___| ___(_)_ __ ___ _ _| | __ _| |_(_) ___ _ __ \n' ) logging.info( "| |_) \ \ / /\___ \ / __| | '_ ` _ \| | | | |/ _` | __| |/ _ \| '_ \ \n" ) logging.info( '| __/ \ V / ___) | \__ \ | | | | | | |_| | | (_| | |_| | (_) | | | |\n' ) logging.info( '|_| \_/ |____/ |___/_|_| |_| |_|\__,_|_|\__,_|\__|_|\___/|_| |_|\n\n' ) logging.info( title1( "Simulation of the PVS flow and tracer transport using steady stokes solver and diffusion-advection solver. Flat plates geometry" )) logging.info("Date and time:" + datetime.now().strftime("%m/%d/%Y, %H:%M:%S")) logging.info('Job name : ' + args.job_name) logging.debug('logging initialized') # Set parameters logging.info(title1("Parameters")) # Geometry params logging.info('\n * Geometry') Rv = args.radius_vessel # centimeters Rpvs = args.radius_pvs # centimeters L = args.length # centimeters Rsas = args.radius_sas + Rv Lsas = args.length_sas logging.info('Vessel radius : %e cm' % Rv) logging.info('PVS radius : %e cm' % Rpvs) logging.info('PVS length : %e cm' % L) logging.info('SAS length : %e cm' % Lsas) logging.info('SAS radius : %e cm' % Rsas) logging.info('\n * Cross section area deformation parameters') ai = args.ai fi = args.fi phii = args.phii logging.info('ai (dimensionless): ' + '%e ' * len(ai) % tuple(ai)) logging.info('fi (Hz) : ' + '%e ' * len(fi) % tuple(fi)) logging.info('phii (rad) : ' + '%e ' * len(phii) % tuple(phii)) #Mesh logging.info('\n * Mesh') #number of cells in the radial direction Nr = args.N_radial DR = (Rpvs - Rv) / Nr logging.info('N radial : %e' % Nr) #time parameters logging.info('\n * Time') toutput = args.toutput tfinal = args.tend dt = args.time_step logging.info('final time: %e s' % tfinal) logging.info('output period : %e s' % toutput) logging.info('time step : %e s' % dt) # approximate CFL for fluid solver : need to compute max velocity depending on the wall displacement... # maybe just add a warning in computation with actual velocity #Uapprox=500e-4 #upper limit for extected max velocity #CFL_dt=0.25*DY/Uapprox #if CFL_dt < dt : # logging.warning('The specified time step of %.2e s does not fullfil the fluid CFL condition. New fluid time step : %.2e s'%(dt, CFL_dt)) #dt_fluid=min(dt,CFL_dt) dt_fluid = dt # approximate CFL for tracer solver dt_advdiff = dt # material parameters logging.info('\n * Fluid properties') mu = args.viscosity rho = args.density logging.info('density: %e g/cm3' % rho) logging.info('dynamic viscosity : %e dyn s/cm2' % mu) logging.info('\n* Tracer properties') D = args.diffusion_coef sigma_gauss = args.sigma logging.info('Free diffusion coef: %e cm2/s' % D) logging.info('STD of initial gaussian profile: %e ' % sigma_gauss) xi_gauss = args.initial_pos logging.info('Initial position: %e cm2' % xi_gauss) logging.info('\n * ALE') kappa = args.ale_parameter logging.info('ALE parameter: %e ' % kappa) logging.info('\n * Lateral BC') resistance = args.resistance logging.info('inner resistance: %e ' % resistance) if resistance == 0: lateral_bc = 'free' logging.info('right BC will be set to the free assumption') elif resistance < 0: lateral_bc = 'noflow' logging.info('right BC will be set to the no flow assumption') else: lateral_bc = 'resistance' logging.info('right BC will be set to the resistance assumption') fluid_parameters = {'mu': mu, 'rho': rho, 'dt': dt_fluid} tracer_parameters = {'kappa': D, 'dt': dt_advdiff} ale_parameters = {'kappa': kappa} # Mesh logging.info(title1('Meshing')) logging.info('cell size : %e cm' % (DR)) from sleep.mesh import mesh_model2d, load_mesh2d, set_mesh_size import sys gmsh.initialize(['', '-format', 'msh2']) model = gmsh.model import math Apvs0 = math.pi * Rpvs**2 Av0 = math.pi * Rv**2 A0 = Apvs0 - Av0 # progressive mesh factory = model.occ a = factory.addPoint(-Lsas, Rv, 0) b = factory.addPoint(L, Rv, 0) c = factory.addPoint(L, Rpvs, 0) d = factory.addPoint(0, Rpvs, 0) e = factory.addPoint(0, Rsas, 0) f = factory.addPoint(-Lsas, Rsas, 0) fluid_lines = [ factory.addLine(*p) for p in ((a, b), (b, c), (c, d), (d, e), (e, f), (f, a)) ] named_lines = dict( zip(('bottom', 'pvs_right', 'pvs_top', 'brain_surf', 'sas_top', 'sas_left'), fluid_lines)) fluid_loop = factory.addCurveLoop(fluid_lines) fluid = factory.addPlaneSurface([fluid_loop]) factory.synchronize() model.addPhysicalGroup(2, [fluid], 1) for name in named_lines: tag = named_lines[name] model.addPhysicalGroup(1, [tag], tag) # boxes for mesh refinement cell_size = DR * (Rpvs - Rv) / (Rpvs - Rv) boxes = [] # add box on the PVS for mesh field = model.mesh.field fid = 1 field.add('Box', fid) field.setNumber(fid, 'XMin', 0) field.setNumber(fid, 'XMax', L) field.setNumber(fid, 'YMin', Rv) field.setNumber(fid, 'YMax', Rpvs) field.setNumber(fid, 'VIn', cell_size) field.setNumber(fid, 'VOut', DR * 50) field.setNumber(fid, 'Thickness', Rsas) boxes.append(fid) # Combine field.add('Min', 2) field.setNumbers(2, 'FieldsList', boxes) field.setAsBackgroundMesh(2) model.occ.synchronize() h5_filename = outputfolder + '/mesh.h5' tags = {'cell': {'F': 1}, 'facet': {}} mesh_model2d(model, tags, h5_filename) mesh_f, markers, lookup = load_mesh2d(h5_filename) gmsh.finalize() # todo : how to know nb of cells ? #logging.info('nb cells: %i'%(Nl*Nr*2)) fluid_bdries = MeshFunction("size_t", mesh_f, mesh_f.topology().dim() - 1, 0) # Label facets xy = mesh_f.coordinates().copy() x, y = xy.T xmin = x.min() xmax = x.max() ymin = y.min() ymax = y.max() tol = cell_size / 2 #cm class Boundary_sas_left(SubDomain): def inside(self, x, on_boundary): return on_boundary and near(x[0], -Lsas, tol) # downstream class Boundary_pvs_right(SubDomain): def inside(self, x, on_boundary): return on_boundary and near(x[0], L, tol) class Boundary_sas_top(SubDomain): def inside(self, x, on_boundary): return on_boundary and near(x[1], Rsas, tol) and (x[0] < tol) class Boundary_pvs_top(SubDomain): def inside(self, x, on_boundary): return on_boundary and near(x[1], Rpvs, tol) and (x[0] > -tol) # brain class Boundary_brainsurf(SubDomain): def inside(self, x, on_boundary): return on_boundary and near(x[0], 0, tol) and (x[1] > Rpvs - tol) class Boundary_bottom(SubDomain): def inside(self, x, on_boundary): return on_boundary and near(x[1], ymin, tol) btop_sas = Boundary_sas_top() btop_pvs = Boundary_pvs_top() bbottom = Boundary_bottom() bvert_left = Boundary_sas_left() bvert_brain = Boundary_brainsurf() bvert_right = Boundary_pvs_right() btop_sas.mark(fluid_bdries, 1) btop_pvs.mark(fluid_bdries, 2) bbottom.mark(fluid_bdries, 3) bvert_left.mark(fluid_bdries, 4) bvert_brain.mark(fluid_bdries, 5) bvert_right.mark(fluid_bdries, 6) facet_lookup = { 'sas_top': 1, 'pvs_top': 2, 'vessel': 3, 'sas_left': 4, 'brain_surf': 5, 'pvs_right': 6 } facets_out << fluid_bdries #FEM space logging.info(title1("Set FEM spaces")) logging.info('\n * Fluid') Vf_elm = VectorElement('Lagrange', triangle, 2) Qf_elm = FiniteElement('Lagrange', triangle, 1) Wf_elm = MixedElement([Vf_elm, Qf_elm]) Wf = FunctionSpace(mesh_f, Wf_elm) logging.info('Velocity : "Lagrange", triangle, 2') logging.info('Pressure : "Lagrange", triangle, 1') logging.info('\n * Tracer') Ct_elm = FiniteElement('Lagrange', triangle, 1) Ct = FunctionSpace(mesh_f, Ct_elm) logging.info('Concentration : "Lagrange", triangle, 1') logging.info('\n * ALE') Va_elm = VectorElement('Lagrange', triangle, 1) Va = FunctionSpace(mesh_f, Va_elm) logging.info('ALE displacement: "Lagrange", triangle, 1') # Setup of boundary conditions logging.info(title1("Boundary conditions")) import sympy tn = sympy.symbols("tn") tnp1 = sympy.symbols("tnp1") sin = sympy.sin sqrt = sympy.sqrt functionR = Rpvs - (Rpvs - Rv) * (1 + sum( [a * sin(2 * pi * f * tn + phi) for a, f, phi in zip(ai, fi, phii)])) # displacement bottom plate R_vessel = sympy.printing.ccode(functionR) functionV = sympy.diff(functionR, tn) # velocity V_vessel = sympy.printing.ccode(functionV) #Delta U for ALE. I dont really like this functionUALE = -(Rpvs - Rv) * (1 + sum([ a * sin(2 * pi * f * tnp1 + phi) for a, f, phi in zip(ai, fi, phii) ])) + (Rpvs - Rv) * (1 + sum( [a * sin(2 * pi * f * tn + phi) for a, f, phi in zip(ai, fi, phii)])) UALE_vessel = sympy.printing.ccode(functionUALE) vf_bottom = Expression(('0', V_vessel), tn=0, degree=2) # no slip no gap condition at vessel wall uale_bottom = Expression(('0', UALE_vessel), tn=0, tnp1=1, degree=2) # displacement for ALE at vessel wall logging.info('\n * Lateral assumption') logging.info(lateral_bc) logging.info('\n * Fluid') logging.info('Left : zero pressure') if lateral_bc == 'free': logging.info('Right : zero pressure') elif lateral_bc == 'resistance': logging.info('Right : resistance') else: logging.info('Right : no flow') logging.info('Top : no slip no gap fixed wall') logging.info('Bottom : no slip no gap moving wall') logging.info('\n * Tracer concentration') logging.info('Left : zero concentration') if lateral_bc == 'free': logging.info('Right : zero concentration') else: logging.info('Right : no flux') logging.info('Top : no flux') logging.info('Bottom : no flux') logging.info('\n * ALE') logging.info('Left : no flux') logging.info('Right : no flux') logging.info('Top : no displacement') logging.info('Bottom : vessel displacement') # Now we wire up if lateral_bc == 'free': bcs_fluid = { 'velocity': [(facet_lookup['vessel'], vf_bottom), (facet_lookup['sas_left'], Constant((0, 0))), (facet_lookup['brain_surf'], Constant((0, 0))), (facet_lookup['pvs_top'], Constant((0, 0)))], 'traction': [], 'pressure': [(facet_lookup['sas_top'], Constant(0)), (facet_lookup['pvs_right'], Constant(0))] } elif lateral_bc == 'resistance': Rpressure = Expression('R*Q+p0', R=resistance, Q=0, p0=0, degree=1) # # Compute pressure to impose according to the flow at previous time step and resistance. bcs_fluid = { 'velocity': [(facet_lookup['vessel'], vf_bottom), (facet_lookup['sas_left'], Constant((0, 0))), (facet_lookup['brain_surf'], Constant((0, 0))), (facet_lookup['pvs_top'], Constant((0, 0)))], 'traction': [], 'pressure': [(facet_lookup['sas_top'], Constant(0)), (facet_lookup['pvs_right'], Rpressure)] } else: bcs_fluid = { 'velocity': [(facet_lookup['vessel'], vf_bottom), (facet_lookup['sas_left'], Constant((0, 0))), (facet_lookup['brain_surf'], Constant((0, 0))), (facet_lookup['pvs_top'], Constant((0, 0))), (facet_lookup['pvs_right'], Constant( (0, 0)))], # would be more correct to impose only on u x 'traction': [], 'pressure': [(facet_lookup['sas_top'], Constant(0))] } if lateral_bc == 'free': bcs_tracer = { 'concentration': [(facet_lookup['sas_top'], Constant(0)), (facet_lookup['pvs_right'], Constant(0))], 'flux': [(facet_lookup['vessel'], Constant(0)), (facet_lookup['sas_left'], Constant(0)), (facet_lookup['brain_surf'], Constant(0)), (facet_lookup['pvs_top'], Constant(0))] } else: bcs_tracer = { 'concentration': [(facet_lookup['sas_top'], Constant(0))], 'flux': [(facet_lookup['vessel'], Constant(0)), (facet_lookup['sas_left'], Constant(0)), (facet_lookup['brain_surf'], Constant(0)), (facet_lookup['pvs_top'], Constant(0)), (facet_lookup['pvs_right'], Constant(0))] } bcs_ale = { 'dirichlet': [(facet_lookup['vessel'], uale_bottom), (facet_lookup['sas_top'], Constant((0, 0))), (facet_lookup['pvs_top'], Constant((0, 0))), (facet_lookup['brain_surf'], Constant((0, 0)))], 'neumann': [(facet_lookup['sas_left'], Constant((0, 0))), (facet_lookup['pvs_right'], Constant((0, 0)))] } # We collect the time dependent BC for update driving_expressions = (uale_bottom, vf_bottom) # Initialisation : logging.info(title1("Initialisation")) # We start at a time shift tshift = -1 / 4 / fi[0] ##todo : modify to be compatible with phii # Update boundary conditions for expr in driving_expressions: hasattr(expr, 'tn') and setattr(expr, 'tn', 0) hasattr(expr, 'tnp1') and setattr(expr, 'tnp1', tshift) #Solve ALE and move mesh eta_f = solve_ale(Va, f=Constant((0, 0)), bdries=fluid_bdries, bcs=bcs_ale, parameters=ale_parameters) ALE.move(mesh_f, eta_f) mesh_f.bounding_box_tree().build(mesh_f) logging.info("\n * Fluid") logging.info("Velocity : zero field") logging.info("Pressure : zero field") uf_n = project(Constant((0, 0)), Wf.sub(0).collapse()) pf_n = project(Constant(0), Wf.sub(1).collapse()) logging.info("\n * Tracer") logging.info("Concentration : Gaussian profile") logging.info(" Centered at mid length") logging.info(" STD parameter = %e" % sigma_gauss) c_0 = Expression('exp(-a*pow(x[0]-b, 2)) ', degree=1, a=1 / 2 / sigma_gauss**2, b=xi_gauss) c_n = project(c_0, Ct) # Save initial state uf_n.rename("uf", "tmp") pf_n.rename("pf", "tmp") c_n.rename("c", "tmp") uf_out << (uf_n, 0) pf_out << (pf_n, 0) c_out << (c_n, 0) # Get the 1 D profiles at umax (to be changed in cyl coordinate) mesh_points = mesh_f.coordinates() x = mesh_points[:, 0] y = mesh_points[:, 1] xmin = 0 xmax = L ymin = min(y) ymax = Rpvs files = [csv_p, csv_u, csv_c] fields = [pf_n, uf_n.sub(0), c_n] field_names = [ 'pressure (dyn/cm2)', 'axial velocity (cm/s)', 'concentration' ] for csv_file, field, name in zip(files, fields, field_names): #values = line_sample(slice_line, field) values = profile(field, xmin, xmax, ymin, ymax) logging.info('Max ' + name + ' : %.2e' % max(abs(values))) #logging.info('Norm '+name+' : %.2e'%field.vector().norm('linf')) row = [0] + list(values) csv_file.write(('%e' + ', %e' * len(values) + '\n') % tuple(row)) csv_rv.write('%e, %e' % (0, ymin)) ############# RUN ############3 logging.info(title1("Run")) # Time loop ### We start at the quarter of the period so that velocity=0 print('tini = ', tshift) time = tshift timestep = 0 # Here I dont know if there will be several dt for advdiff and fluid solver while time < tfinal + tshift: # Update boundary conditions for expr in driving_expressions: hasattr(expr, 'tn') and setattr(expr, 'tn', time) hasattr(expr, 'tnp1') and setattr(expr, 'tnp1', time + dt) if lateral_bc == 'resistance': z, r = SpatialCoordinate(mesh_f) ds = Measure('ds', domain=mesh_f, subdomain_data=fluid_bdries) n = FacetNormal(mesh_f) Flow = assemble(2 * pi * r * dot(uf_n, n) * ds(facet_lookup['x_max'])) print('Right outflow : %e \n' % Flow) setattr(Rpressure, 'Q', Flow) #Solve ALE and move mesh eta_f = solve_ale(Va, f=Constant((0, 0)), bdries=fluid_bdries, bcs=bcs_ale, parameters=ale_parameters) ALE.move(mesh_f, eta_f) mesh_f.bounding_box_tree().build(mesh_f) # Solve fluid problem uf_, pf_ = solve_fluid(Wf, u_0=uf_n, f=Constant((0, 0)), bdries=fluid_bdries, bcs=bcs_fluid, parameters=fluid_parameters) tracer_parameters["T0"] = time tracer_parameters["nsteps"] = 1 tracer_parameters["dt"] = dt # Solve tracer problem c_, T0 = solve_adv_diff(Ct, velocity=uf_ - eta_f / Constant(dt), phi=Constant(1), f=Constant(0), c_0=c_n, phi_0=Constant(1), bdries=fluid_bdries, bcs=bcs_tracer, parameters=tracer_parameters) # Update current solution uf_n.assign(uf_) pf_n.assign(pf_) c_n.assign(c_) #Update time time += dt timestep += 1 # Save output if (timestep % int(toutput / dt) == 0): logging.info("\n*** save output time %e s" % (time - tshift)) logging.info("number of time steps %i" % timestep) # may report Courant number or other important values that indicate how is doing the run uf_.rename("uf", "tmp") pf_.rename("pf", "tmp") c_.rename("c", "tmp") uf_out << (uf_, time - tshift) pf_out << (pf_, time - tshift) c_out << (c_, time - tshift) # Get the 1 D profiles at umax (to be changed in cyl coordinate) mesh_points = mesh_f.coordinates() x = mesh_points[:, 0] y = mesh_points[:, 1] xmin = 0 xmax = L ymin = min(y) ymax = Rpvs #slice_line = line([xmin,(ymin+ymax)/2],[xmax,(ymin+ymax)/2], 100) logging.info('Rpvs : %e' % ymax) logging.info('Rvn : %e' % ymin) files = [csv_p, csv_u, csv_c] fields = [pf_n, uf_n.sub(0), c_n] field_names = [ 'pressure (dyn/cm2)', 'axial velocity (cm/s)', 'concentration' ] for csv_file, field, name in zip(files, fields, field_names): #values = line_sample(slice_line, field) values = profile(field, xmin, xmax, ymin, ymax) logging.info('Max ' + name + ' : %.2e' % max(abs(values))) #logging.info('Norm '+name+' : %.2e'%field.vector().norm('linf')) row = [time - tshift] + list(values) csv_file.write( ('%e' + ', %e' * len(values) + '\n') % tuple(row)) csv_rv.write('%e, %e' % (time - tshift, ymin))
def PVS_simulation(args): """ Solve the flow and tracer transport in the PVS : Outputs : - a logfile with information about the simulation - .pvd files with the u, p and c field at specified args.toutput time period Return : u, p, c 1D array of the u, p, c fields on the middle line """ # output folder name outputfolder = args.output_folder + '/' + args.job_name + '/' if not os.path.exists(outputfolder): os.makedirs(outputfolder) #if not os.path.exists(outputfolder+'profiles'): # os.makedirs(outputfolder+'profiles') if not os.path.exists(outputfolder + 'fields'): os.makedirs(outputfolder + 'fields') # Create output files #txt files csv_p = open(args.output_folder + '/' + args.job_name + '_pressure.txt', 'w') csv_u = open(args.output_folder + '/' + args.job_name + '_velocity.txt', 'w') csv_c = open( args.output_folder + '/' + args.job_name + '_concentration.txt', 'w') csv_rv = open(args.output_folder + '/' + args.job_name + '_radius.txt', 'w') csv_mass = open(args.output_folder + '/' + args.job_name + '_mass.txt', 'w') #pvd files uf_out, pf_out = File(outputfolder + 'fields' + '/uf.pvd'), File(outputfolder + 'fields' + '/pf.pvd') c_out = File(outputfolder + 'fields' + '/c.pvd') facets_out = File(outputfolder + 'fields' + '/facets.pvd') # Create logger logger = logging.getLogger() logger.setLevel(logging.INFO) # log to a file now = datetime.now().strftime("%Y%m%d_%H%M%S") filename = args.output_folder + '/' + args.job_name + '_PVSinfo.log' file_handler = logging.FileHandler(filename, mode='w') file_handler.setLevel(logging.INFO) #formatter = logging.Formatter("%(asctime)s %(filename)s, %(lineno)d, %(funcName)s: %(message)s") #file_handler.setFormatter(formatter) logger.addHandler(file_handler) # log to the console console_handler = logging.StreamHandler() level = logging.INFO console_handler.setLevel(level) logger.addHandler(console_handler) # initialise logging # initialise logging logging.info( ' ______ ______ _ _ _ _ ' ) logging.info( '| _ \ \ / / ___| ___(_)_ __ ___ _ _| | __ _| |_(_) ___ _ __ ' ) logging.info( "| |_) \ \ / /\___ \ / __| | '_ ` _ \| | | | |/ _` | __| |/ _ \| '_ \ " ) logging.info( '| __/ \ V / ___) | \__ \ | | | | | | |_| | | (_| | |_| | (_) | | | |' ) logging.info( '|_| \_/ |____/ |___/_|_| |_| |_|\__,_|_|\__,_|\__|_|\___/|_| |_|\n' ) logging.info( title1( "Simulation of the PVS flow and tracer transport using stoke solver and diffusion-advection solver" )) logging.info("Date and time:" + datetime.now().strftime("%m/%d/%Y, %H:%M:%S")) logging.info('Job name : ' + args.job_name) logging.debug('logging initialized') # Set parameters logging.info(title1("Parameters")) # Geometry params logging.info('\n * Geometry') Rv = args.radius_vessel # centimeters Rpvs = args.radius_pvs # centimeters L = args.length # centimeters logging.info('Vessel radius : %e cm' % Rv) logging.info('PVS radius : %e cm' % Rpvs) logging.info('PVS length : %e cm' % L) # test presence of the SAS compartment on the mesh isSAS = args.SAS if isSAS: logging.info('Add a SAS compartment on the left') Rsas = args.radius_sas + Rv Lsas = args.length_sas logging.info('SAS length : %e cm' % Lsas) logging.info('SAS radius : %e cm' % Rsas) #Mesh logging.info('\n * Mesh') #number of cells in the radial direction Nr = args.N_radial DR = (Rpvs - Rv) / Nr #number of cells in the axial direction if args.N_axial: Nl = args.N_axial else: Nl = round(L / DR) DY = L / Nl logging.info('N axial : %i' % Nl) logging.info('N radial : %e' % Nr) #time parameters logging.info('\n * Time') toutput = args.toutput tfinal = args.tend dt = args.time_step logging.info('final time: %e s' % tfinal) logging.info('output period : %e s' % toutput) logging.info('time step : %e s' % dt) # approximate CFL for fluid solver : need to compute max velocity depending on the wall displacement... # maybe just add a warning in computation with actual velocity #Uapprox=500e-4 #upper limit for extected max velocity #CFL_dt=0.25*DY/Uapprox #if CFL_dt < dt : # logging.warning('The specified time step of %.2e s does not fullfil the fluid CFL condition. New fluid time step : %.2e s'%(dt, CFL_dt)) #dt_fluid=min(dt,CFL_dt) dt_fluid = dt # approximate CFL for tracer solver dt_advdiff = dt # material parameters logging.info('\n * Fluid properties') mu = args.viscosity rho = args.density logging.info('density: %e g/cm3' % rho) logging.info('dynamic viscosity : %e dyn s/cm2' % mu) logging.info('\n* Tracer properties') D = args.diffusion_coef sigma_gauss = args.sigma logging.info('Free diffusion coef: %e cm2/s' % D) logging.info('STD of initial gaussian profile: %e ' % sigma_gauss) xi_gauss = args.initial_pos logging.info('Initial position: %e cm2' % xi_gauss) logging.info('\n * ALE') kappa = args.ale_parameter logging.info('ALE parameter: %e ' % kappa) logging.info('\n * Lateral BC') resistance = args.resistance logging.info('inner resistance: %e ' % resistance) if resistance == 0: lateral_bc = 'free' logging.info('right BC will be set to the free assumption') elif resistance < 0: lateral_bc = 'noflow' logging.info('right BC will be set to the no flow assumption') else: lateral_bc = 'resistance' logging.info('right BC will be set to the resistance assumption') fluid_parameters = {'mu': mu, 'rho': rho, 'dt': dt_fluid} tracer_parameters = {'kappa': D, 'dt': dt_advdiff} ale_parameters = {'kappa': kappa} # Setup of boundary conditions logging.info(title1("Boundary conditions")) logging.info('\n * Cross section area parameters') if args.cycle: logging.info('frequency and amplitude data from cycle ' + args.cycle) timedependentfa = True else: timedependentfa = False ai = args.ai fi = args.fi phii = args.phii logging.info('ai (dimensionless): ' + '%e ' * len(ai) % tuple(ai)) logging.info('fi (Hz) : ' + '%e ' * len(fi) % tuple(fi)) logging.info('phii (rad) : ' + '%e ' * len(phii) % tuple(phii)) if timedependentfa: ## logging.info('creation of cycle') cycleObj = ReadCycle('../stages/cycles.yml', args.cycle) totalcycletime = np.sum(cycleObj.durations) spantime, listspana, listspanf, spanRv, spanh0, spanRpvs = cycleObj.generatedata( int(tfinal / totalcycletime) + 1) logging.info('*** Simulation of %s cycle ' % cycleObj.name) # adjust last time in order to be able to interpolate #spantime[-1]=max(tfinal+2*dt,spantime[-1]) from scipy.interpolate import interp1d varflist = {} varalist = {} for freq in listspanf: varflist[freq] = interp1d(spantime, listspanf[freq]) varalist[freq] = interp1d(spantime, listspana[freq]) varh0 = interp1d(spantime, spanh0) varRpvs = interp1d(spantime, spanRpvs) #varda=interp1d(spantime,dadt,kind="previous") fs = 1 / dt # test if same result if multiplying by 10 ? time = 0 + np.arange( int((tfinal + 2 * dt) * fs) ) / fs # longer than the simulation time because we need tn+1 for the U ALE computation modulation = {} for freq in listspanf: modulation[freq] = varalist[freq](time) * np.sin( 2 * np.pi * np.cumsum(varflist[freq](time)) / fs) OuterRadius = varRpvs(time) InnerRadius = varRpvs(time) - varh0(time) * ( 1 + modulation['cardiac'] + modulation['resp'] + modulation['LF'] + modulation['VLF']) # define the thickness interpolation function interph0 = interp1d( time, varh0(time) * (1 + modulation['cardiac'] + modulation['resp'] + modulation['LF'] + modulation['VLF'])) ## More easy here to take the numerical derivative of the radius ##dadt= np.array(list(np.diff(vara(time))/np.diff(time))+[0.0]) ##dRadiusdt= -(Rpvs-Rv)*(dadt*np.sin(2*np.pi*np.cumsum(varf(time))/fs)+vara(time)*np.cos((2*np.pi*np.cumsum(varf(time))/fs))*(2*np.pi*varf(time))) douterRadiusdt = np.array( list(np.diff(OuterRadius) / np.diff(time)) + [0.0]) dinnerRadiusdt = np.array( list(np.diff(InnerRadius) / np.diff(time)) + [0.0]) # define an expression for the radius and the derivative class Interp(UserExpression): def __init__(self, x, y, **kwargs): super().__init__(self, **kwargs) self.interp = interp1d(x, y) self.tn = 0 def eval(self, values, x): values[0] = 0 values[1] = self.interp(self.tn) def value_shape(self): return (2, ) class Interpdiff(UserExpression): def __init__(self, x, y, **kwargs): super().__init__(self, **kwargs) self.interp = interp1d(x, y) self.tn = 0 self.tnp1 = dt def eval(self, values, x): values[0] = 0 values[1] = self.interp(self.tnp1) - self.interp(self.tn) def value_shape(self): return (2, ) interpRpvs = Interp(time, OuterRadius, degree=1) interpRv = Interp(time, InnerRadius, degree=1) #initial values for Rv and Rpvs uale_top = Interpdiff(time, OuterRadius, degree=1) uale_bottom = Interpdiff(time, InnerRadius, degree=1) vf_top = Interp(time, douterRadiusdt, degree=1) vf_bottom = Interp(time, dinnerRadiusdt, degree=1) Rvfunction = interp1d(time, InnerRadius) Rpvsfunction = interp1d(time, OuterRadius) dRvdtfunction = interp1d(time, dinnerRadiusdt) else: import sympy tn = sympy.symbols("tn") tnp1 = sympy.symbols("tnp1") sin = sympy.sin sqrt = sympy.sqrt functionR = Rpvs - (Rpvs - Rv) * (1 + sum([ a * sin(2 * pi * f * tn + phi) for a, f, phi in zip(ai, fi, phii) ])) # displacement R_vessel = sympy.printing.ccode(functionR) functionV = sympy.diff(functionR, tn) # velocity V_vessel = sympy.printing.ccode(functionV) #Delta U for ALE. I dont really like this functionUALE = -(Rpvs - Rv) * (1 + sum([ a * sin(2 * pi * f * tnp1 + phi) for a, f, phi in zip(ai, fi, phii) ])) + (Rpvs - Rv) * (1 + sum([ a * sin(2 * pi * f * tn + phi) for a, f, phi in zip(ai, fi, phii) ])) UALE_vessel = sympy.printing.ccode(functionUALE) vf_bottom = Expression( ('0', V_vessel), tn=0, degree=2) # no slip no gap condition at vessel wall uale_bottom = Expression( ('0', UALE_vessel), tn=0, tnp1=1, degree=2) # displacement for ALE at vessel wall vf_top = Constant((0, 0)) uale_top = Constant((0, 0)) ## Is there a better way to do that ? Rvfunction = lambda t: functionR.subs(tn, t).evalf() Rpvsfunction = lambda t: Rpvs dRvdtfunction = lambda t: functionV.subs(tn, t).evalf() if isSAS: import sympy tn = sympy.symbols("tn") sin = sympy.sin # We add the possibility for rigid moion of the brain a_rigid = args.arigid #cm f_rigid = args.frigid #Hz logging.info('ridig motion of the brain, amplitude : %.2e um' % a_rigid) logging.info('ridig motion of the brain, frequency : %.2e Hz' % f_rigid) functionY = a_rigid * sin(2 * pi * f_rigid * tn) functionVbone = sympy.diff(functionY, tn) # velocity V_bone = sympy.printing.ccode(functionVbone) vf_bone = Expression(('0', V_bone), tn=0, degree=2) logging.info('\n * Lateral assumption') logging.info(lateral_bc) logging.info('\n * Fluid') logging.info('Left : zero pressure') if lateral_bc == 'free': logging.info('Right : zero pressure') elif lateral_bc == 'resistance': logging.info('Right : resistance') else: logging.info('Right : no flow') logging.info('Top : no slip no gap fixed wall') logging.info('Bottom : no slip no gap moving wall') logging.info('\n * Tracer concentration') sas_bc = args.sasbc init_concentration_type = args.c0init init_concentration_value = args.c0valuePVS logging.info('Left BC scenario :', sas_bc) if lateral_bc == 'free': logging.info('Right : zero concentration') else: productionrate = args.productionrate if productionrate: logging.info( 'Right : imposed solute production rate : %e ([c]/s)' % args.productionrate) else: logging.info('Right : no flux') logging.info('Top : no flux') logging.info('Bottom : no flux') logging.info('\n * ALE') logging.info('Left : no flux') logging.info('Right : no flux') logging.info('Top : no displacement') logging.info('Bottom : vessel displacement') # Mesh logging.info(title1('Meshing')) if isSAS: Rv = Rvfunction(0) Rpvs = Rpvsfunction(0) DR = (Rpvs - Rv) / Nr logging.info('cell size : %e cm' % (DR)) from sleep.mesh import mesh_model2d, load_mesh2d, set_mesh_size import gmsh gmsh.initialize(['', '-format', 'msh2']) model = gmsh.model import math Apvs0 = math.pi * Rpvs**2 Av0 = math.pi * Rv**2 A0 = Apvs0 - Av0 # progressive mesh factory = model.occ a = factory.addPoint(-Lsas, Rv, 0) b = factory.addPoint(L, Rv, 0) c = factory.addPoint(L, Rpvs, 0) d = factory.addPoint(0, Rpvs, 0) e = factory.addPoint(0, Rsas, 0) f = factory.addPoint(-Lsas, Rsas, 0) fluid_lines = [ factory.addLine(*p) for p in ((a, b), (b, c), (c, d), (d, e), (e, f), (f, a)) ] named_lines = dict( zip(('bottom', 'pvs_right', 'pvs_top', 'brain_surf', 'sas_top', 'sas_left'), fluid_lines)) fluid_loop = factory.addCurveLoop(fluid_lines) fluid = factory.addPlaneSurface([fluid_loop]) factory.synchronize() model.addPhysicalGroup(2, [fluid], 1) for name in named_lines: tag = named_lines[name] model.addPhysicalGroup(1, [tag], tag) # boxes for mesh refinement boxes = [] # add box on the PVS for mesh field = model.mesh.field fid = 1 field.add('Box', fid) field.setNumber(fid, 'XMin', 0) field.setNumber(fid, 'XMax', L) field.setNumber(fid, 'YMin', Rvfunction(0)) field.setNumber(fid, 'YMax', Rpvsfunction(0)) field.setNumber(fid, 'VIn', DR) field.setNumber(fid, 'VOut', DR * 50) field.setNumber(fid, 'Thickness', Rsas) boxes.append(fid) # Combine field.add('Min', 2) field.setNumbers(2, 'FieldsList', boxes) field.setAsBackgroundMesh(2) model.occ.synchronize() h5_filename = outputfolder + '/mesh.h5' tags = {'cell': {'F': 1}, 'facet': {}} mesh_model2d(model, tags, h5_filename) mesh_f, markers, lookup = load_mesh2d(h5_filename) gmsh.finalize() else: # simple PVS mesh logging.info('cell size : %e cm' % (np.sqrt(DR**2 + DY**2))) logging.info('nb cells: %i' % (Nl * Nr * 2)) mesh_f = RectangleMesh(Point(0, Rvfunction(0)), Point(L, Rpvsfunction(0)), Nl, Nr) ## Refinement at the SAS boundary if args.refineleft: x = mesh_f.coordinates()[:, 0] y = mesh_f.coordinates()[:, 1] #Deformation of the mesh def deform_mesh(x, y): x = L * (x / L)**2.5 return [x, y] x_bar, y_bar = deform_mesh(x, y) xy_bar_coor = np.array([x_bar, y_bar]).transpose() mesh_f.coordinates()[:] = xy_bar_coor mesh_f.bounding_box_tree().build(mesh_f) fluid_bdries = MeshFunction("size_t", mesh_f, mesh_f.topology().dim() - 1, 0) # Label facets xy = mesh_f.coordinates().copy() x, y = xy.T xmin = x.min() xmax = x.max() ymin = y.min() ymax = y.max() tol = min(DR, DY) / 2 #cm if isSAS: class Boundary_sas_left(SubDomain): def inside(self, x, on_boundary): return on_boundary and near(x[0], -Lsas, tol) # downstream class Boundary_pvs_right(SubDomain): def inside(self, x, on_boundary): return on_boundary and near(x[0], L, tol) class Boundary_sas_top(SubDomain): def inside(self, x, on_boundary): return on_boundary and near(x[1], Rsas, tol) and (x[0] < tol) class Boundary_pvs_top(SubDomain): def inside(self, x, on_boundary): return on_boundary and near(x[1], Rpvs, tol) and (x[0] > -tol) # brain class Boundary_brainsurf(SubDomain): def inside(self, x, on_boundary): return on_boundary and near(x[0], 0, tol) and (x[1] > Rpvs - tol) class Boundary_bottom(SubDomain): def inside(self, x, on_boundary): return on_boundary and near(x[1], ymin, tol) btop_sas = Boundary_sas_top() btop_pvs = Boundary_pvs_top() bbottom = Boundary_bottom() bvert_left = Boundary_sas_left() bvert_brain = Boundary_brainsurf() bvert_right = Boundary_pvs_right() btop_sas.mark(fluid_bdries, 1) btop_pvs.mark(fluid_bdries, 2) bbottom.mark(fluid_bdries, 3) bvert_left.mark(fluid_bdries, 4) bvert_brain.mark(fluid_bdries, 5) bvert_right.mark(fluid_bdries, 6) facet_lookup = { 'sas_out': 1, 'pvs_tissue': 2, 'vessel': 3, 'sas_bone': 4, 'sas_tissue': 5, 'pvs_end': 6 } else: # simple PVS mesh class Boundary_left(SubDomain): def inside(self, x, on_boundary): return on_boundary and near(x[0], xmin, tol) #left class Boundary_right(SubDomain): def inside(self, x, on_boundary): return on_boundary and near(x[0], xmax, tol) # right class Boundary_bottom(SubDomain): def inside(self, x, on_boundary): return on_boundary and near(x[1], ymin, tol) #bottom class Boundary_top(SubDomain): def inside(self, x, on_boundary): return on_boundary and near(x[1], ymax, tol) #top btop = Boundary_top() bbottom = Boundary_bottom() bleft = Boundary_left() bright = Boundary_right() bbottom.mark(fluid_bdries, 2) btop.mark(fluid_bdries, 4) bleft.mark(fluid_bdries, 1) bright.mark(fluid_bdries, 3) facet_lookup = { 'sas_out': 1, 'vessel': 2, 'pvs_end': 3, 'pvs_tissue': 4 } facets_out << fluid_bdries # Now we wire up rate_prod = Expression('rate/surface', rate=productionrate, surface=1, degree=1) if lateral_bc == 'free': bcs_fluid = { 'velocity': [(facet_lookup['vessel'], vf_bottom), (facet_lookup['pvs_tissue'], vf_top)], 'traction': [], 'pressure': [(facet_lookup['sas_out'], Constant(0)), (facet_lookup['pvs_end'], Constant(0))] } elif lateral_bc == 'resistance': Rpressure = Expression('R*Q+p0', R=resistance, Q=0, p0=0, degree=1) # # Compute pressure to impose according to the flow at previous time step and resistance. bcs_fluid = { 'velocity': [(facet_lookup['vessel'], vf_bottom), (facet_lookup['pvs_tissue'], vf_top)], 'traction': [], 'pressure': [(facet_lookup['sas_out'], Constant(0)), (facet_lookup['pvs_end'], Rpressure)] } else: bcs_fluid = { 'velocity': [(facet_lookup['vessel'], vf_bottom), (facet_lookup['pvs_tissue'], vf_top), (facet_lookup['pvs_end'], Constant( (0, 0)))], # I would like only normal flow to be zero 'traction': [], 'pressure': [(facet_lookup['sas_out'], Constant(0))] } #### This is overwritten later depending on the scenario bcs_tracer_in = { 'concentration': [(facet_lookup['sas_out'], 0)], 'flux': [(facet_lookup['pvs_end'], rate_prod), (facet_lookup['pvs_tissue'], Constant(0)), (facet_lookup['vessel'], Constant(0))] } bcs_tracer_out = { 'concentration': [], 'flux': [(facet_lookup['sas_out'], Constant(0)), (facet_lookup['pvs_end'], rate_prod), (facet_lookup['pvs_tissue'], Constant(0)), (facet_lookup['vessel'], Constant(0))] } # todo : add possibility to have other BC at pvs_end #if lateral_bc=='free' : # bcs_tracer = {'concentration': [(facet_lookup['pvs_end'], Constant(0)), # (facet_lookup['sas_out'], c_SAS)], # 'flux': [(facet_lookup['pvs_tissue'], Constant(0)), # (facet_lookup['vessel'], Constant(0))]} # add BC on the extra boundary in the mesh of the SAS if isSAS: bcs_fluid['velocity'].append((facet_lookup['sas_bone'], vf_bone)) bcs_fluid['velocity'].append( (facet_lookup['sas_tissue'], Constant((0, 0)))) bcs_tracer_in['flux'].append((facet_lookup['sas_bone'], Constant(0))) bcs_tracer_in['flux'].append((facet_lookup['sas_tissue'], Constant(0))) bcs_tracer_out['flux'].append((facet_lookup['sas_bone'], Constant(0))) bcs_tracer_out['flux'].append( (facet_lookup['sas_tissue'], Constant(0))) # BC for ALE (not used anymore) bcs_ale = { 'dirichlet': [(facet_lookup['vessel'], uale_bottom), (facet_lookup['pvs_tissue'], uale_top)], 'neumann': [(facet_lookup['sas_out'], Constant((0, 0))), (facet_lookup['pvs_end'], Constant((0, 0)))] } # We collect the time dependent BC for update driving_expressions = [uale_bottom, vf_bottom, uale_top, vf_top] if isSAS: driving_expressions.append(vf_bone) #FEM space logging.info(title1("Set FEM spaces")) logging.info('\n * Fluid') Vf_elm = VectorElement('Lagrange', triangle, 2) Qf_elm = FiniteElement('Lagrange', triangle, 1) Wf_elm = MixedElement([Vf_elm, Qf_elm]) Wf = FunctionSpace(mesh_f, Wf_elm) logging.info('Velocity : "Lagrange", triangle, 2') logging.info('Pressure : "Lagrange", triangle, 1') logging.info('\n * Tracer') Ct_elm = FiniteElement('Lagrange', triangle, 1) Ct = FunctionSpace(mesh_f, Ct_elm) logging.info('Concentration : "Lagrange", triangle, 1') logging.info('\n * ALE') Va_elm = VectorElement('Lagrange', triangle, 1) Va = FunctionSpace(mesh_f, Va_elm) logging.info('ALE displacement: "Lagrange", triangle, 1') # Initialisation : logging.info(title1("Initialisation")) c_SAS = Expression('m/VCSF', m=0, VCSF=40e-3, degree=2) #initial concentration in SAS if sas_bc == 'scenarioA': cSAS = 0 else: cSAS = args.c0valueSAS # number of vessels used for mass balance Nvessels = 6090 # initial volume of CSF in PVS z, r = SpatialCoordinate(mesh_f) ds = Measure('ds', domain=mesh_f, subdomain_data=fluid_bdries) n = FacetNormal(mesh_f) # volume of pvs VPVS = 2 * np.pi * assemble(Constant(1.0) * r * dx(mesh_f)) # initial volume of CSF in SAS : assumed to be 10 times larger than volume in PVS VCSF = 10 * VPVS #40e-3 # initial pressure of the CSF PCSF = 4 # mmHg # initial volume of arterial blood Vblood = 4e-3 # ml # equivalent vessel length used for the compliance function and assessement of ICP leq = Vblood / (np.pi * Rvfunction(0)**2) # initial tracer mass in the CSF m = cSAS * VCSF # constant production of CSF Qprod = 6e-6 # ml/s # Outflow resistance Rcsf = 5 / 1.7e-5 # mmHg/(ml/s) # CSF compliance Ccsf = 1e-3 #ml/mmHg if sas_bc == 'scenarioA': logging.info('Left : zero concentration') # initial outflow of CSF (not used, just for output file) Qout = 0 elif sas_bc == 'scenarioB': logging.info('Left : mass conservation, no CSF outflow') # initial outflow of CSF Qout = 0 elif sas_bc == 'scenarioC': logging.info('Left : mass conservation, constant CSF outflow') # initial outflow of CSF Qout = Qprod elif sas_bc == 'scenarioD': logging.info( 'Left : mass conservation, pressure dependent CSF outflow') # initial outflow of CSF Qout = Qprod # venous pressure Pss = PCSF - Qout * Rcsf logging.info("\n * Fluid") logging.info("Velocity : zero field") logging.info("Pressure : zero field") uf_n = project(Constant((0, 0)), Wf.sub(0).collapse()) pf_n = project(Constant(0), Wf.sub(1).collapse()) logging.info("\n * Tracer") if init_concentration_type == 'gaussian': logging.info("Concentration : Gaussian profile") logging.info(" Centered at xi = %e" % xi_gauss) logging.info(" STD parameter = %e" % sigma_gauss) logging.info(" Max value=%e" % init_concentration_value) c_0 = Expression('c0*exp(-a*pow(x[0]-b, 2)) ', degree=1, a=1 / 2 / sigma_gauss**2, b=xi_gauss, c0=init_concentration_value) c_n = project(c_0, Ct) elif init_concentration_type == 'constant': logging.info("Concentration : Uniform profile") if isSAS: logging.info("Value in PVS=%e" % init_concentration_value) logging.info("Value in SAS=%e" % cSAS) c_0 = Expression('x[0]>0 ? cPVS : cSAS ', degree=2, cPVS=init_concentration_value, cSAS=cSAS) c_n = project(c_0, Ct) else: logging.info("Value=%e" % init_concentration_value) c_n = project(Constant(init_concentration_value), Ct) elif init_concentration_type == 'null': logging.info("Concentration : zero in the vessel") c_n = project(Constant(0), Ct) else: logging.info("Concentration : Uniform profile (default)") logging.info("Value=%e" % 0) ## Initialisation c_n = project(Constant(0), Ct) # Save initial state uf_n.rename("uf", "tmp") pf_n.rename("pf", "tmp") c_n.rename("c", "tmp") uf_out << (uf_n, 0) pf_out << (pf_n, 0) c_out << (c_n, 0) files = [csv_p, csv_u, csv_c] fields = [pf_n, uf_n.sub(0), c_n] slice_line = line([0, (Rpvs + Rv) / 2], [L, (Rpvs + Rv) / 2], 100) for csv_file, field in zip(files, fields): #print the x scale values = np.linspace(0, L, 100) row = [0] + list(values) csv_file.write(('%e' + ', %e' * len(values) + '\n') % tuple(row)) #print the initial 1D slice values = line_sample(slice_line, field) row = [0] + list(values) csv_file.write(('%e' + ', %e' * len(values) + '\n') % tuple(row)) ############# RUN ############ logging.info(title1("Run")) # Time loop time = 0. timestep = 0 z, r = SpatialCoordinate(mesh_f) ds = Measure('ds', domain=mesh_f, subdomain_data=fluid_bdries) n = FacetNormal(mesh_f) # volume of pvs volume = 2 * np.pi * assemble(Constant(1.0) * r * dx(mesh_f)) # integral of concentration intc = 2 * np.pi * assemble(r * c_n * dx(mesh_f)) # tracer mass out of the system mout = 0 csv_mass.write('%s, %s, %s, %s, %s, %s, %s, %s, %s\n' % ('time', 'mass PVS', 'mass CSF', 'mass out', 'Total mass', 'PVS volume', 'CSF volume', 'P csf', 'Q out')) csv_mass.write('%e, %e, %e, %e, %e, %e, %e, %e, %e\n' % (time, Nvessels * intc, m, mout, Nvessels * intc + m + mout, Nvessels * volume, VCSF, PCSF, Qout)) # ALE deformation function expressionDeformation = Expression(( "0", "x[1]<=rpvs ? (x[1]-rpvs)/(rpvs-rvessel)*htarget+rpvstarget-x[1]:rpvstarget-rpvs" ), rvessel=0, rpvs=1, rpvstarget=1, htarget=1, degree=1) # Extend normal to 3d as GradAxisym(scalar) is 3-vector normal = as_vector((Constant(-1), Constant(0), Constant(0))) # Here I dont know if there will be several dt for advdiff and fluid solver while time < tfinal: for expr in driving_expressions: hasattr(expr, 'tn') and setattr(expr, 'tn', time) hasattr(expr, 'tnp1') and setattr(expr, 'tnp1', time + dt) if lateral_bc == 'resistance': Flow = assemble(2 * pi * r * dot(uf_n, n) * ds(facet_lookup['pvs_end'])) setattr(Rpressure, 'Q', Flow) #Solve ALE and move mesh #eta_f = solve_ale(Va, f=Constant((0, 0)), bdries=fluid_bdries, bcs=bcs_ale, parameters=ale_parameters) # Or just compute the deformation xy = mesh_f.coordinates() x, y = xy.T expressionDeformation.rpvstarget = Rpvsfunction(time) expressionDeformation.htarget = Rpvsfunction(time) - Rvfunction(time) expressionDeformation.rvessel = min( y[x > 0]) # We look only in the PVS (x>0) not the SAS expressionDeformation.rpvs = max(y[x > 0]) # #eta_f = interpolate(expressionDeformation,VectorFunctionSpace(mesh_f,"CG",1)) eta_f = project(expressionDeformation, Va) ALE.move(mesh_f, eta_f) mesh_f.bounding_box_tree().build(mesh_f) # update the coordinates z, r = SpatialCoordinate(mesh_f) ds = Measure('ds', domain=mesh_f, subdomain_data=fluid_bdries) n = FacetNormal(mesh_f) # Solve fluid problem uf_, pf_ = solve_fluid(Wf, u_0=uf_n, f=Constant((0, 0)), bdries=fluid_bdries, bcs=bcs_fluid, parameters=fluid_parameters) # Solve tracer problem tracer_parameters["T0"] = time tracer_parameters["nsteps"] = 1 tracer_parameters["dt"] = dt # If the fluid is exiting the PVS we compute the amount of mass entering the SAS. The tracer left BC is free. # If the fluid is entering the PVS then we impose the concentration in the SAS at the left BC. #Fluid flow at the BC FluidFlow = assemble(2 * pi * r * dot(uf_, n) * ds(facet_lookup['sas_out'])) # n is directed in the outward direction : is it ? print('fluid flow : ', FluidFlow) if FluidFlow > 0: # then the fluid is going out and we impose natural BC for concentration bcs_tracer = bcs_tracer_out else: # then the fluid is going in and we impose the SAS concentration cmean = assemble( 2 * pi * r * c_n * ds(facet_lookup['sas_out'])) / assemble( 2 * pi * r * Constant(1) * ds(facet_lookup['sas_out'])) # we allow the possibility to use a relaxation here alpha = 0. # 0 means no relaxation c_imposed = (1 - alpha) * cSAS + alpha * cmean c_imposed = max(c_imposed, 0) bcs_tracer = bcs_tracer_in bcs_tracer['concentration'] = [(facet_lookup['sas_out'], Constant(c_imposed))] c_, T0 = solve_adv_diff(Ct, velocity=uf_ - eta_f / Constant(dt), phi=Constant(1), f=Constant(0), c_0=c_n, phi_0=Constant(1), bdries=fluid_bdries, bcs=bcs_tracer, parameters=tracer_parameters) Massflow = assemble(2 * pi * r * dot(uf_ - eta_f / Constant(dt), n) * c_ * ds(facet_lookup['sas_out'])) Massdiffusion = tracer_parameters["kappa"] * assemble(2 * pi * r * dot( cyl.GradAxisym(c_), normal) * ds(facet_lookup['sas_out'])) if sas_bc == 'scenarioD': # update CSF outflow Qout = max((PCSF - Pss) / Rcsf, 0) # valve # update CSF pressure PCSF += dt / Ccsf * (Qprod - Qout) + np.pi * leq * ( Rvfunction(time + dt)**2 - Rvfunction(time)**2) / Ccsf # link between leq and Nvessels ? if sas_bc == 'scenarioA': if FluidFlow >= 0: # mainly advection mout += dt * Nvessels * Massflow # lost mass in the PVS due to diffusion mout += -dt * Nvessels * Massdiffusion else: # Advected mass m += dt * Nvessels * Massflow - dt * Qout * cSAS if FluidFlow >= 0: # when in-flow we impose c sas at the boundary so no c gradient # Adding diffusion m += -dt * Nvessels * Massdiffusion mout += Qout * cSAS * dt # update the volume of CSF #VCSF+=dt*Nvessels*FluidFlow # should correspond to the volume change due to vessel dilation #VCSF+=np.pi*leq*(Rvfunction(time+dt)**2-Rvfunction(time)**2) # update tracer concentration in SAS cSAS = m / VCSF rate_prod.surface = assemble(2 * pi * r * ds(facet_lookup['pvs_end'])) # Update current solution uf_n.assign(uf_) pf_n.assign(pf_) c_n.assign(c_) #Update time time += dt timestep += 1 # Save output if (timestep % int(toutput / dt) == 0): logging.info("\n*** save output time %e s" % time) logging.info("number of time steps %i" % timestep) # may report Courant number or other important values that indicate how is doing the run uf_.rename("uf", "tmp") pf_.rename("pf", "tmp") c_.rename("c", "tmp") uf_out << (uf_, time) pf_out << (pf_, time) c_out << (c_, time) # Get the 1 D profiles at umax (to be changed in cyl coordinate) mesh_points = mesh_f.coordinates() x = mesh_points[:, 0] y = mesh_points[:, 1] xmin = min(x) xmax = max(x) ymin = min(y[x > 0]) ymax = max(y[x > 0]) # update the coordinates z, r = SpatialCoordinate(mesh_f) ds = Measure('ds', domain=mesh_f, subdomain_data=fluid_bdries) n = FacetNormal(mesh_f) #slice_line = line([xmin,(ymin+ymax)/2],[xmax,(ymin+ymax)/2], 100) logging.info('Rpvs : %e' % ymax) logging.info('Rvn : %e' % ymin) files = [csv_p, csv_u, csv_c] fields = [pf_n, uf_n.sub(0), c_n] field_names = [ 'pressure (dyn/cm2)', 'axial velocity (cm/s)', 'concentration' ] for csv_file, field, name in zip(files, fields, field_names): #values = line_sample(slice_line, field) values = profile(field, xmin, xmax, ymin, ymax) logging.info('Max ' + name + ' : %.2e' % max(abs(values))) #logging.info('Norm '+name+' : %.2e'%field.vector().norm('linf')) row = [time] + list(values) csv_file.write( ('%e' + ', %e' * len(values) + '\n') % tuple(row)) csv_file.flush() csv_rv.write(('%e, %e\n') % (time, ymin)) csv_rv.flush() # volume of pvs volume = 2 * np.pi * assemble(Constant(1.0) * r * dx(mesh_f)) # integral of concentration intc = 2 * np.pi * assemble(r * c_ * dx(mesh_f)) csv_mass.write('%e, %e, %e, %e, %e, %e, %e, %e, %e\n' % (time, Nvessels * intc, m, mout, Nvessels * intc + m + mout, Nvessels * volume, VCSF, PCSF, Qout)) csv_mass.flush()
field.setNumber(fid, 'YMin', ymin) field.setNumber(fid, 'YMax', ymax) field.setNumber(fid, 'VIn', Vin) field.setNumber(fid, 'VOut', Vout) field.setNumber(fid, 'Thickness', t) boxes.append(fid) fid += 1 # Combine field.add('Min', fid) field.setNumbers(fid, 'FieldsList', boxes) field.setAsBackgroundMesh(fid) model.occ.synchronize() # gmsh.fltk.initialize() # gmsh.fltk.run() h5_filename = './test/fbb_domain.h5' mesh_model2d(model, tags, h5_filename) mesh, markers, lookup = load_mesh2d(h5_filename) cell_f, facet_f = markers check_markers(cell_f, facet_f, lookup, geometry_parameters) gmsh.finalize() df.File('./test/fbb_cells.pvd') << cell_f df.File('./test/fbb_facets.pvd') << facet_f
def PVSbrain_simulation(args): """ Test case for simple diffusion from PVS to the brain. Outputs : - a logfile with information about the simulation - .pvd files at specified args.toutput time period with the u, p and c fields in stokes domain and u, p , q, phi and c fields in Biot domain - .csv files of u, p, c 1D array of the u, p, c fields on the middle line of the PVS - .csv files of the total mass in the domain, mass flux from the brain to sas, brain to PVS, PVS to SAS """ # output folder name outputfolder = args.output_folder + '/' + args.job_name + '/' if not os.path.exists(outputfolder): os.makedirs(outputfolder) if not os.path.exists(outputfolder + '/fields'): os.makedirs(outputfolder + '/fields') # Create output files #pvd files c_out = File(outputfolder + 'fields' + '/c.pvd') facets_out_fluid = File(outputfolder + 'fields' + '/facets_fluid.pvd') facets_out_solid = File(outputfolder + 'fields' + '/facets_solid.pvd') # Create logger logger = logging.getLogger() logger.setLevel(logging.INFO) # log to a file now = datetime.now().strftime("%Y%m%d_%H%M%S") filename = os.path.join(outputfolder + '/', 'PVSBrain_info.log') file_handler = logging.FileHandler(filename, mode='w') file_handler.setLevel(logging.INFO) #formatter = logging.Formatter("%(asctime)s %(filename)s, %(lineno)d, %(funcName)s: %(message)s") #file_handler.setFormatter(formatter) logger.addHandler(file_handler) # log to the console console_handler = logging.StreamHandler() level = logging.INFO console_handler.setLevel(level) logger.addHandler(console_handler) # initialise logging logging.info( title1( "Test case of simple diffusion from PVS to the brain using the diffusion-advection solver" )) logging.info("Date and time:" + datetime.now().strftime("%m/%d/%Y, %H:%M:%S")) logging.info('Job name : ' + args.job_name) logging.debug('logging initialized') # Set parameters logging.info(title1("Parameters")) # Geometry params logging.info('\n * Geometry') Rv = args.radius_vessel # centimeters Rpvs = args.radius_pvs # centimeters Rbrain = args.radius_brain # centimeters L = args.length # centimeters logging.info('Vessel radius : %e cm' % Rv) logging.info('PVS radius : %e cm' % Rpvs) logging.info('Brain radius : %e cm' % Rbrain) logging.info('length : %e cm' % L) #Mesh logging.info('\n * Mesh') #number of cells in the radial direction fluid domain Nr = args.N_radial_fluid #number of cells in the radial direction biot domain Nr_biot = args.N_radial_biot s_biot = args.biot_progression DR = (Rpvs - Rv) / Nr #number of cells in the axial direction if args.N_axial: Nl = args.N_axial else: Nl = round(L / DR) DY = L / Nl logging.info('N axial: %i' % Nl) logging.info('N radial PVS: %e' % Nr) logging.info('N radial Biot: %e' % Nr_biot) logging.info('progression parameter in biot: %e' % s_biot) #time parameters logging.info('\n * Time') toutput = args.toutput tfinal = args.tend dt = args.time_step logging.info('final time: %e s' % tfinal) logging.info('output period : %e s' % toutput) logging.info('time step : %e s' % dt) dt_advdiff = dt logging.info('\n* Tracer properties') D = args.diffusion_coef sigma_gauss = args.sigma logging.info('Free diffusion coef: %e cm2/s' % D) logging.info('STD of initial gaussian profile: %e ' % sigma_gauss) xi_gauss = args.initial_pos logging.info('Initial position: %e cm2' % xi_gauss) logging.info('\n * Porous medium properties') porosity_0 = args.biot_porosity tortuosity = args.biot_tortuosity dt_solid = dt logging.info('initial porosity: %e ' % porosity_0) logging.info('tortuosity: %e ' % tortuosity) ## The tracer is solver in the full domain, so it has two subdomains # 1 for solid # 0 for fluid tracer_parameters = { 'kappa_0': D, 'kappa_1': D * tortuosity, 'dt': dt_advdiff, 'nsteps': 1 } # Mesh logging.info(title1('Meshing')) meshing = args.mesh_method ##### Meshing method : regular if meshing == 'regular': # Create a mesh using Rectangle mesh : all the cells are regulars but this means a lot of cells logging.info('cell size : %e cm' % (np.sqrt(DR**2 + DY**2))) # Create a rectangle mesh with Nr + Nsolid cells in the radias direction and Nl cells in the axial direction # the geometrical progression for the solid mesh is s #Creation of the uniform mesh Rext = 1 + Nr_biot / Nr mesh = RectangleMesh(Point(0, 0), Point(L, Rext), Nl, Nr + Nr_biot) x = mesh.coordinates()[:, 0] y = mesh.coordinates()[:, 1] #Deformation of the mesh def deform_mesh(x, y): transform_fluid = Rv + (Rpvs - Rv) * y transform_solid = Rpvs + (Rbrain - Rpvs) * ((y - 1) / (Rext - 1))**s_biot yp = np.where(y <= 1, transform_fluid, transform_solid) return [x, yp] x_bar, y_bar = deform_mesh(x, y) xy_bar_coor = np.array([x_bar, y_bar]).transpose() mesh.coordinates()[:] = xy_bar_coor mesh.bounding_box_tree().build(mesh) else: ##### Meshing method : gmsh with box for refinement from sleep.mesh import mesh_model2d, load_mesh2d, set_mesh_size import sys gmsh.initialize(['', '-format', 'msh2']) model = gmsh.model import math Apvs0 = math.pi * Rpvs**2 Av0 = math.pi * Rv**2 A0 = Apvs0 - Av0 # progressive mesh factory = model.occ a = factory.addPoint(0, Rv, 0) b = factory.addPoint(L, Rv, 0) c = factory.addPoint(L, Rpvs, 0) d = factory.addPoint(0, Rpvs, 0) e = factory.addPoint(L, Rbrain, 0) f = factory.addPoint(0, Rbrain, 0) fluid_lines = [ factory.addLine(*p) for p in ((a, b), (b, c), (c, d), (d, a)) ] named_lines = dict( zip(('bottom', 'pvs_right', 'interface', 'pvs_left'), fluid_lines)) fluid_loop = factory.addCurveLoop(fluid_lines) fluid = factory.addPlaneSurface([fluid_loop]) solid_lines = [ factory.addLine(*p) for p in ((d, c), (c, e), (e, f), (f, d)) ] named_lines.update( dict( zip(('interface', 'brain_right', 'brain_top', 'brain_left'), solid_lines))) solid_loop = factory.addCurveLoop(solid_lines) solid = factory.addPlaneSurface([solid_loop]) factory.synchronize() tags = {'cell': {'F': 1, 'S': 2}, 'facet': {}} model.addPhysicalGroup(2, [fluid], 1) model.addPhysicalGroup(2, [solid], 2) for name in named_lines: tag = named_lines[name] model.addPhysicalGroup(1, [tag], tag) # boxes for mesh refinement cell_size = DR * (Rpvs - Rv) / (Rpvs - Rv) boxes = [] # add box on the PVS for mesh field = model.mesh.field fid = 1 field.add('Box', fid) field.setNumber(fid, 'XMin', 0) field.setNumber(fid, 'XMax', L) field.setNumber(fid, 'YMin', Rv) field.setNumber(fid, 'YMax', Rpvs) field.setNumber(fid, 'VIn', cell_size * 2) field.setNumber(fid, 'VOut', DR * 50) field.setNumber(fid, 'Thickness', (Rpvs - Rv) / 4) boxes.append(fid) # Combine field.add('Min', fid + 1) field.setNumbers(fid + 1, 'FieldsList', boxes) field.setAsBackgroundMesh(fid + 1) model.occ.synchronize() h5_filename = outputfolder + '/mesh.h5' mesh_model2d(model, tags, h5_filename) mesh, markers, lookup = load_mesh2d(h5_filename) from IPython import embed embed() gmsh.finalize() ## Define subdomains x = mesh.coordinates()[:, 0] y = mesh.coordinates()[:, 1] tol = 1e-7 class Omega_0(SubDomain): def inside(self, x, on_boundary): return x[1] < Rpvs + tol class Omega_1(SubDomain): def inside(self, x, on_boundary): return x[1] > Rpvs - tol subdomains = MeshFunction("size_t", mesh, mesh.topology().dim(), 0) subdomain_0 = Omega_0() subdomain_1 = Omega_1() subdomain_0.mark(subdomains, 0) subdomain_1.mark(subdomains, 1) mesh_f = EmbeddedMesh(subdomains, 0) mesh_s = EmbeddedMesh(subdomains, 1) ## Define boundaries solid_bdries = MeshFunction("size_t", mesh_s, mesh_s.topology().dim() - 1, 0) fluid_bdries = MeshFunction("size_t", mesh_f, mesh_f.topology().dim() - 1, 0) full_bdries = MeshFunction("size_t", mesh, mesh.topology().dim() - 1, 0) # Label facets class Boundary_left_fluid(SubDomain): def inside(self, x, on_boundary): return on_boundary and near(x[0], 0, tol) and (x[1] <= Rpvs + tol ) #left fluid class Boundary_right_fluid(SubDomain): def inside(self, x, on_boundary): return on_boundary and near(x[0], L, tol) and (x[1] <= Rpvs + tol ) # right fluid class Boundary_left_solid(SubDomain): def inside(self, x, on_boundary): return on_boundary and near(x[0], 0, tol) and (x[1] >= Rpvs - tol ) #left solid class Boundary_right_solid(SubDomain): def inside(self, x, on_boundary): return on_boundary and near(x[0], L, tol) and (x[1] >= Rpvs - tol ) # right solid class Boundary_bottom(SubDomain): def inside(self, x, on_boundary): return on_boundary and near(x[1], Rv, tol) #bottom class Boundary_top(SubDomain): def inside(self, x, on_boundary): return on_boundary and near(x[1], Rbrain, tol) #top class Boundary_interface(SubDomain): def inside(self, x, on_boundary): return on_boundary and near(x[1], Rpvs, tol) #interface #todo : keep separate F and S for right and left in the full bdries btop = Boundary_top() bbottom = Boundary_bottom() bleft_fluid = Boundary_left_fluid() bright_fluid = Boundary_right_fluid() bleft_solid = Boundary_left_solid() bright_solid = Boundary_right_solid() binterface = Boundary_interface() bbottom.mark(fluid_bdries, 2) binterface.mark(fluid_bdries, 4) bleft_fluid.mark(fluid_bdries, 1) bright_fluid.mark(fluid_bdries, 3) binterface.mark(solid_bdries, 4) bright_solid.mark(solid_bdries, 5) btop.mark(solid_bdries, 6) bleft_solid.mark(solid_bdries, 7) bleft_fluid.mark(full_bdries, 1) bleft_solid.mark(full_bdries, 7) bbottom.mark(full_bdries, 2) bright_fluid.mark(full_bdries, 3) bright_solid.mark(full_bdries, 5) btop.mark(full_bdries, 6) facet_lookup = { 'F_left': 1, 'F_bottom': 2, 'F_right': 3, 'Interface': 4, 'S_left': 7, 'S_top': 6, 'S_right': 5 } facets_out_fluid << fluid_bdries facets_out_solid << solid_bdries # define the domain specific parameters for the tracer # NOTE: Here we do P0 projection dx = Measure('dx', domain=mesh, subdomain_data=subdomains) CoefSpace = FunctionSpace(mesh, 'DG', 0) q = TestFunction(CoefSpace) # Remove coef = 'kappa' fluid_coef = tracer_parameters.pop('%s_0' % coef) solid_coef = tracer_parameters.pop('%s_1' % coef) form = ((1 / CellVolume(mesh)) * fluid_coef * q * dx(0) + (1 / CellVolume(mesh)) * solid_coef * q * dx(1)) tracer_parameters[coef] = Function(CoefSpace, assemble(form)) #FEM space logging.info(title1("Set FEM spaces")) logging.info('\n * Tracer') #### Todo : I would like to be able to have discontinuous concentration when we will have the membrane #### Beter to solve in two domains or one domain with discontinuous lagrange element ? Ct_elm = FiniteElement('Lagrange', triangle, 1) Ct = FunctionSpace(mesh, Ct_elm) logging.info('Concentration : "Lagrange", triangle, 1') #Advection velocity FS_advvel = VectorFunctionSpace(mesh, 'CG', 2) # Setup of boundary conditions logging.info(title1("Boundary conditions")) ## to do : allow zero concentration if fluid BC is free on the right logging.info('\n * Tracer concentration') bcs_tracer = { 'concentration': [(facet_lookup['S_top'], Constant(0))], 'flux': [ (facet_lookup['S_left'], Constant(0)), (facet_lookup['S_right'], Constant(0)), (facet_lookup['F_right'], Constant(0)), (facet_lookup['F_left'], Constant(0)), (facet_lookup['F_bottom'], Constant(0)), ] } # Initialisation : # 1 in the PVS cf_0 = Expression('x[1]<= Rpvs ? 1 : 0 ', degree=2, a=1 / 2 / sigma_gauss**2, b=xi_gauss, Rv=Rv, Rpvs=Rpvs) c_n = project(cf_0, Ct) # File('initial_reg.pvd') << c_n exit() #Initial deformation of the fluid domain # We start at a time shift tshift = 0 # c_n.rename("c", "tmp") c_out << (c_n, 0) ############# RUN ############3 logging.info(title1("Run")) # Time loop time = tshift timestep = 0 # Here I dont know if there will be several dt for advdiff and fluid solver while time < tfinal + tshift: time += dt timestep += 1 print('time', time - tshift) # Solve tracer problem tracer_parameters["T0"] = time advection_velocity = project(Constant((0, 0)), FS_advvel) c_, T0 = solve_adv_diff(Ct, velocity=advection_velocity, phi=Constant(0.2), f=Constant(0), c_0=c_n, phi_0=Constant(0.2), bdries=full_bdries, bcs=bcs_tracer, parameters=tracer_parameters) # Update current solution c_n.assign(c_) # Save output if (timestep % int(toutput / dt) == 0): logging.info("\n*** save output time %e s" % (time - tshift)) logging.info("number of time steps %i" % timestep) # may report Courant number or other important values that indicate how is doing the run c_n.rename("c", "tmp") c_out << (c_n, time - tshift) advection_velocity.rename("adv_vel", "tmp") File(outputfolder + 'fields' + '/adv_vel.pvd') << (advection_velocity, time - tshift)