Coverage for /usr/lib/python3/dist-packages/scipy/optimize/_shgo_lib/_complex.py: 3%

666 statements  

« prev     ^ index     » next       coverage.py v7.9.1, created at 2025-06-14 15:55 +0200

1"""Base classes for low memory simplicial complex structures.""" 

2import copy 

3import logging 

4import itertools 

5import decimal 

6from functools import cache 

7 

8import numpy 

9 

10from ._vertex import (VertexCacheField, VertexCacheIndex) 

11 

12 

13class Complex: 

14 """ 

15 Base class for a simplicial complex described as a cache of vertices 

16 together with their connections. 

17 

18 Important methods: 

19 Domain triangulation: 

20 Complex.triangulate, Complex.split_generation 

21 Triangulating arbitrary points (must be traingulable, 

22 may exist outside domain): 

23 Complex.triangulate(sample_set) 

24 Converting another simplicial complex structure data type to the 

25 structure used in Complex (ex. OBJ wavefront) 

26 Complex.convert(datatype, data) 

27 

28 Important objects: 

29 HC.V: The cache of vertices and their connection 

30 HC.H: Storage structure of all vertex groups 

31 

32 Parameters 

33 ---------- 

34 dim : int 

35 Spatial dimensionality of the complex R^dim 

36 domain : list of tuples, optional 

37 The bounds [x_l, x_u]^dim of the hyperrectangle space 

38 ex. The default domain is the hyperrectangle [0, 1]^dim 

39 Note: The domain must be convex, non-convex spaces can be cut 

40 away from this domain using the non-linear 

41 g_cons functions to define any arbitrary domain 

42 (these domains may also be disconnected from each other) 

43 sfield : 

44 A scalar function defined in the associated domain f: R^dim --> R 

45 sfield_args : tuple 

46 Additional arguments to be passed to `sfield` 

47 vfield : 

48 A scalar function defined in the associated domain 

49 f: R^dim --> R^m 

50 (for example a gradient function of the scalar field) 

51 vfield_args : tuple 

52 Additional arguments to be passed to vfield 

53 symmetry : None or list 

54 Specify if the objective function contains symmetric variables. 

55 The search space (and therefore performance) is decreased by up to 

56 O(n!) times in the fully symmetric case. 

57 

58 E.g. f(x) = (x_1 + x_2 + x_3) + (x_4)**2 + (x_5)**2 + (x_6)**2 

59 

60 In this equation x_2 and x_3 are symmetric to x_1, while x_5 and 

61 x_6 are symmetric to x_4, this can be specified to the solver as: 

62 

63 symmetry = [0, # Variable 1 

64 0, # symmetric to variable 1 

65 0, # symmetric to variable 1 

66 3, # Variable 4 

67 3, # symmetric to variable 4 

68 3, # symmetric to variable 4 

69 ] 

70 

71 constraints : dict or sequence of dict, optional 

72 Constraints definition. 

73 Function(s) ``R**n`` in the form:: 

74 

75 g(x) <= 0 applied as g : R^n -> R^m 

76 h(x) == 0 applied as h : R^n -> R^p 

77 

78 Each constraint is defined in a dictionary with fields: 

79 

80 type : str 

81 Constraint type: 'eq' for equality, 'ineq' for inequality. 

82 fun : callable 

83 The function defining the constraint. 

84 jac : callable, optional 

85 The Jacobian of `fun` (only for SLSQP). 

86 args : sequence, optional 

87 Extra arguments to be passed to the function and Jacobian. 

88 

89 Equality constraint means that the constraint function result is to 

90 be zero whereas inequality means that it is to be 

91 non-negative.constraints : dict or sequence of dict, optional 

92 Constraints definition. 

93 Function(s) ``R**n`` in the form:: 

94 

95 g(x) <= 0 applied as g : R^n -> R^m 

96 h(x) == 0 applied as h : R^n -> R^p 

97 

98 Each constraint is defined in a dictionary with fields: 

99 

100 type : str 

101 Constraint type: 'eq' for equality, 'ineq' for inequality. 

102 fun : callable 

103 The function defining the constraint. 

104 jac : callable, optional 

105 The Jacobian of `fun` (unused). 

106 args : sequence, optional 

107 Extra arguments to be passed to the function and Jacobian. 

108 

109 Equality constraint means that the constraint function result is to 

110 be zero whereas inequality means that it is to be non-negative. 

111 

112 workers : int optional 

113 Uses `multiprocessing.Pool <multiprocessing>`) to compute the field 

114 functions in parrallel. 

115 """ 

116 def __init__(self, dim, domain=None, sfield=None, sfield_args=(), 

117 symmetry=None, constraints=None, workers=1): 

118 self.dim = dim 

119 

120 # Domains 

121 self.domain = domain 

122 if domain is None: 

123 self.bounds = [(float(0), float(1.0)), ] * dim 

124 else: 

125 self.bounds = domain 

126 self.symmetry = symmetry 

127 # here in init to avoid if checks 

128 

129 # Field functions 

130 self.sfield = sfield 

131 self.sfield_args = sfield_args 

132 

133 # Process constraints 

134 # Constraints 

135 # Process constraint dict sequence: 

136 if constraints is not None: 

137 self.min_cons = constraints 

138 self.g_cons = [] 

139 self.g_args = [] 

140 if (type(constraints) is not tuple) and (type(constraints) 

141 is not list): 

142 constraints = (constraints,) 

143 

144 for cons in constraints: 

145 if cons['type'] in ('ineq'): 

146 self.g_cons.append(cons['fun']) 

147 try: 

148 self.g_args.append(cons['args']) 

149 except KeyError: 

150 self.g_args.append(()) 

151 self.g_cons = tuple(self.g_cons) 

152 self.g_args = tuple(self.g_args) 

153 else: 

154 self.g_cons = None 

155 self.g_args = None 

156 

157 # Homology properties 

158 self.gen = 0 

159 self.perm_cycle = 0 

160 

161 # Every cell is stored in a list of its generation, 

162 # ex. the initial cell is stored in self.H[0] 

163 # 1st get new cells are stored in self.H[1] etc. 

164 # When a cell is sub-generated it is removed from this list 

165 

166 self.H = [] # Storage structure of vertex groups 

167 

168 # Cache of all vertices 

169 if (sfield is not None) or (self.g_cons is not None): 

170 # Initiate a vertex cache and an associated field cache, note that 

171 # the field case is always initiated inside the vertex cache if an 

172 # associated field scalar field is defined: 

173 if sfield is not None: 

