Coverage for /usr/local/lib/python3.8/site-packages/spam/excursions/elkc.py: 87%

93 statements  

« prev     ^ index     » next       coverage.py v7.2.3, created at 2023-11-22 13:26 +0000

1""" 

2Library of SPAM functions for computation Lipschitz-Killing Curvatures. 

3Copyright (C) 2020 SPAM Contributors 

4 

5This program is free software: you can redistribute it and/or modify it 

6under the terms of the GNU General Public License as published by the Free 

7Software Foundation, either version 3 of the License, or (at your option) 

8any later version. 

9 

10This program is distributed in the hope that it will be useful, but WITHOUT 

11ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 

12FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for 

13more details. 

14 

15You should have received a copy of the GNU General Public License along with 

16this program. If not, see <http://www.gnu.org/licenses/>. 

17""" 

18 

19import numpy 

20from scipy.constants import pi 

21 

22def expectedMesures(kappa, j, n, hittingSet='tail', distributionType='gauss', mu=0.0, std=1.0, correlationType='gauss', lc=1.0, nu=2.0, a=1.0): 

23 """ 

24 Compute the Lipschitz-Killing Curvatures E(LKC) 

25 

26 Parameters 

27 ----------- 

28 kappa : float or list 

29 value of the threshold 

30 j : int 

31 number of the functionnal 

32 n : int 

33 spatial dimension 

34 hittingSet : string 

35 the hitting set 

36 distributionType : string 

37 type of distribution (see gaussianMinkowskiFunctionals) 

38 mu : float 

39 mean value of the distribution 

40 std : float 

41 standard deviation of the distribution 

42 correlationType : string 

43 type of correlation function 

44 lc : float 

45 correlation length 

46 nu : float 

47 parameter used for correlationType='matern' 

48 a : float or list of floats 

49 size(s) of the object 

50 if float, the object is considered as a cube 

51 

52 Returns 

53 -------- 

54 Same type as kappa 

55 the Gaussian Minkowski functionnal 

56 """ 

57 

58 e = 0.0 * kappa 

59 # compute second spetral moment 

60 lam2 = secondSpectralMoment(std, lc, correlationType=correlationType, nu=nu) 

61 # loop over i 

62 for i in range(n - j + 1): 

63 # comute LKC of the cube 

64 lkc = lkcCuboid(i + j, n, a) 

65 # compute GMF 

66 gmf = gaussianMinkowskiFunctionals(kappa, i, hittingSet=hittingSet, distributionType=distributionType, mu=mu, std=std) 

67 # compute 

68 fla = flag(i + j, i) 

69 e = e + fla * lkc * gmf * (lam2 / (2.0 * pi))**(i / 2.0) 

70 

71 return e 

72 

73 

74def gaussianMinkowskiFunctionals(kappa, j, hittingSet='tail', distributionType='gauss', mu=0.0, std=1.0): 

75 """ 

76 Evaluate the Gaussian Minkowski functionals j 

77 

78 Parameters 

79 ----------- 

80 kappa : float or list of floats 

81 value(s) of the threshold(s) 

82 j : int 

83 number of the functionnal 

84 hittingSet : string 

85 hitting set 

86 distributionType : string 

87 type of the underlying distribution. Implemented: ``gauss`` and ``log`` 

88 mu : float 

89 mean value of the distribution 

90 std : float 

91 standard deviation of the distribution 

92 

93 Returns 

94 -------- 

95 mink : same type as kappa 

96 the Gaussian Minkowski functionnal 

97 """ 

98 from scipy.special import erf 

99 

100 if hittingSet == 'tail': 

101 sign = 1.0 

102 elif hittingSet == 'head': 

103 sign = (-1.0)**(j + 1) 

104 else: 

105 print('error in spam.excursions.elkc.gaussianMinkowskiFunctionals(): hitting set {} not implemented.'.format(hittingSet)) 

106 return 0 

107 

108 # STEP 2: change variable 

109 if distributionType == 'gauss': 

110 k = (kappa - mu) / std 

111 elif distributionType == 'log': 

112 k = (numpy.log(kappa) - mu) / std 

113 else: 

114 print('error in spam.excursions.elkc.gaussianMinkowskiFunctionals(): distribution type {} not implemented.'.format(distributionType)) 

115 return 0 

116 

117 if j == 0: 

118 mink = 0.5 * (1.0 - sign * erf(k / 2**0.5)) 

119 elif j > 0: 

