agd.Eikonal.HFM_CUDA.SellingAnisotropicDiffusion

  1# Copyright 2022 Jean-Marie Mirebeau, ENS Paris-Saclay, CNRS, University Paris-Saclay
  2# Distributed WITHOUT ANY WARRANTY. Licensed under the Apache License, Version 2.0, see http://www.apache.org/licenses/LICENSE-2.0
  3
  4import numpy as np
  5import cupy as cp
  6import os
  7import numbers
  8
  9from . import cupy_module_helper
 10from .cupy_module_helper import SetModuleConstant
 11from ... import AutomaticDifferentiation as ad
 12
 13def anisotropic_diffusion_scheme(D,dx=1.,ced=None,periodic=False,block_size=256,traits=None):
 14	"""
 15	First stage of solving D_t u = div( D grad u ), with Neumann b.c.
 16	Input : 
 17	- D : positive definite diffusion tensors. Format : (n1,...,nd,d*(d+1)/2)
 18	- ced (optional) : a dictionary with keys
 19	  	- alpha, gamma (real) : weickerts coherence enhancing diffusion parameters
 20	  	- cond_max,cond_amplification_threshold (real, optional) : additional parameters
 21	  	- retD (bool, optional) : wether to return the diffusion tensors 
 22
 23	  	If ced is present, then D is regarded as a structure tensor, and modified 
 24		according to Weickert's coherence enhancing diffusion principle
 25	- dx (optional): grid scale
 26	
 27	Output : 
 28	- dt_max : the largest stable time step for the scheme
 29	- scheme_data : data to be used in anisotropic_diffusion_steps 
 30	- D (optional) : the diffusion tensors, returned if ced['retD']=true 
 31	"""
 32	if D.shape[0]==D.shape[1]==D.ndim-2: # Also allow common format (d,d,n1,...,nd)  
 33		from ... import Metrics
 34		D = np.moveaxis(Metrics.misc.flatten_symmetric_matrix(D),0,-1)
 35	D = cp.ascontiguousarray(D)
 36	shape = D.shape[:-1]
 37	ndim = len(shape)
 38	size = np.prod(shape)
 39	symdim = (ndim*(ndim+1))//2
 40	decompdim = symdim # changes in dimension 4
 41	assert ndim in (1,2,3) and D.shape[-1]==symdim
 42	int_t = np.int32
 43	float_t = np.float32
 44	assert D.dtype==float_t
 45
 46	traits_default = {
 47		'ndim_macro':ndim,
 48		'Int':int_t,
 49		'Scalar':float_t,
 50		'prev_coef':1, # Set prev_coef = 0 for the pure linear operator
 51		'fourth_order_macro':False,
 52	}
 53	if traits is not None: traits_default.update(traits)
 54	traits = traits_default
 55
 56	if isinstance(periodic,numbers.Number): periodic = (periodic,)*ndim
 57	if any(periodic): 
 58		traits['periodic_macro'] = 1
 59		traits['periodic_axes'] = periodic
 60	if ced is not None: 
 61		assert ndim>1
 62		ced = ced.copy()
 63		traits['ced_macro']=1
 64		traits['retD_macro'] = ced.pop('retD',False)
 65	retD = (np.empty_like(D),) if traits.get('retD_macro',False) else tuple()
 66
 67	cuda_path = os.path.join(os.path.dirname(os.path.realpath(__file__)),"cuda")
 68	date_modified = cupy_module_helper.getmtime_max(cuda_path)
 69	source = cupy_module_helper.traits_header(traits,size_of_shape=True)
 70
 71	source += [
 72	'#include "Kernel_SellingAnisotropicDiffusion.h"',
 73	f"// Date cuda code last modified : {date_modified}"]
 74	cuoptions = ("-default-device", f"-I {cuda_path}") 
 75
 76	source = "\n".join(source)
 77	module = cupy_module_helper.GetModule(source,cuoptions)
 78	SetModuleConstant(module,'shape_tot',shape,int_t)
 79	SetModuleConstant(module,'size_tot',size,int_t)
 80	if isinstance(dx,numbers.Number): dx = (dx,)*ndim
 81	SetModuleConstant(module,'dx',dx,float_t)
 82	if ced is not None:
 83		SetModuleConstant(module,"ced_alpha",ced.pop("α"),float_t)
 84		SetModuleConstant(module,"ced_gamma",ced.pop("γ"),float_t)
 85		SetModuleConstant(module,"ced_cond_max",ced.pop("cond_max",100),float_t)
 86		SetModuleConstant(module,"ced_cond_amplification_threshold",
 87			ced.pop("cond_amplification_threshold",2),float_t)
 88		if ced: print(f"Warning : unused ced keys {list(ced.keys())}")
 89
 90	cupy_kernel = module.get_function("anisotropic_diffusion_scheme")
 91	wdiag  = np.full_like(D,0.,shape=shape)
 92	wneigh = np.empty_like(D,shape=shape+(decompdim,));
 93	nneigh = 4 if traits['fourth_order_macro'] else 2
 94	ineigh = np.full_like(D,-1,shape=shape+(decompdim,nneigh),dtype=int_t);
 95
 96	grid_size = int(np.ceil(size/block_size))
 97	cupy_kernel((grid_size,),(block_size,),(D,wdiag,wneigh,ineigh)+retD)
 98
 99	dt_max = 2./np.max(wdiag)