174 self.V = VertexCacheField(field=sfield, field_args=sfield_args, 

175 g_cons=self.g_cons, 

176 g_cons_args=self.g_args, 

177 workers=workers) 

178 elif self.g_cons is not None: 

179 self.V = VertexCacheField(field=sfield, field_args=sfield_args, 

180 g_cons=self.g_cons, 

181 g_cons_args=self.g_args, 

182 workers=workers) 

183 else: 

184 self.V = VertexCacheIndex() 

185 

186 self.V_non_symm = [] # List of non-symmetric vertices 

187 

188 def __call__(self): 

189 return self.H 

190 

191 # %% Triangulation methods 

192 def cyclic_product(self, bounds, origin, supremum, centroid=True): 

193 """Generate initial triangulation using cyclic product""" 

194 # Define current hyperrectangle 

195 vot = tuple(origin) 

196 vut = tuple(supremum) # Hyperrectangle supremum 

197 self.V[vot] 

198 vo = self.V[vot] 

199 yield vo.x 

200 self.V[vut].connect(self.V[vot]) 

201 yield vut 

202 # Cyclic group approach with second x_l --- x_u operation. 

203 

204 # These containers store the "lower" and "upper" vertices 

205 # corresponding to the origin or supremum of every C2 group. 

206 # It has the structure of `dim` times embedded lists each containing 

207 # these vertices as the entire complex grows. Bounds[0] has to be done 

208 # outside the loops before we have symmetric containers. 

209 # NOTE: This means that bounds[0][1] must always exist 

210 C0x = [[self.V[vot]]] 

211 a_vo = copy.copy(list(origin)) 

212 a_vo[0] = vut[0] # Update aN Origin 

213 a_vo = self.V[tuple(a_vo)] 

214 # self.V[vot].connect(self.V[tuple(a_vo)]) 

215 self.V[vot].connect(a_vo) 

216 yield a_vo.x 

217 C1x = [[a_vo]] 

218 # C1x = [[self.V[tuple(a_vo)]]] 

219 ab_C = [] # Container for a + b operations 

220 

221 # Loop over remaining bounds 

222 for i, x in enumerate(bounds[1:]): 

223 # Update lower and upper containers 

224 C0x.append([]) 

225 C1x.append([]) 

226 # try to access a second bound (if not, C1 is symmetric) 

227 try: 

228 # Early try so that we don't have to copy the cache before 

229 # moving on to next C1/C2: Try to add the operation of a new 

230 # C2 product by accessing the upper bound 

231 x[1] 

232 # Copy lists for iteration 

233 cC0x = [x[:] for x in C0x[:i + 1]] 

234 cC1x = [x[:] for x in C1x[:i + 1]] 

235 for j, (VL, VU) in enumerate(zip(cC0x, cC1x)): 

236 for k, (vl, vu) in enumerate(zip(VL, VU)): 

237 # Build aN vertices for each lower-upper pair in N: 

238 a_vl = list(vl.x) 

239 a_vu = list(vu.x) 

240 a_vl[i + 1] = vut[i + 1] 

241 a_vu[i + 1] = vut[i + 1] 

242 a_vl = self.V[tuple(a_vl)] 

243 

244 # Connect vertices in N to corresponding vertices 

245 # in aN: 

246 vl.connect(a_vl) 

247 

248 yield a_vl.x 

249 

250 a_vu = self.V[tuple(a_vu)] 

251 # Connect vertices in N to corresponding vertices 

252 # in aN: 

253 vu.connect(a_vu) 

254 

255 # Connect new vertex pair in aN: 

256 a_vl.connect(a_vu) 

257 

258 # Connect lower pair to upper (triangulation 

259 # operation of a + b (two arbitrary operations): 

260 vl.connect(a_vu) 

261 ab_C.append((vl, a_vu)) 

262 

263 # Update the containers 

264 C0x[i + 1].append(vl) 

265 C0x[i + 1].append(vu) 

266 C1x[i + 1].append(a_vl) 

267 C1x[i + 1].append(a_vu) 

268 

269 # Update old containers 

270 C0x[j].append(a_vl) 

271 C1x[j].append(a_vu) 

272 

273 # Yield new points 

274 yield a_vu.x 

275 

276 # Try to connect aN lower source of previous a + b 

277 # operation with a aN vertex 

278 ab_Cc = copy.copy(ab_C) 

279 

280 for vp in ab_Cc: 

281 b_v = list(vp[0].x) 

282 ab_v = list(vp[1].x) 

283 b_v[i + 1] = vut[i + 1] 

284 ab_v[i + 1] = vut[i + 1] 

285 b_v = self.V[tuple(b_v)] # b + vl 

286 ab_v = self.V[tuple(ab_v)] # b + a_vl 

287 # Note o---o is already connected 

288 vp[0].connect(ab_v) # o-s 

289 b_v.connect(ab_v) # s-s 

290 

291 # Add new list of cross pairs 

292 ab_C.append((vp[0], ab_v)) 

293 ab_C.append((b_v, ab_v)) 

294 

295 except IndexError: 

296 cC0x = C0x[i] 

297 cC1x = C1x[i] 

298 VL, VU = cC0x, cC1x 

299 for k, (vl, vu) in enumerate(zip(VL, VU)): 

300 # Build aN vertices for each lower-upper pair in N: 

301 a_vu = list(vu.x) 

302 a_vu[i + 1] = vut[i + 1] 

303 # Connect vertices in N to corresponding vertices 

304 # in aN: 

305 a_vu = self.V[tuple(a_vu)] 

306 # Connect vertices in N to corresponding vertices 

307 # in aN: 

308 vu.connect(a_vu) 

309 # Connect new vertex pair in aN: 

310 # a_vl.connect(a_vu) 

311 # Connect lower pair to upper (triangulation 

312 # operation of a + b (two arbitrary operations): 

313 vl.connect(a_vu) 

314 ab_C.append((vl, a_vu)) 

315 C0x[i + 1].append(vu) 

316 C1x[i + 1].append(a_vu) 

317 # Yield new points 

318 a_vu.connect(self.V[vut]) 

319 yield a_vu.x 

320 ab_Cc = copy.copy(ab_C) 

321 for vp in ab_Cc: 

322 if vp[1].x[i] == vut[i]: 

323 ab_v = list(vp[1].x) 

324 ab_v[i + 1] = vut[i + 1] 

325 ab_v = self.V[tuple(ab_v)] # b + a_vl 

326 # Note o---o is already connected 

327 vp[0].connect(ab_v) # o-s 

328 