120 mink = sign * numpy.exp(-k**2.0 / 2.0) * hermitePolynomial(k, j - 1) / (std**j * (2.0 * pi)**0.5) 

121 # mink = - (k**3 - 3*k)*numpy.exp(-k**2.0/2.0) / (std**4*(2.0*pi)**0.5) 

122 else: 

123 print('error in spam.excursions.elkc.gaussianMinkowskiFunctionals(): j should be > 0. {} given.'.format(j)) 

124 return 0 

125 

126 return mink 

127 

128 

129def secondSpectralMoment(std, lc, correlationType='gauss', nu=2.0): 

130 """ 

131 Compute the second spectral moment for a given covariance function: 

132 

133 .. math:: 

134 {\lambda}_2 = C''(h)|_{h=0} 

135 

136 Parameters 

137 ----------- 

138 std : float 

139 standard deviation 

140 lc : float 

141 correlation length 

142 correlationType : string 

143 type of correlation function 

144 nu : float 

145 parameter used for correlationType='matern' 

146 

147 Returns 

148 -------- 

149 float 

150 the second spectral moment 

151 """ 

152 v = std**2.0 

153 if correlationType == 'gauss': 

154 return 2.0 * v / lc**2.0 

155 elif correlationType == 'matern': 

156 if nu <= 1: 

157 print('error in spam.excursions.elkc.secondSpectralMoment(): nu should be > 1. {} given.'.format(nu)) 

158 return 0 

159 return (v * nu) / (lc**2.0 * (nu - 1.0)) 

160 else: 

161 print('error in spam.excursions.elkc.secondSpectralMoment(): correlation type {} not implemented.'.format(correlationType)) 

162 return 0 

163 

164 

165def flag(n, j): 

166 """ 

167 Compute the flag coefficients ``[ n, j ] = ( n, j ) w_n / w_{n-j}w_j`` 

168 

169 Parameters 

170 ----------- 

171 n : int 

172 first parameter of the binom 

173 j : int 

174 second parameter of the binom 

175 

176 Returns 

177 -------- 

178 float 

179 the flag coefficient [n , j] 

180 """ 

181 from scipy.special import binom 

182 

183 if j > n: 

184 print('error in spam.excursions.elkc.flag(): n should be >= j. Here n={}, j={}'.format(n, j)) 

185 return 0 

186 vn = ballVolume(n) 

187 vj = ballVolume(j) 

188 vnj = ballVolume(n - j) 

189 

190 return binom(n, j) * vn / (vnj * vj) 

191 

192 

193def hermitePolynomial(x, n): 

194 """ 

195 Evaluate a x the probabilitic Hermite polynomial n 

196 

197 Parameters 

198 ----------- 

199 x : float 

200 point of evaluation 

201 n : int 

202 number of Hermite polynomia 

203 

204 Returns 

205 -------- 

206 float 

207 ``He_n(x)`` the value of the polynomial 

208 

209 """ 

210 from numpy.polynomial.hermite_e import hermeval 

211 

212 coefs = [0] * (n + 1) 

213 coefs[n] = 1 

214 

215 return hermeval(x, coefs) 

216 

217 

218def ballVolume(n, r=1.0): 

219 """ 

220 Compute the volume of a nth dimensional ball 

221 

222 Parameters 

223 ----------- 

224 n : int 

225 spatial dimension 

226 r : float 

227 radius of the ball 

228 

229 Returns 

230 -------- 

231 float 

232 volume of the ball 

233 """ 

234 from scipy.special import gamma 

235 

236 return float(r)**float(n) * pi**(0.5 * n) / gamma(1.0 + 0.5 * n) 

237 

238 

239def lkcCuboid(i, n, a): 

240 """ 

241 Compute the Lipschitz-Killing Curvatures of a parallelepiped 

242 

243 Parameters 

244 ----------- 

245 i : int 

246 number of the LKC. ``0 <= i <= n`` 

247 n : int 

248 spatial dimension 

249 a : float or list of float 

250 size(s) of the cuboid. If float, the object is considered to be a cube. 

251 

252 Returns 

253 -------- 

254 float 

255 The Lipschitz-Killing Curvature 

256 """ 

257 from scipy.special import binom 

258 

259 if i > n or i < 0: 

260 print('error in spam.excursions.elkc.lkcCuboid(): i should be between 0 and n. Here n={}, i={}'.format(n, i)) 

261 return 0 

262 

263 # cube 

264 if isinstance(a, (int, float)): 

265 # general formula for every i and n 

266 lkc = binom(n, i) * a**i 

267 return lkc 

268 