100	return (dt_max,(wdiag,wneigh,ineigh,module))+retD
101
102def anisotropic_diffusion_steps(u,dt,ndt,scheme_data,
103	block_size=1024,overwrite_u=False,out=None):
104	"""
105	Solving D_t u = div( D grad u ), with Neumann b.c.
106	Input : 
107	- u : initial data
108	- dt : time step
109	- ndt : number of time steps
110	- scheme_data : output of anisotropic_diffusion_scheme 
111	"""
112	if not overwrite_u: u=u.copy()
113	u = cp.ascontiguousarray(u)
114	if out is None: out = np.empty_like(u)
115	wdiag,wneigh,ineigh,module = scheme_data
116	float_t = wneigh.dtype 
117	assert u.dtype==float_t
118	assert u.shape==scheme_data[0].shape
119	assert out.shape==u.shape and out.dtype==u.dtype 
120	assert u.flags['C_CONTIGUOUS'] and out.flags['C_CONTIGUOUS']
121	SetModuleConstant(module,'dt',dt,float_t)
122	cupy_kernel = module.get_function("anisotropic_diffusion_step")
123	uold,unew = u,out
124	grid_size = int(np.ceil(u.size/block_size))
125	for i in range(ndt):
126		unew[:]=0.
127		cupy_kernel((grid_size,),(block_size,),(uold,unew,wdiag,wneigh,ineigh))
128		uold,unew = unew,uold
129	if out is not uold: out[:]=uold # unew and uold were just swapped
130	return out
def anisotropic_diffusion_scheme(D, dx=1.0, ced=None, periodic=False, block_size=256, traits=None):
 14def anisotropic_diffusion_scheme(D,dx=1.,ced=None,periodic=False,block_size=256,traits=None):
 15	"""
 16	First stage of solving D_t u = div( D grad u ), with Neumann b.c.
 17	Input : 
 18	- D : positive definite diffusion tensors. Format : (n1,...,nd,d*(d+1)/2)
 19	- ced (optional) : a dictionary with keys
 20	  	- alpha, gamma (real) : weickerts coherence enhancing diffusion parameters
 21	  	- cond_max,cond_amplification_threshold (real, optional) : additional parameters
 22	  	- retD (bool, optional) : wether to return the diffusion tensors 
 23
 24	  	If ced is present, then D is regarded as a structure tensor, and modified 
 25		according to Weickert's coherence enhancing diffusion principle
 26	- dx (optional): grid scale
 27	
 28	Output : 
 29	- dt_max : the largest stable time step for the scheme
 30	- scheme_data : data to be used in anisotropic_diffusion_steps 
 31	- D (optional) : the diffusion tensors, returned if ced['retD']=true 
 32	"""
 33	if D.shape[0]==D.shape[1]==D.ndim-2: # Also allow common format (d,d,n1,...,nd)  
 34		from ... import Metrics
 35		D = np.moveaxis(Metrics.misc.flatten_symmetric_matrix(D),0,-1)
 36	D = cp.ascontiguousarray(D)
 37	shape = D.shape[:-1]
 38	ndim = len(shape)
 39	size = np.prod(shape)
 40	symdim = (ndim*(ndim+1))//2
 41	decompdim = symdim # changes in dimension 4
 42	assert ndim in (1,2,3) and D.shape[-1]==symdim
 43	int_t = np.int32
 44	float_t = np.float32
 45	assert D.dtype==float_t
 46
 47	traits_default = {
 48		'ndim_macro':ndim,
 49		'Int':int_t,
 50		'Scalar':float_t,
 51		'prev_coef':1, # Set prev_coef = 0 for the pure linear operator
 52		'fourth_order_macro':False,
 53	}
 54	if traits is not None: traits_default.update(traits)
 55	traits = traits_default
 56
 57	if isinstance(periodic,numbers.Number): periodic = (periodic,)*ndim
 58	if any(periodic): 
 59		traits['periodic_macro'] = 1
 60		traits['periodic_axes'] = periodic
 61	if ced is not None: 
 62		assert ndim>1
 63		ced = ced.copy()
 64		traits['ced_macro']=1
 65		traits['retD_macro'] = ced.pop('retD',False)
 66	retD = (np.empty_like(D),) if traits.get('retD_macro',False) else tuple()
 67
 68	cuda_path = os.path.join(os.path.dirname(os.path.realpath(__file__)),"cuda")
 69	date_modified = cupy_module_helper.getmtime_max(cuda_path)
 70	source = cupy_module_helper.traits_header(traits,size_of_shape=True)
 71
 72	source += [
 73	'#include "Kernel_SellingAnisotropicDiffusion.h"',
 74	f"// Date cuda code last modified : {date_modified}"]
 75	cuoptions = ("-default-device", f"-I {cuda_path}") 
 76
 77	source = "\n".join(source)
 78	module = cupy_module_helper.GetModule(source,cuoptions)
 79	SetModuleConstant(module,'shape_tot',shape,int_t)
 80	SetModuleConstant(module,'size_tot',size,int_t)
 81	if isinstance(dx,numbers.Number): dx = (dx,)*ndim
 82	SetModuleConstant(module,'dx',dx,float_t)
 83	if ced is not None:
 84		SetModuleConstant(module,"ced_alpha",ced.pop("α"),float_t)
 85		SetModuleConstant(module,"ced_gamma",ced.pop("γ"),float_t)
 86		SetModuleConstant(module,"ced_cond_max",ced.pop("cond_max",100),float_t)
 87		SetModuleConstant(module,"ced_cond_amplification_threshold",
 88			ced.pop("cond_amplification_threshold",2),float_t)
 89		if ced: print(f"Warning : unused ced keys {list(ced.keys())}")
 90
 91	cupy_kernel = module.get_function("anisotropic_diffusion_scheme")
 92	wdiag  = np.full_like(D,0.,shape=shape)
 93	wneigh = np.empty_like(D,shape=shape+(decompdim,));
 94	nneigh = 4 if traits['fourth_order_macro'] else 2
 95	ineigh = np.full_like(D,-1,shape=shape+(decompdim,nneigh),dtype=int_t);
 96
 97	grid_size = int(np.ceil(size/block_size))
 98	cupy_kernel((grid_size,),(block_size,),(D,wdiag,wneigh,ineigh)+retD)
 99