329 # Add new list of cross pairs 

330 ab_C.append((vp[0], ab_v)) 

331 

332 # Clean class trash 

333 try: 

334 del C0x 

335 del cC0x 

336 del C1x 

337 del cC1x 

338 del ab_C 

339 del ab_Cc 

340 except UnboundLocalError: 

341 pass 

342 

343 # Extra yield to ensure that the triangulation is completed 

344 if centroid: 

345 vo = self.V[vot] 

346 vs = self.V[vut] 

347 # Disconnect the origin and supremum 

348 vo.disconnect(vs) 

349 # Build centroid 

350 vc = self.split_edge(vot, vut) 

351 for v in vo.nn: 

352 v.connect(vc) 

353 yield vc.x 

354 return vc.x 

355 else: 

356 yield vut 

357 return vut 

358 

359 def triangulate(self, n=None, symmetry=None, centroid=True, 

360 printout=False): 

361 """ 

362 Triangulate the initial domain, if n is not None then a limited number 

363 of points will be generated 

364 

365 Parameters 

366 ---------- 

367 n : int, Number of points to be sampled. 

368 symmetry : 

369 

370 Ex. Dictionary/hashtable 

371 f(x) = (x_1 + x_2 + x_3) + (x_4)**2 + (x_5)**2 + (x_6)**2 

372 

373 symmetry = symmetry[0]: 0, # Variable 1 

374 symmetry[1]: 0, # symmetric to variable 1 

375 symmetry[2]: 0, # symmetric to variable 1 

376 symmetry[3]: 3, # Variable 4 

377 symmetry[4]: 3, # symmetric to variable 4 

378 symmetry[5]: 3, # symmetric to variable 4 

379 } 

380 centroid : bool, if True add a central point to the hypercube 

381 printout : bool, if True print out results 

382 

383 NOTES: 

384 ------ 

385 Rather than using the combinatorial algorithm to connect vertices we 

386 make the following observation: 

387 

388 The bound pairs are similar a C2 cyclic group and the structure is 

389 formed using the cartesian product: 

390 

391 H = C2 x C2 x C2 ... x C2 (dim times) 

392 

393 So construct any normal subgroup N and consider H/N first, we connect 

394 all vertices within N (ex. N is C2 (the first dimension), then we move 

395 to a left coset aN (an operation moving around the defined H/N group by 

396 for example moving from the lower bound in C2 (dimension 2) to the 

397 higher bound in C2. During this operation connection all the vertices. 

398 Now repeat the N connections. Note that these elements can be connected 

399 in parrallel. 

400 """ 

401 # Inherit class arguments 

402 if symmetry is None: 

403 symmetry = self.symmetry 

404 # Build origin and supremum vectors 

405 origin = [i[0] for i in self.bounds] 

406 self.origin = origin 

407 supremum = [i[1] for i in self.bounds] 

408 

409 self.supremum = supremum 

410 

411 if symmetry is None: 

412 cbounds = self.bounds 

413 else: 

414 cbounds = copy.copy(self.bounds) 

415 for i, j in enumerate(symmetry): 

416 if i is not j: 

417 # pop second entry on second symmetry vars 

418 cbounds[i] = [self.bounds[symmetry[i]][0]] 

419 # Sole (first) entry is the sup value and there is no 

420 # origin: 

421 cbounds[i] = [self.bounds[symmetry[i]][1]] 

422 if (self.bounds[symmetry[i]] is not 

423 self.bounds[symmetry[j]]): 

424 logging.warning(f"Variable {i} was specified as " 

425 f"symmetetric to variable {j}, however" 

426 f", the bounds {i} =" 

427 f" {self.bounds[symmetry[i]]} and {j}" 

428 f" =" 

429 f" {self.bounds[symmetry[j]]} do not " 

430 f"match, the mismatch was ignored in " 

431 f"the initial triangulation.") 

432 cbounds[i] = self.bounds[symmetry[j]] 

433 

434 if n is None: 

435 # Build generator 

436 self.cp = self.cyclic_product(cbounds, origin, supremum, centroid) 

437 for i in self.cp: 

438 i 

439 

440 try: 

441 self.triangulated_vectors.append((tuple(self.origin), 

442 tuple(self.supremum))) 

443 except (AttributeError, KeyError): 

444 self.triangulated_vectors = [(tuple(self.origin), 

445 tuple(self.supremum))] 

446 

447 else: 

448 # Check if generator already exists 

449 try: 

450 self.cp 

451 except (AttributeError, KeyError): 

452 self.cp = self.cyclic_product(cbounds, origin, supremum, 

453 centroid) 

454 

455 try: 

456 while len(self.V.cache) < n: 

457 next(self.cp) 

458 except StopIteration: 

459 try: 

460 self.triangulated_vectors.append((tuple(self.origin), 

461 tuple(self.supremum))) 

462 except (AttributeError, KeyError): 

463 self.triangulated_vectors = [(tuple(self.origin), 

464 tuple(self.supremum))] 

465 

466 if printout: 

467 # for v in self.C0(): 

468 # v.print_out() 

469 for v in self.V.cache: 

470 self.V[v].print_out() 

471 

472 return 

473 

474 def refine(self, n=1): 

475 if n is None: 

476 try: 

477 self.triangulated_vectors 

478 self.refine_all() 

479 return 

480 except AttributeError as ae: 

481 if str(ae) == "'Complex' object has no attribute " \ 

482 "'triangulated_vectors'": 

483 self.triangulate(symmetry=self.symmetry) 

484 return 

485 else: 

486 raise 

487 

488 nt = len(self.V.cache) + n # Target number of total vertices 

489 # In the outer while loop we iterate until we have added an extra `n` 

490 # vertices to the complex: 

491 while len(self.V.cache) < nt: # while loop 1 

492 try: # try 1 

493 # Try to access triangulated_vectors, this should only be 

494 # defined if an initial triangulation has already been 

495 # performed: 

496 self.triangulated_vectors 

497 # Try a usual iteration of the current generator, if it 

498 # does not exist or is exhausted then produce a new generator 

499 try: # try 2 

500 next(self.rls) 

501 except (AttributeError, StopIteration, KeyError): 

502 vp = self.triangulated_vectors[0] 

503 self.rls = self.refine_local_space(*vp, bounds=self.bounds) 

504 next(self.rls) 

505 

506 except (AttributeError, KeyError): 

507 # If an initial triangulation has not been completed, then 

508 # we start/continue the initial triangulation targeting `nt` 

509 # vertices, if nt is greater than the initial number of 