269 # cuboid 

270 else: 

271 # check if a is well defined 

272 if len(a) != n: 

273 print('error in spam.excursions.elkc.lkcCuboid(): Spatial dimension doesn\'t match length definition. len(a) = {} and n = {}'.format(len(a), n)) 

274 return 0 

275 

276 # 2D 

277 if n == 2: 

278 switcher = { 

279 0: 1, 

280 1: a[0] + a[1], 

281 2: a[0] * a[1], 

282 } 

283 return switcher.get(i, -1) 

284 

285 # 3D 

286 elif n == 3: 

287 switcher = { 

288 0: 1, 

289 1: a[0] + a[1] + a[2], 

290 2: a[0] * a[1] + a[0] * a[2] + a[2] * a[1], 

291 3: a[0] * a[1] * a[2], 

292 } 

293 return switcher.get(i, -1) 

294 

295 else: 

296 print('error in spam.excursions.elkc.lkcCuboid(): Spatial dimension n = {} not implemented.'.format(n)) 

297 return 0 

298 

299# 

300# def phi(x, s): 

301# return numpy.exp(-x**2/2.0)/(s*numpy.sqrt(2.0*pi)) 

302# 

303# 

304# def bigPhi(x, s): 

305# from scipy.special import erf 

306# return phi(x, s) - 0.5*x*(1.0+erf(-x/numpy.sqrt(2.0))) 

307 

308 

309# def expectedMesuresLinearThreshold(alpha, beta, j, n, hittingSet='tail', distributionType='gauss', mu=0.0, std=1.0, correlationType='gauss', lc=1.0, nu=2.0, a=1.0): 

310# """ 

311# Compute the Lipschitz-Killing Curvatures E(LKC) 

312# 

313# Parameters 

314# ----------- 

315# kappa : float or list 

316# value of the threshold 

317# j : int 

318# number of the functionnal 

319# n : int 

320# spatial dimension 

321# hittingSet : string 

322# the hitting set 

323# distributionType : string 

324# type of distribution (see gaussianMinkowskiFunctionals) 

325# mu : float 

326# mean value of the distribution 

327# std : float 

328# standard deviation of the distribution 

329# correlationType : string 

330# type of correlation function 

331# lc : float 

332# correlation length 

333# nu : float 

334# parameter used for correlationType='matern' 

335# a : float or list of floats 

336# size(s) of the object 

337# if float, the object is considered as a cube 

338# 

339# Returns 

340# -------- 

341# mink : same type as kappa 

342# the Gaussian Minkowski functionnal 

343# """ 

344# # HISTORY: 

345# # 2016-04-08: First version of the function 

346# # 

347# 

348# e = 0.0*alpha 

349# # compute second spetral moment 

350# lam2 = secondSpectralMoment(std, lc, correlationType=correlationType, nu=2.0) 

351# # loop over i 

352# # if prlv>5: print 'IN ELKC: n = {}, j = {}'.format( n, j ) 

353# # for i in range( n-j+1 ): 

354# # if prlv>5: print '\t\ti = {}'.format( i ) 

355# # # comute LKC of the cube 

356# # lkc = lkcCuboid( i+j, n, a ) 

357# # # compute GMF 

358# # gmf = gaussianMinkowskiFunctionalsLinearThreshold( alpha, beta, a, i, hittingSet=hittingSet, lam2=lam2 ) 

359# 

360# # # compute 

361# # fla = flag( i+j , i ) 

362# # e = e + fla*gmf*lkc*( lam2/(2.0*pi) )**(i/2.0) 

363# 

364# e = 0.0 

365# if n == 1: 

366# from scipy.special import erf, erfc 

367# cstA = alpha*a/(numpy.sqrt(2.0)*std) 

368# cstB = beta/(numpy.sqrt(2.0)*std) 

369# cstAB = cstA+cstB 

370# 

371# # intOfMo = (( numpy.sqrt(2.0/pi) * std * ( numpy.exp(-cstAB**2.0) - numpy.exp(-cstB**2) ) + (alpha*a+beta) * erf(cstAB) - beta*erf(cstB) )/(2.0*alpha) + 0.5*a) # tail 

372# intOfMo = ((numpy.sqrt(2.0/pi) * std * (-numpy.exp(-cstAB**2.0) + numpy.exp(-cstB**2)) + 

373# (alpha*a) * erfc(cstAB) - beta*erf(cstAB) + + beta*erf(cstB))/(2.0*alpha)) # head 

374# 

375# if j == 1: 