100	dt_max = 2./np.max(wdiag)
101	return (dt_max,(wdiag,wneigh,ineigh,module))+retD

First stage of solving D_t u = div( D grad u ), with Neumann b.c. Input :

  • D : positive definite diffusion tensors. Format : (n1,...,nd,d*(d+1)/2)
  • ced (optional) : a dictionary with keys
    • alpha, gamma (real) : weickerts coherence enhancing diffusion parameters
    • cond_max,cond_amplification_threshold (real, optional) : additional parameters
    • retD (bool, optional) : wether to return the diffusion tensors
    If ced is present, then D is regarded as a structure tensor, and modified 
    according to Weickert's coherence enhancing diffusion principle

  • dx (optional): grid scale

Output :

  • dt_max : the largest stable time step for the scheme
  • scheme_data : data to be used in anisotropic_diffusion_steps
  • D (optional) : the diffusion tensors, returned if ced['retD']=true
def anisotropic_diffusion_steps( u, dt, ndt, scheme_data, block_size=1024, overwrite_u=False, out=None):
103def anisotropic_diffusion_steps(u,dt,ndt,scheme_data,
104	block_size=1024,overwrite_u=False,out=None):
105	"""
106	Solving D_t u = div( D grad u ), with Neumann b.c.
107	Input : 
108	- u : initial data
109	- dt : time step
110	- ndt : number of time steps
111	- scheme_data : output of anisotropic_diffusion_scheme 
112	"""
113	if not overwrite_u: u=u.copy()
114	u = cp.ascontiguousarray(u)
115	if out is None: out = np.empty_like(u)
116	wdiag,wneigh,ineigh,module = scheme_data
117	float_t = wneigh.dtype 
118	assert u.dtype==float_t
119	assert u.shape==scheme_data[0].shape
120	assert out.shape==u.shape and out.dtype==u.dtype 
121	assert u.flags['C_CONTIGUOUS'] and out.flags['C_CONTIGUOUS']
122	SetModuleConstant(module,'dt',dt,float_t)
123	cupy_kernel = module.get_function("anisotropic_diffusion_step")
124	uold,unew = u,out
125	grid_size = int(np.ceil(u.size/block_size))
126	for i in range(ndt):
127		unew[:]=0.
128		cupy_kernel((grid_size,),(block_size,),(uold,unew,wdiag,wneigh,ineigh))
129		uold,unew = unew,uold
130	if out is not uold: out[:]=uold # unew and uold were just swapped
131	return out

Solving D_t u = div( D grad u ), with Neumann b.c. Input :

  • u : initial data
  • dt : time step
  • ndt : number of time steps
  • scheme_data : output of anisotropic_diffusion_scheme