510 # vertices then the `refine` routine will move back to try 1. 

511 self.triangulate(nt, self.symmetry) 

512 return 

513 

514 def refine_all(self, centroids=True): 

515 """Refine the entire domain of the current complex.""" 

516 try: 

517 self.triangulated_vectors 

518 tvs = copy.copy(self.triangulated_vectors) 

519 for i, vp in enumerate(tvs): 

520 self.rls = self.refine_local_space(*vp, bounds=self.bounds) 

521 for i in self.rls: 

522 i 

523 except AttributeError as ae: 

524 if str(ae) == "'Complex' object has no attribute " \ 

525 "'triangulated_vectors'": 

526 self.triangulate(symmetry=self.symmetry, centroid=centroids) 

527 else: 

528 raise 

529 

530 # This adds a centroid to every new sub-domain generated and defined 

531 # by self.triangulated_vectors, in addition the vertices ! to complete 

532 # the triangulation 

533 return 

534 

535 def refine_local_space(self, origin, supremum, bounds, centroid=1): 

536 # Copy for later removal 

537 origin_c = copy.copy(origin) 

538 supremum_c = copy.copy(supremum) 

539 

540 # Initiate local variables redefined in later inner `for` loop: 

541 vl, vu, a_vu = None, None, None 

542 

543 # Change the vector orientation so that it is only increasing 

544 s_ov = list(origin) 

545 s_origin = list(origin) 

546 s_sv = list(supremum) 

547 s_supremum = list(supremum) 

548 for i, vi in enumerate(s_origin): 

549 if s_ov[i] > s_sv[i]: 

550 s_origin[i] = s_sv[i] 

551 s_supremum[i] = s_ov[i] 

552 

553 vot = tuple(s_origin) 

554 vut = tuple(s_supremum) # Hyperrectangle supremum 

555 

556 vo = self.V[vot] # initiate if doesn't exist yet 

557 vs = self.V[vut] 

558 # Start by finding the old centroid of the new space: 

559 vco = self.split_edge(vo.x, vs.x) # Split in case not centroid arg 

560 

561 # Find set of extreme vertices in current local space 

562 sup_set = copy.copy(vco.nn) 

563 # Cyclic group approach with second x_l --- x_u operation. 

564 

565 # These containers store the "lower" and "upper" vertices 

566 # corresponding to the origin or supremum of every C2 group. 

567 # It has the structure of `dim` times embedded lists each containing 

568 # these vertices as the entire complex grows. Bounds[0] has to be done 

569 # outside the loops before we have symmetric containers. 

570 # NOTE: This means that bounds[0][1] must always exist 

571 

572 a_vl = copy.copy(list(vot)) 

573 a_vl[0] = vut[0] # Update aN Origin 

574 if tuple(a_vl) not in self.V.cache: 

575 vo = self.V[vot] # initiate if doesn't exist yet 

576 vs = self.V[vut] 

577 # Start by finding the old centroid of the new space: 

578 vco = self.split_edge(vo.x, vs.x) # Split in case not centroid arg 

579 

580 # Find set of extreme vertices in current local space 

581 sup_set = copy.copy(vco.nn) 

582 a_vl = copy.copy(list(vot)) 

583 a_vl[0] = vut[0] # Update aN Origin 

584 a_vl = self.V[tuple(a_vl)] 

585 else: 

586 a_vl = self.V[tuple(a_vl)] 

587 

588 c_v = self.split_edge(vo.x, a_vl.x) 

589 c_v.connect(vco) 

590 yield c_v.x 

591 Cox = [[vo]] 

592 Ccx = [[c_v]] 

593 Cux = [[a_vl]] 

594 ab_C = [] # Container for a + b operations 

595 s_ab_C = [] # Container for symmetric a + b operations 

596 

597 # Loop over remaining bounds 

598 for i, x in enumerate(bounds[1:]): 

599 # Update lower and upper containers 

600 Cox.append([]) 

601 Ccx.append([]) 

602 Cux.append([]) 

603 # try to access a second bound (if not, C1 is symmetric) 

604 try: 

605 t_a_vl = list(vot) 

606 t_a_vl[i + 1] = vut[i + 1] 

607 

608 # New: lists are used anyway, so copy all 

609 # %% 

610 # Copy lists for iteration 

611 cCox = [x[:] for x in Cox[:i + 1]] 

612 cCcx = [x[:] for x in Ccx[:i + 1]] 

613 cCux = [x[:] for x in Cux[:i + 1]] 

614 # Try to connect aN lower source of previous a + b 

615 # operation with a aN vertex 

616 ab_Cc = copy.copy(ab_C) # NOTE: We append ab_C in the 

617 # (VL, VC, VU) for-loop, but we use the copy of the list in the 

618 # ab_Cc for-loop. 

619 s_ab_Cc = copy.copy(s_ab_C) 

620 

621 # Early try so that we don't have to copy the cache before 

622 # moving on to next C1/C2: Try to add the operation of a new 

623 # C2 product by accessing the upper bound 

624 if tuple(t_a_vl) not in self.V.cache: 

625 # Raise error to continue symmetric refine 

626 raise IndexError 

627 t_a_vu = list(vut) 

628 t_a_vu[i + 1] = vut[i + 1] 

629 if tuple(t_a_vu) not in self.V.cache: 

630 # Raise error to continue symmetric refine: 

631 raise IndexError 

632 

633 for vectors in s_ab_Cc: 

634 # s_ab_C.append([c_vc, vl, vu, a_vu]) 

635 bc_vc = list(vectors[0].x) 

636 b_vl = list(vectors[1].x) 

637 b_vu = list(vectors[2].x) 

638 ba_vu = list(vectors[3].x) 

639 

640 bc_vc[i + 1] = vut[i + 1] 

641 b_vl[i + 1] = vut[i + 1] 

642 b_vu[i + 1] = vut[i + 1] 

643 ba_vu[i + 1] = vut[i + 1] 

644 

645 bc_vc = self.V[tuple(bc_vc)] 

646 bc_vc.connect(vco) # NOTE: Unneeded? 

647 yield bc_vc 

648 

649 # Split to centre, call this centre group "d = 0.5*a" 

650 d_bc_vc = self.split_edge(vectors[0].x, bc_vc.x) 

651 d_bc_vc.connect(bc_vc) 

652 d_bc_vc.connect(vectors[1]) # Connect all to centroid 

653 d_bc_vc.connect(vectors[2]) # Connect all to centroid 

654 d_bc_vc.connect(vectors[3]) # Connect all to centroid 