376# e = intOfMo 

377# elif j == 0: 

378# c = numpy.sqrt(lam2) * bigPhi(alpha/(numpy.sqrt(lam2)*std), std) 

379# intOfUpCrossed = c * (erf(cstAB) - erf(cstB))/(2.0*alpha) 

380# 

381# e = intOfMo/a + intOfUpCrossed 

382# else: 

383# print("ELKC not implemented for n={} and j={}-> exiting".format(n, j)) 

384# 

385# else: 

386# print("ELKC not implemented for n={} -> exiting".format(n)) 

387# 

388# return e 

389# 

390# 

391# def gaussianMinkowskiFunctionalsLinearThreshold(alpha, beta, a, j, hittingSet='tail', distributionType='gauss', mu=0.0, std=1.0, lam2=1.0): 

392# """ 

393# Compute the Gaussian Minkowski functionals j 

394# 

395# Parameters 

396# ----------- 

397# kappa : float or list 

398# value of the threshold 

399# j : int 

400# number of the functionnal 

401# hittingSet : string 

402# the hitting set 

403# distributionType : string 

404# type of distribution (see gaussianMinkowskiFunctionals) 

405# implemented: distributionType='gauss' and distributionType='log' 

406# mu : float 

407# mean value of the distribution 

408# std : float 

409# standard deviation of the distribution 

410# 

411# Returns: 

412# mink : same type as kappa 

413# the Gaussian Minkowski functionnal 

414# """ 

415# # HISTORY: 

416# # 2016-04-08: First version of the function 

417# # 

418# 

419# from scipy.special import erf, erfc 

420# cstA = alpha*a/(numpy.sqrt(2.0)*std) 

421# cstB = beta/(numpy.sqrt(2.0)*std) 

422# cstAB = cstA+cstB 

423# 

424# if j == 0: 

425# if hittingSet == 'tail': 

426# mink = ((numpy.sqrt(2.0/pi) * std * (numpy.exp(-cstAB**2.0) - numpy.exp(-cstB**2)) + 

427# (alpha*a+beta) * erf(cstAB) - beta*erf(cstB))/(2.0*alpha) + 0.5*a)/a 

428# elif hittingSet == 'head': 

429# mink = ((numpy.sqrt(2.0/pi) * std * (-numpy.exp(-cstAB**2.0) + numpy.exp(-cstB**2)) + 

430# (alpha*a) * erfc(cstAB) - beta*erf(cstAB) + + beta*erf(cstB))/(2.0*alpha))/a 

431# 

432# elif j == 1: 

433# if hittingSet == 'tail': 

434# mink = (erf(cstAB)-erf(cstB))/(2*alpha*a) 

435# elif hittingSet == 'head': 

436# x = alpha/(numpy.sqrt(lam2)*std) 

437# mink = (erf(cstAB)-erf(cstB))/(2*alpha*a) * bigPhi(x, std) 

438# else: 

439# print("Error GMF not implemented", j) 

440# exit() 

441# return mink 

442 

443 

444def getThresholdFromVolume(targetVolume, initialThreshold, hittingSet='tail', distributionType='gauss', mu=0.0, std=1.0, correlationType='gauss', nu=2.0, a=1.0): 

445 import scipy.optimize 

446 

447 def minFunc(t): 

448 vol = expectedMesures(t, 3, 3, hittingSet=hittingSet, distributionType=distributionType, mu=mu, std=std, correlationType=correlationType, nu=nu, a=a) 

449 # print("t = {}, vol = {}, diff = {}".format(t, vol, vol-targetVolume)) 

450 return numpy.square(vol - targetVolume) 

451 

452 res = scipy.optimize.minimize(minFunc, initialThreshold, method='BFGS') 

453 return res.x[0] 

454 

455 

456def getCorrelationLengthFormArea(targetArea, initialLength, threshold, hittingSet='tail', distributionType='gauss', mu=0.0, std=1.0, correlationType='gauss', nu=2.0, a=1.0): 

457 import scipy.optimize 

458 

459 def minFunc(l): 

460 ar = 2.0 * expectedMesures(threshold, 2, 3, hittingSet=hittingSet, distributionType=distributionType, mu=mu, std=std, lc=l, correlationType=correlationType, nu=nu, a=a) 

461 # print("l = {}, ar = {}, diff = {}".format(l, ar, ar-targetArea)) 

462 return numpy.square(ar - targetArea) 

463 

464 res = scipy.optimize.minimize(minFunc, initialLength, method='BFGS') 

465 return res.x[0]