agd.Eikonal.HFM_CUDA.Eigh

  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 os
  5import numpy as np
  6import cupy as cp
  7
  8from . import cupy_module_helper
  9from .cupy_module_helper import SetModuleConstant
 10
 11def eigh(m,quaternion=False,flatsym=None,use_numpy=False):
 12	"""
 13	Computes the eigenvalues of the symmetric matrix m, of dimension d<=3.
 14	The cuda routines are likely more accurate, but they are often slow and memory
 15	intensive for large arrays of small matrices.
 16	Input : 
 17	 - m : array of shape (n1,...,nk,d,d) symmetric w.r.t the last two entries, or of 
 18	  shape (n1,...,nk, d(d+1)/2) if flatsym==True
 19	 - flatsym (bool, default: autodetect): format for the symmetric matrices, see above.
 20	 - quaternion (bool): format specifier for the eigenvectors, see below
 21	 - use_numpy : use the numpy.linalg.eigh routine (calls cuda)
 22	Output :
 23	 - λ : array of shape (n1,...,nk,d). The eigenvalues of m, sorted increasingly.
 24	 - v : the eigenvectors of m, in the following format
 25	   quaternion == false : shape (n1,...,nk,d,d)
 26	   - true : export the eigenvectors compactly using the quaternion format
 27	    (d=3), or omitting the second eigenvector (d=2).
 28	"""
 29	float_t = np.float32
 30	int_t = np.int32
 31	block_size = 1024
 32
 33	if flatsym is None: flatsym = not(m.ndim>=2 and m.shape[-1]==m.shape[-2]<=3)
 34	shape = m.shape[:(-1 if flatsym else -2)]
 35	size = np.prod(shape,dtype=int)
 36	d = int(np.floor(np.sqrt(2*m.shape[-1]))) if flatsym else m.shape[-1]
 37
 38	assert m.shape == ( (*shape,(d*(d+1))//2) if flatsym else (*shape,d,d) )
 39	assert isinstance(quaternion,bool) or quaternion is None
 40	assert 1<=d<=3
 41	vshape = (*shape,{1:1,2:2,3:4}[d]) if quaternion else (*shape,d,d) 
 42
 43	if d==1: # Trivial 1D case
 44		v=np.broadcast_to(cp.ones(1,dtype=float_t),vshape)
 45		λ = m.reshape((*shape,d)).astype(float_t)
 46		return λ,v
 47	if use_numpy:
 48		from ... import Sphere
 49		assert not flatsym
 50		if quaternion is None: return np.linalg.eigvalsh(m)
 51		λ,v = np.linalg.eigh(m)
 52		if not quaternion: return λ,v 
 53		v[np.linalg.det(v)<0,:,-1] *= -1 # Turn v into a direct orthogonal basis
 54		v = np.moveaxis(v,(-2,-1),(0,1))
 55		v = Sphere.sphere1_from_rotation2(v) if d==2 else Sphere.sphere3_from_rotation3(v)
 56		v = np.moveaxis(v,0,-1)
 57		return λ,v
 58	if not flatsym: 
 59		m = cp.stack([m[...,i,j] for i in range(d) for j in range(i+1)], axis=-1)
 60	m = cp.ascontiguousarray(m,dtype=float_t)
 61
 62	traits = {
 63	'Scalar':float_t,
 64	'Int':int_t,
 65	'quaternion_macro':bool(quaternion),
 66	'ndim_macro':d,
 67	}
 68
 69	cuda_path = os.path.join(os.path.dirname(os.path.realpath(__file__)),"cuda")
 70	date_modified = cupy_module_helper.getmtime_max(cuda_path)
 71	source = cupy_module_helper.traits_header(traits)
 72
 73	source += [
 74	'#include "Kernel_Eigh.h"',
 75	f"// Date cuda code last modified : {date_modified}"]
 76	cuoptions = ("-default-device", f"-I {cuda_path}") 
 77
 78	source = "\n".join(source)
 79	module = cupy_module_helper.GetModule(source,cuoptions)
 80	def setcst(*args): SetModuleConstant(module,*args)
 81	setcst('size_tot',size,int_t)
 82
 83	λ = cp.empty((*shape,d),dtype=float_t)
 84	grid_size = int(np.ceil(size/block_size))
 85	if quaternion is None: # Special value to get the eigenvalues alone
 86		f = module.get_function('kernel_eigvalsh')
 87		f((grid_size,),(block_size,),(m,λ))
 88		return λ
 89	
 90	v = cp.empty(vshape,dtype=float_t)
 91	f = module.get_function('kernel_eigh')
 92	f((grid_size,),(block_size,),(m,λ,v))
 93	return λ,v
 94
 95def eigvalsh(m,flatsym=False):
 96	"""
 97	Computes the eigenvalues of the symmetric matrix m, of dimension d<=3.
 98	The cuda routines are likely more accurate, but often slow and memory intensive.
 99	Input : 
100	 - m : array of shape (n1,...,nk,d,d) symmetric w.r.t the last two entries, or of 
101	  shape (n1,...,nk, d(d+1)/2) if flatsym==True
102	 - flatsym (bool): format for the symmetric matrices, see above
103
104	Output :
105	 - λ : array of shape (n1,...,nk,d). The eigenvalues of m, sorted increasingly.
106	"""
107	return eigh(m,flatsym=flatsym,quaternion=None)
def eigh(m, quaternion=False, flatsym=None, use_numpy=False):
12def eigh(m,quaternion=False,flatsym=None,use_numpy=False):
13	"""
14	Computes the eigenvalues of the symmetric matrix m, of dimension d<=3.
15	The cuda routines are likely more accurate, but they are often slow and memory
16	intensive for large arrays of small matrices.
17	Input : 
18	 - m : array of shape (n1,...,nk,d,d) symmetric w.r.t the last two entries, or of 
19	  shape (n1,...,nk, d(d+1)/2) if flatsym==True
20	 - flatsym (bool, default: autodetect): format for the symmetric matrices, see above.
21	 - quaternion (bool): format specifier for the eigenvectors, see below
22	 - use_numpy : use the numpy.linalg.eigh routine (calls cuda)
23	Output :
24	 - λ : array of shape (n1,...,nk,d). The eigenvalues of m, sorted increasingly.
25	 - v : the eigenvectors of m, in the following format
26	   quaternion == false : shape (n1,...,nk,d,d)
27	   - true : export the eigenvectors compactly using the quaternion format
28	    (d=3), or omitting the second eigenvector (d=2).
29	"""
30	float_t = np.float32
31	int_t = np.int32
32	block_size = 1024
33
34	if flatsym is None: flatsym = not(m.ndim>=2 and m.shape[-1]==m.shape[-2]<=3)
35	shape = m.shape[:(-1 if flatsym else -2)]
36	size = np.prod(shape,dtype=int)
37	d = int(np.floor(np.sqrt(2*m.shape[-1]))) if flatsym else m.shape[-1]
38
39	assert m.shape == ( (*shape,(d*(d+1))//2) if flatsym else (*shape,d,d) )
40	assert isinstance(quaternion,bool) or quaternion is None
41	assert 1<=d<=3
42	vshape = (*shape,{1:1,2:2,3:4}[d]) if quaternion else (*shape,d,d) 
43
44	if d==1: # Trivial 1D case
45		v=np.broadcast_to(cp.ones(1,dtype=float_t),vshape)
46		λ = m.reshape((*shape,d)).astype(float_t)
47		return λ,v
48	if use_numpy:
49		from ... import Sphere
50		assert not flatsym
51		if quaternion is None: return np.linalg.eigvalsh(m)
52		λ,v = np.linalg.eigh(m)
53		if not quaternion: return λ,v 
54		v[np.linalg.det(v)<0,:,-1] *= -1 # Turn v into a direct orthogonal basis
55		v = np.moveaxis(v,(-2,-1),(0,1))
56		v = Sphere.sphere1_from_rotation2(v) if d==2 else Sphere.sphere3_from_rotation3(v)
57		v = np.moveaxis(v,0,-1)
58		return λ,v
59	if not flatsym: 
60		m = cp.stack([m[...,i,j] for i in range(d) for j in range(i+1)], axis=-1)
61	m = cp.ascontiguousarray(m,dtype=float_t)
62
63	traits = {
64	'Scalar':float_t,
65	'Int':int_t,
66	'quaternion_macro':bool(quaternion),
67	'ndim_macro':d,
68	}
69
70	cuda_path = os.path.join(os.path.dirname(os.path.realpath(__file__)),"cuda")
71	date_modified = cupy_module_helper.getmtime_max(cuda_path)
72	source = cupy_module_helper.traits_header(traits)
73
74	source += [
75	'#include "Kernel_Eigh.h"',
76	f"// Date cuda code last modified : {date_modified}"]
77	cuoptions = ("-default-device", f"-I {cuda_path}") 
78
79	source = "\n".join(source)
80	module = cupy_module_helper.GetModule(source,cuoptions)
81	def setcst(*args): SetModuleConstant(module,*args)
82	setcst('size_tot',size,int_t)
83
84	λ = cp.empty((*shape,d),dtype=float_t)
85	grid_size = int(np.ceil(size/block_size))
86	if quaternion is None: # Special value to get the eigenvalues alone
87		f = module.get_function('kernel_eigvalsh')
88		f((grid_size,),(block_size,),(m,λ))
89		return λ
90	
91	v = cp.empty(vshape,dtype=float_t)
92	f = module.get_function('kernel_eigh')
93	f((grid_size,),(block_size,),(m,λ,v))
94	return λ,v

Computes the eigenvalues of the symmetric matrix m, of dimension d<=3. The cuda routines are likely more accurate, but they are often slow and memory intensive for large arrays of small matrices. Input :

  • m : array of shape (n1,...,nk,d,d) symmetric w.r.t the last two entries, or of shape (n1,...,nk, d(d+1)/2) if flatsym==True
  • flatsym (bool, default: autodetect): format for the symmetric matrices, see above.
  • quaternion (bool): format specifier for the eigenvectors, see below
  • use_numpy : use the numpy.linalg.eigh routine (calls cuda) Output :
  • λ : array of shape (n1,...,nk,d). The eigenvalues of m, sorted increasingly.
  • v : the eigenvectors of m, in the following format quaternion == false : shape (n1,...,nk,d,d)
    • true : export the eigenvectors compactly using the quaternion format (d=3), or omitting the second eigenvector (d=2).
def eigvalsh(m, flatsym=False):
 96def eigvalsh(m,flatsym=False):
 97	"""
 98	Computes the eigenvalues of the symmetric matrix m, of dimension d<=3.
 99	The cuda routines are likely more accurate, but often slow and memory intensive.
100	Input : 
101	 - m : array of shape (n1,...,nk,d,d) symmetric w.r.t the last two entries, or of 
102	  shape (n1,...,nk, d(d+1)/2) if flatsym==True
103	 - flatsym (bool): format for the symmetric matrices, see above
104
105	Output :
106	 - λ : array of shape (n1,...,nk,d). The eigenvalues of m, sorted increasingly.
107	"""
108	return eigh(m,flatsym=flatsym,quaternion=None)

Computes the eigenvalues of the symmetric matrix m, of dimension d<=3. The cuda routines are likely more accurate, but often slow and memory intensive. Input :

  • m : array of shape (n1,...,nk,d,d) symmetric w.r.t the last two entries, or of shape (n1,...,nk, d(d+1)/2) if flatsym==True
  • flatsym (bool): format for the symmetric matrices, see above

Output :

  • λ : array of shape (n1,...,nk,d). The eigenvalues of m, sorted increasingly.