655 yield d_bc_vc.x 

656 b_vl = self.V[tuple(b_vl)] 

657 bc_vc.connect(b_vl) # Connect aN cross pairs 

658 d_bc_vc.connect(b_vl) # Connect all to centroid 

659 

660 yield b_vl 

661 b_vu = self.V[tuple(b_vu)] 

662 bc_vc.connect(b_vu) # Connect aN cross pairs 

663 d_bc_vc.connect(b_vu) # Connect all to centroid 

664 

665 b_vl_c = self.split_edge(b_vu.x, b_vl.x) 

666 bc_vc.connect(b_vl_c) 

667 

668 yield b_vu 

669 ba_vu = self.V[tuple(ba_vu)] 

670 bc_vc.connect(ba_vu) # Connect aN cross pairs 

671 d_bc_vc.connect(ba_vu) # Connect all to centroid 

672 

673 # Split the a + b edge of the initial triangulation: 

674 os_v = self.split_edge(vectors[1].x, ba_vu.x) # o-s 

675 ss_v = self.split_edge(b_vl.x, ba_vu.x) # s-s 

676 b_vu_c = self.split_edge(b_vu.x, ba_vu.x) 

677 bc_vc.connect(b_vu_c) 

678 yield os_v.x # often equal to vco, but not always 

679 yield ss_v.x # often equal to bc_vu, but not always 

680 yield ba_vu 

681 # Split remaining to centre, call this centre group 

682 # "d = 0.5*a" 

683 d_bc_vc = self.split_edge(vectors[0].x, bc_vc.x) 

684 d_bc_vc.connect(vco) # NOTE: Unneeded? 

685 yield d_bc_vc.x 

686 d_b_vl = self.split_edge(vectors[1].x, b_vl.x) 

687 d_bc_vc.connect(vco) # NOTE: Unneeded? 

688 d_bc_vc.connect(d_b_vl) # Connect dN cross pairs 

689 yield d_b_vl.x 

690 d_b_vu = self.split_edge(vectors[2].x, b_vu.x) 

691 d_bc_vc.connect(vco) # NOTE: Unneeded? 

692 d_bc_vc.connect(d_b_vu) # Connect dN cross pairs 

693 yield d_b_vu.x 

694 d_ba_vu = self.split_edge(vectors[3].x, ba_vu.x) 

695 d_bc_vc.connect(vco) # NOTE: Unneeded? 

696 d_bc_vc.connect(d_ba_vu) # Connect dN cross pairs 

697 yield d_ba_vu 

698 

699 # comb = [c_vc, vl, vu, a_vl, a_vu, 

700 # bc_vc, b_vl, b_vu, ba_vl, ba_vu] 

701 comb = [vl, vu, a_vu, 

702 b_vl, b_vu, ba_vu] 

703 comb_iter = itertools.combinations(comb, 2) 

704 for vecs in comb_iter: 

705 self.split_edge(vecs[0].x, vecs[1].x) 

706 # Add new list of cross pairs 

707 ab_C.append((d_bc_vc, vectors[1], b_vl, a_vu, ba_vu)) 

708 ab_C.append((d_bc_vc, vl, b_vl, a_vu, ba_vu)) # = prev 

709 

710 for vectors in ab_Cc: 

711 bc_vc = list(vectors[0].x) 

712 b_vl = list(vectors[1].x) 

713 b_vu = list(vectors[2].x) 

714 ba_vl = list(vectors[3].x) 

715 ba_vu = list(vectors[4].x) 

716 bc_vc[i + 1] = vut[i + 1] 

717 b_vl[i + 1] = vut[i + 1] 

718 b_vu[i + 1] = vut[i + 1] 

719 ba_vl[i + 1] = vut[i + 1] 

720 ba_vu[i + 1] = vut[i + 1] 

721 bc_vc = self.V[tuple(bc_vc)] 

722 bc_vc.connect(vco) # NOTE: Unneeded? 

723 yield bc_vc 

724 

725 # Split to centre, call this centre group "d = 0.5*a" 

726 d_bc_vc = self.split_edge(vectors[0].x, bc_vc.x) 

727 d_bc_vc.connect(bc_vc) 

728 d_bc_vc.connect(vectors[1]) # Connect all to centroid 

729 d_bc_vc.connect(vectors[2]) # Connect all to centroid 

730 d_bc_vc.connect(vectors[3]) # Connect all to centroid 

731 d_bc_vc.connect(vectors[4]) # Connect all to centroid 

732 yield d_bc_vc.x 

733 b_vl = self.V[tuple(b_vl)] 

734 bc_vc.connect(b_vl) # Connect aN cross pairs 

735 d_bc_vc.connect(b_vl) # Connect all to centroid 

736 yield b_vl 

737 b_vu = self.V[tuple(b_vu)] 

738 bc_vc.connect(b_vu) # Connect aN cross pairs 

739 d_bc_vc.connect(b_vu) # Connect all to centroid 

740 yield b_vu 

741 ba_vl = self.V[tuple(ba_vl)] 

742 bc_vc.connect(ba_vl) # Connect aN cross pairs 

743 d_bc_vc.connect(ba_vl) # Connect all to centroid 

744 self.split_edge(b_vu.x, ba_vl.x) 

745 yield ba_vl 

746 ba_vu = self.V[tuple(ba_vu)] 

747 bc_vc.connect(ba_vu) # Connect aN cross pairs 

748 d_bc_vc.connect(ba_vu) # Connect all to centroid 

749 # Split the a + b edge of the initial triangulation: 

750 os_v = self.split_edge(vectors[1].x, ba_vu.x) # o-s 

751 ss_v = self.split_edge(b_vl.x, ba_vu.x) # s-s 

752 yield os_v.x # often equal to vco, but not always 

753 yield ss_v.x # often equal to bc_vu, but not always 

754 yield ba_vu 

755 # Split remaining to centre, call this centre group 

756 # "d = 0.5*a" 

757 d_bc_vc = self.split_edge(vectors[0].x, bc_vc.x) 

758 d_bc_vc.connect(vco) # NOTE: Unneeded? 

759 yield d_bc_vc.x 

760 d_b_vl = self.split_edge(vectors[1].x, b_vl.x) 

761 d_bc_vc.connect(vco) # NOTE: Unneeded? 

762 d_bc_vc.connect(d_b_vl) # Connect dN cross pairs 

763 yield d_b_vl.x 

764 d_b_vu = self.split_edge(vectors[2].x, b_vu.x) 

765 d_bc_vc.connect(vco) # NOTE: Unneeded? 

766 d_bc_vc.connect(d_b_vu) # Connect dN cross pairs 

767 yield d_b_vu.x 

768 d_ba_vl = self.split_edge(vectors[3].x, ba_vl.x) 

769 d_bc_vc.connect(vco) # NOTE: Unneeded? 

770 d_bc_vc.connect(d_ba_vl) # Connect dN cross pairs 

771 yield d_ba_vl 

772 d_ba_vu = self.split_edge(vectors[4].x, ba_vu.x) 

773 d_bc_vc.connect(vco) # NOTE: Unneeded? 

774 d_bc_vc.connect(d_ba_vu) # Connect dN cross pairs 

775 yield d_ba_vu 

776 c_vc, vl, vu, a_vl, a_vu = vectors 

777 

778 comb = [vl, vu, a_vl, a_vu, 

779 b_vl, b_vu, ba_vl, ba_vu] 

780 comb_iter = itertools.combinations(comb, 2) 

781 for vecs in comb_iter: 

782 self.split_edge(vecs[0].x, vecs[1].x) 

783 

784 # Add new list of cross pairs 

785 ab_C.append((bc_vc, b_vl, b_vu, ba_vl, ba_vu)) 

786 ab_C.append((d_bc_vc, d_b_vl, d_b_vu, d_ba_vl, d_ba_vu)) 

787 ab_C.append((d_bc_vc, vectors[1], b_vl, a_vu, ba_vu)) 

788 ab_C.append((d_bc_vc, vu, b_vu, a_vl, ba_vl)) 

789 

790 for j, (VL, VC, VU) in enumerate(zip(cCox, cCcx, cCux)): 

791 for k, (vl, vc, vu) in enumerate(zip(VL, VC, VU)): 

792 # Build aN vertices for each lower-upper C3 group in N: 

793 a_vl = list(vl.x) 

794 a_vu = list(vu.x) 

795 a_vl[i + 1] = vut[i + 1] 

796 a_vu[i + 1] = vut[i + 1] 

797 a_vl = self.V[tuple(a_vl)] 

798 a_vu = self.V[tuple(a_vu)] 

799 # Note, build (a + vc) later for consistent yields 

800 # Split the a + b edge of the initial triangulation: 

801 c_vc = self.split_edge(vl.x, a_vu.x) 

802 self.split_edge(vl.x, vu.x) # Equal to vc 

803 # Build cN vertices for each lower-upper C3 group in N: 

804 c_vc.connect(vco) 

805 c_vc.connect(vc) 

806 c_vc.connect(vl) # Connect c + ac operations 

807 c_vc.connect(vu) # Connect c + ac operations 

808 c_vc.connect(a_vl) # Connect c + ac operations 

809 c_vc.connect(a_vu) # Connect c + ac operations 

810 yield c_vc.x 

811 c_vl = self.split_edge(vl.x, a_vl.x) 

812 c_vl.connect(vco) 

813 c_vc.connect(c_vl) # Connect cN group vertices 

814 yield c_vl.x 

815 # yield at end of loop: 

816 c_vu = self.split_edge(vu.x, a_vu.x) 

817 c_vu.connect(vco) 

818 # Connect remaining cN group vertices 

819 c_vc.connect(c_vu) # Connect cN group vertices 

820 yield c_vu.x 

821 

822 a_vc = self.split_edge(a_vl.x, a_vu.x) # is (a + vc) ? 

823 a_vc.connect(vco) 

824 a_vc.connect(c_vc) 

825 

826 # Storage for connecting c + ac operations: 

827 ab_C.append((c_vc, vl, vu, a_vl, a_vu)) 

828 

829 # Update the containers 

830 Cox[i + 1].append(vl) 

831 Cox[i + 1].append(vc) 

832 Cox[i + 1].append(vu) 

833 Ccx[i + 1].append(c_vl) 

834 Ccx[i + 1].append(c_vc) 

835 Ccx[i + 1].append(c_vu) 

836 Cux[i + 1].append(a_vl) 

837 Cux[i + 1].append(a_vc) 

838 Cux[i + 1].append(a_vu) 

839 

840 # Update old containers 

841 Cox[j].append(c_vl) # ! 

842 Cox[j].append(a_vl) 

843 Ccx[j].append(c_vc) # ! 

844 Ccx[j].append(a_vc) # ! 

845 Cux[j].append(c_vu) # ! 

846 Cux[j].append(a_vu) 

847 

848 # Yield new points 

849 yield a_vc.x 

850 

851 except IndexError: 

852 for vectors in ab_Cc: 

853 ba_vl = list(vectors[3].x) 

854 ba_vu = list(vectors[4].x) 

855 ba_vl[i + 1] = vut[i + 1] 

856 ba_vu[i + 1] = vut[i + 1] 

857 ba_vu = self.V[tuple(ba_vu)] 

858 yield ba_vu 

859 d_bc_vc = self.split_edge(vectors[1].x, ba_vu.x) # o-s 

860 yield ba_vu 

861 d_bc_vc.connect(vectors[1]) # Connect all to centroid 

862 d_bc_vc.connect(vectors[2]) # Connect all to centroid 

863 d_bc_vc.connect(vectors[3]) # Connect all to centroid 

864 d_bc_vc.connect(vectors[4]) # Connect all to centroid 

865 yield d_bc_vc.x 

866 ba_vl = self.V[tuple(ba_vl)] 

867 yield ba_vl 

868 d_ba_vl = self.split_edge(vectors[3].x, ba_vl.x) 

869 d_ba_vu = self.split_edge(vectors[4].x, ba_vu.x) 

870 d_ba_vc = self.split_edge(d_ba_vl.x, d_ba_vu.x) 

871 yield d_ba_vl 

872 yield d_ba_vu 

873 yield d_ba_vc 

874 c_vc, vl, vu, a_vl, a_vu = vectors 

875 comb = [vl, vu, a_vl, a_vu, 

876 ba_vl, 

877 ba_vu] 

878 comb_iter = itertools.combinations(comb, 2) 

879 for vecs in comb_iter: 

880 self.split_edge(vecs[0].x, vecs[1].x) 

881 

882 # Copy lists for iteration 

883 cCox = Cox[i] 

884 cCcx = Ccx[i] 

885 cCux = Cux[i] 

886 VL, VC, VU = cCox, cCcx, cCux 

887 for k, (vl, vc, vu) in enumerate(zip(VL, VC, VU)): 

888 # Build aN vertices for each lower-upper pair in N: 

889 a_vu = list(vu.x) 

890 a_vu[i + 1] = vut[i + 1] 

891 

892 # Connect vertices in N to corresponding vertices 

893 # in aN: 

894 a_vu = self.V[tuple(a_vu)] 

895 yield a_vl.x 

896 # Split the a + b edge of the initial triangulation: 

897 c_vc = self.split_edge(vl.x, a_vu.x) 

898 self.split_edge(vl.x, vu.x) # Equal to vc 

899 c_vc.connect(vco) 

900 c_vc.connect(vc) 

901 c_vc.connect(vl) # Connect c + ac operations 

902 c_vc.connect(vu) # Connect c + ac operations 

903 c_vc.connect(a_vu) # Connect c + ac operations 

904 yield (c_vc.x) 

905 c_vu = self.split_edge(vu.x, 

906 a_vu.x) # yield at end of loop 

907 c_vu.connect(vco) 

908 # Connect remaining cN group vertices 

909 c_vc.connect(c_vu) # Connect cN group vertices 

910 yield (c_vu.x) 

911 

912 # Update the containers 

913 Cox[i + 1].append(vu) 

914 Ccx[i + 1].append(c_vu) 

915 Cux[i + 1].append(a_vu) 

916 

917 # Update old containers 

918 s_ab_C.append([c_vc, vl, vu, a_vu]) 

919 

920 yield a_vu.x 

921 

922 # Clean class trash 

923 try: 

924 del Cox 

925 del Ccx 

926 del Cux 

927 del ab_C 

928 del ab_Cc 

929 except UnboundLocalError: 

930 pass 

931 

932 try: 

933 self.triangulated_vectors.remove((tuple(origin_c), 

934 tuple(supremum_c))) 

935 except ValueError: 

936 # Turn this into a logging warning? 

937 pass 

938 # Add newly triangulated vectors: 

939 for vs in sup_set: 

940 self.triangulated_vectors.append((tuple(vco.x), tuple(vs.x))) 

941 

942 # Extra yield to ensure that the triangulation is completed 

943 if centroid: 

944 vcn_set = set() 

945 c_nn_lists = [] 

946 for vs in sup_set: 

947 # Build centroid 

948 c_nn = self.vpool(vco.x, vs.x) 

949 try: 

950 c_nn.remove(vcn_set) 

951 except KeyError: 

952 pass 

953 c_nn_lists.append(c_nn) 

954 

955 for c_nn in c_nn_lists: 

956 try: 

957 c_nn.remove(vcn_set) 

958 except KeyError: 

959 pass 

960 

961 for vs, c_nn in zip(sup_set, c_nn_lists): 

962 # Build centroid 

963 vcn = self.split_edge(vco.x, vs.x) 

964 vcn_set.add(vcn) 

965 try: # Shouldn't be needed? 

966 c_nn.remove(vcn_set) 

967 except KeyError: 

968 pass 

969 for vnn in c_nn: 

970 vcn.connect(vnn) 

971 yield vcn.x 

972 else: 

973 pass 

974 

975 yield vut 

976 return 

977 

978 def refine_star(self, v): 

979 """Refine the star domain of a vertex `v`.""" 

980 # Copy lists before iteration 

981 vnn = copy.copy(v.nn) 

982 v1nn = [] 

983 d_v0v1_set = set() 

984 for v1 in vnn: 

985 v1nn.append(copy.copy(v1.nn)) 

986 

987 for v1, v1nn in zip(vnn, v1nn): 

988 vnnu = v1nn.intersection(vnn) 

989 

990 d_v0v1 = self.split_edge(v.x, v1.x) 

991 for o_d_v0v1 in d_v0v1_set: 

992 d_v0v1.connect(o_d_v0v1) 

993 d_v0v1_set.add(d_v0v1) 

994 for v2 in vnnu: 

995 d_v1v2 = self.split_edge(v1.x, v2.x) 

996 d_v0v1.connect(d_v1v2) 

997 return 

998 

999 @cache 

1000 def split_edge(self, v1, v2): 

1001 v1 = self.V[v1] 

1002 v2 = self.V[v2] 

1003 # Destroy original edge, if it exists: 

1004 v1.disconnect(v2) 

1005 # Compute vertex on centre of edge: 

1006 try: 

1007 vct = (v2.x_a - v1.x_a) / 2.0 + v1.x_a 

1008 except TypeError: # Allow for decimal operations 

1009 vct = (v2.x_a - v1.x_a) / decimal.Decimal(2.0) + v1.x_a 

1010 

1011 vc = self.V[tuple(vct)] 

1012 # Connect to original 2 vertices to the new centre vertex 

1013 vc.connect(v1) 

1014 vc.connect(v2) 

1015 return vc 

1016 

1017 def vpool(self, origin, supremum): 

1018 vot = tuple(origin) 

1019 vst = tuple(supremum) 

1020 # Initiate vertices in case they don't exist 

1021 vo = self.V[vot] 

1022 vs = self.V[vst] 

1023 

1024 # Remove origin - supremum disconnect 

1025 

1026 # Find the lower/upper bounds of the refinement hyperrectangle 

1027 bl = list(vot) 

1028 bu = list(vst) 

1029 for i, (voi, vsi) in enumerate(zip(vot, vst)): 

1030 if bl[i] > vsi: 

1031 bl[i] = vsi 

1032 if bu[i] < voi: 

1033 bu[i] = voi 

1034 

1035 # NOTE: This is mostly done with sets/lists because we aren't sure 

1036 # how well the numpy arrays will scale to thousands of 

1037 # dimensions. 

1038 vn_pool = set() 

1039 vn_pool.update(vo.nn) 

1040 vn_pool.update(vs.nn) 

1041 cvn_pool = copy.copy(vn_pool) 

1042 for vn in cvn_pool: 

1043 for i, xi in enumerate(vn.x): 

1044 if bl[i] <= xi <= bu[i]: 

1045 pass 

1046 else: 

1047 try: 

1048 vn_pool.remove(vn) 

1049 except KeyError: 

1050 pass # NOTE: Not all neigbouds are in initial pool 

1051 return vn_pool 

1052 

1053 def vf_to_vv(self, vertices, simplices): 

1054 """ 

1055 Convert a vertex-face mesh to a vertex-vertex mesh used by this class 

1056 

1057 Parameters 

1058 ---------- 

1059 vertices : list 

1060 Vertices 

1061 simplices : list 

1062 Simplices 

1063 """ 

1064 if self.dim > 1: 

1065 for s in simplices: 

1066 edges = itertools.combinations(s, self.dim) 

1067 for e in edges: 

1068 self.V[tuple(vertices[e[0]])].connect( 

1069 self.V[tuple(vertices[e[1]])]) 

1070 else: 

1071 for e in simplices: 

1072 self.V[tuple(vertices[e[0]])].connect( 

1073 self.V[tuple(vertices[e[1]])]) 

1074 return 

1075 

1076 def connect_vertex_non_symm(self, v_x, near=None): 

1077 """ 

1078 Adds a vertex at coords v_x to the complex that is not symmetric to the 

1079 initial triangulation and sub-triangulation. 

1080 

1081 If near is specified (for example; a star domain or collections of 

1082 cells known to contain v) then only those simplices containd in near 

1083 will be searched, this greatly speeds up the process. 

1084 

1085 If near is not specified this method will search the entire simplicial 

1086 complex structure. 

1087 

1088 Parameters 

1089 ---------- 

1090 v_x : tuple 

1091 Coordinates of non-symmetric vertex 

1092 near : set or list 

1093 List of vertices, these are points near v to check for 

1094 """ 

1095 if near is None: 

1096 star = self.V 

1097 else: 

1098 star = near 

1099 # Create the vertex origin 

1100 if tuple(v_x) in self.V.cache: 

1101 if self.V[v_x] in self.V_non_symm: 

1102 pass 

1103 else: 

1104 return 

1105 

1106 self.V[v_x] 

1107 found_nn = False 

1108 S_rows = [] 

1109 for v in star: 

1110 S_rows.append(v.x) 

1111 

1112 S_rows = numpy.array(S_rows) 

1113 A = numpy.array(S_rows) - numpy.array(v_x) 

1114 # Iterate through all the possible simplices of S_rows 

1115 for s_i in itertools.combinations(range(S_rows.shape[0]), 

1116 r=self.dim + 1): 

1117 # Check if connected, else s_i is not a simplex 

1118 valid_simplex = True 

1119 for i in itertools.combinations(s_i, r=2): 

1120 # Every combination of vertices must be connected, we check of 

1121 # the current iteration of all combinations of s_i are 

1122 # connected we break the loop if it is not. 

1123 if ((self.V[tuple(S_rows[i[1]])] not in 

1124 self.V[tuple(S_rows[i[0]])].nn) 

1125 and (self.V[tuple(S_rows[i[0]])] not in 

1126 self.V[tuple(S_rows[i[1]])].nn)): 

1127 valid_simplex = False 

1128 break 

1129 

1130 S = S_rows[tuple([s_i])] 

1131 if valid_simplex: 

1132 if self.deg_simplex(S, proj=None): 

1133 valid_simplex = False 

1134 

1135 # If s_i is a valid simplex we can test if v_x is inside si 

1136 if valid_simplex: 

1137 # Find the A_j0 value from the precalculated values 

1138 A_j0 = A[tuple([s_i])] 

1139 if self.in_simplex(S, v_x, A_j0): 

1140 found_nn = True 

1141 # breaks the main for loop, s_i is the target simplex: 

1142 break 

1143 

1144 # Connect the simplex to point 

1145 if found_nn: 

1146 for i in s_i: 

1147 self.V[v_x].connect(self.V[tuple(S_rows[i])]) 

1148 # Attached the simplex to storage for all non-symmetric vertices 

1149 self.V_non_symm.append(self.V[v_x]) 

1150 # this bool value indicates a successful connection if True: 

1151 return found_nn 

1152 

1153 def in_simplex(self, S, v_x, A_j0=None): 

1154 """Check if a vector v_x is in simplex `S`. 

1155 

1156 Parameters 

1157 ---------- 

1158 S : array_like 

1159 Array containing simplex entries of vertices as rows 

1160 v_x : 

1161 A candidate vertex 

1162 A_j0 : array, optional, 

1163 Allows for A_j0 to be pre-calculated 

1164 

1165 Returns 

1166 ------- 

1167 res : boolean 

1168 True if `v_x` is in `S` 

1169 """ 

1170 A_11 = numpy.delete(S, 0, 0) - S[0] 

1171 

1172 sign_det_A_11 = numpy.sign(numpy.linalg.det(A_11)) 

1173 if sign_det_A_11 == 0: 

1174 # NOTE: We keep the variable A_11, but we loop through A_jj 

1175 # ind= 

1176 # while sign_det_A_11 == 0: 

1177 # A_11 = numpy.delete(S, ind, 0) - S[ind] 

1178 # sign_det_A_11 = numpy.sign(numpy.linalg.det(A_11)) 

1179 

1180 sign_det_A_11 = -1 # TODO: Choose another det of j instead? 

1181 # TODO: Unlikely to work in many cases 

1182 

1183 if A_j0 is None: 

1184 A_j0 = S - v_x 

1185 

1186 for d in range(self.dim + 1): 

1187 det_A_jj = (-1)**d * sign_det_A_11 

1188 # TODO: Note that scipy might be faster to add as an optional 

1189 # dependency 

1190 sign_det_A_j0 = numpy.sign(numpy.linalg.det(numpy.delete(A_j0, d, 

1191 0))) 

1192 # TODO: Note if sign_det_A_j0 == then the point is coplanar to the 

1193 # current simplex facet, so perhaps return True and attach? 

1194 if det_A_jj == sign_det_A_j0: 

1195 continue 

1196 else: 

1197 return False 

1198 

1199 return True 

1200 

1201 def deg_simplex(self, S, proj=None): 

1202 """Test a simplex S for degeneracy (linear dependence in R^dim). 

1203 

1204 Parameters 

1205 ---------- 

1206 S : np.array 

1207 Simplex with rows as vertex vectors 

1208 proj : array, optional, 

1209 If the projection S[1:] - S[0] is already 

1210 computed it can be added as an optional argument. 

1211 """ 

1212 # Strategy: we test all combination of faces, if any of the 

1213 # determinants are zero then the vectors lie on the same face and is 

1214 # therefore linearly dependent in the space of R^dim 

1215 if proj is None: 

1216 proj = S[1:] - S[0] 

1217 

1218 # TODO: Is checking the projection of one vertex against faces of other 

1219 # vertices sufficient? Or do we need to check more vertices in 

1220 # dimensions higher than 2? 

1221 # TODO: Literature seems to suggest using proj.T, but why is this 

1222 # needed? 

1223 if numpy.linalg.det(proj) == 0.0: # TODO: Repalace with tolerance? 

1224 return True # Simplex is degenerate 

1225 else: 

1226 return False # Simplex is not degenerate