triangulation.py 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790
  1. import numpy as np
  2. import copy
  3. try:
  4. from functools import lru_cache # For Python 3 only
  5. except ImportError: # Python 2:
  6. import time
  7. import functools
  8. import collections
  9. # Note to avoid using external packages such as functools32 we use this code
  10. # only using the standard library
  11. def lru_cache(maxsize=255, timeout=None):
  12. """
  13. Thanks to ilialuk @ https://stackoverflow.com/users/2121105/ilialuk for
  14. this code snippet. Modifications by S. Endres
  15. """
  16. class LruCacheClass(object):
  17. def __init__(self, input_func, max_size, timeout):
  18. self._input_func = input_func
  19. self._max_size = max_size
  20. self._timeout = timeout
  21. # This will store the cache for this function,
  22. # format - {caller1 : [OrderedDict1, last_refresh_time1],
  23. # caller2 : [OrderedDict2, last_refresh_time2]}.
  24. # In case of an instance method - the caller is the instance,
  25. # in case called from a regular function - the caller is None.
  26. self._caches_dict = {}
  27. def cache_clear(self, caller=None):
  28. # Remove the cache for the caller, only if exists:
  29. if caller in self._caches_dict:
  30. del self._caches_dict[caller]
  31. self._caches_dict[caller] = [collections.OrderedDict(),
  32. time.time()]
  33. def __get__(self, obj, objtype):
  34. """ Called for instance methods """
  35. return_func = functools.partial(self._cache_wrapper, obj)
  36. return_func.cache_clear = functools.partial(self.cache_clear,
  37. obj)
  38. # Return the wrapped function and wraps it to maintain the
  39. # docstring and the name of the original function:
  40. return functools.wraps(self._input_func)(return_func)
  41. def __call__(self, *args, **kwargs):
  42. """ Called for regular functions """
  43. return self._cache_wrapper(None, *args, **kwargs)
  44. # Set the cache_clear function in the __call__ operator:
  45. __call__.cache_clear = cache_clear
  46. def _cache_wrapper(self, caller, *args, **kwargs):
  47. # Create a unique key including the types (in order to
  48. # differentiate between 1 and '1'):
  49. kwargs_key = "".join(map(
  50. lambda x: str(x) + str(type(kwargs[x])) + str(kwargs[x]),
  51. sorted(kwargs)))
  52. key = "".join(
  53. map(lambda x: str(type(x)) + str(x), args)) + kwargs_key
  54. # Check if caller exists, if not create one:
  55. if caller not in self._caches_dict:
  56. self._caches_dict[caller] = [collections.OrderedDict(),
  57. time.time()]
  58. else:
  59. # Validate in case the refresh time has passed:
  60. if self._timeout is not None:
  61. if (time.time() - self._caches_dict[caller][1]
  62. > self._timeout):
  63. self.cache_clear(caller)
  64. # Check if the key exists, if so - return it:
  65. cur_caller_cache_dict = self._caches_dict[caller][0]
  66. if key in cur_caller_cache_dict:
  67. return cur_caller_cache_dict[key]
  68. # Validate we didn't exceed the max_size:
  69. if len(cur_caller_cache_dict) >= self._max_size:
  70. # Delete the first item in the dict:
  71. try:
  72. cur_caller_cache_dict.popitem(False)
  73. except KeyError:
  74. pass
  75. # Call the function and store the data in the cache (call it
  76. # with the caller in case it's an instance function)
  77. if caller is not None:
  78. args = (caller,) + args
  79. cur_caller_cache_dict[key] = self._input_func(*args, **kwargs)
  80. return cur_caller_cache_dict[key]
  81. # Return the decorator wrapping the class (also wraps the instance to
  82. # maintain the docstring and the name of the original function):
  83. return (lambda input_func: functools.wraps(input_func)(
  84. LruCacheClass(input_func, maxsize, timeout)))
  85. class Complex:
  86. def __init__(self, dim, func, func_args=(), symmetry=False, bounds=None,
  87. g_cons=None, g_args=()):
  88. self.dim = dim
  89. self.bounds = bounds
  90. self.symmetry = symmetry # TODO: Define the functions to be used
  91. # here in init to avoid if checks
  92. self.gen = 0
  93. self.perm_cycle = 0
  94. # Every cell is stored in a list of its generation,
  95. # ex. the initial cell is stored in self.H[0]
  96. # 1st get new cells are stored in self.H[1] etc.
  97. # When a cell is subgenerated it is removed from this list
  98. self.H = [] # Storage structure of cells
  99. # Cache of all vertices
  100. self.V = VertexCache(func, func_args, bounds, g_cons, g_args)
  101. # Generate n-cube here:
  102. self.n_cube(dim, symmetry=symmetry)
  103. # TODO: Assign functions to a the complex instead
  104. if symmetry:
  105. self.generation_cycle = 1
  106. # self.centroid = self.C0()[-1].x
  107. # self.C0.centroid = self.centroid
  108. else:
  109. self.add_centroid()
  110. self.H.append([])
  111. self.H[0].append(self.C0)
  112. self.hgr = self.C0.homology_group_rank()
  113. self.hgrd = 0 # Complex group rank differential
  114. # self.hgr = self.C0.hg_n
  115. # Build initial graph
  116. self.graph_map()
  117. self.performance = []
  118. self.performance.append(0)
  119. self.performance.append(0)
  120. def __call__(self):
  121. return self.H
  122. def n_cube(self, dim, symmetry=False, printout=False):
  123. """
  124. Generate the simplicial triangulation of the n dimensional hypercube
  125. containing 2**n vertices
  126. """
  127. origin = list(np.zeros(dim, dtype=int))
  128. self.origin = origin
  129. supremum = list(np.ones(dim, dtype=int))
  130. self.supremum = supremum
  131. # tuple versions for indexing
  132. origintuple = tuple(origin)
  133. supremumtuple = tuple(supremum)
  134. x_parents = [origintuple]
  135. if symmetry:
  136. self.C0 = Simplex(0, 0, 0, self.dim) # Initial cell object
  137. self.C0.add_vertex(self.V[origintuple])
  138. i_s = 0
  139. self.perm_symmetry(i_s, x_parents, origin)
  140. self.C0.add_vertex(self.V[supremumtuple])
  141. else:
  142. self.C0 = Cell(0, 0, origin, supremum) # Initial cell object
  143. self.C0.add_vertex(self.V[origintuple])
  144. self.C0.add_vertex(self.V[supremumtuple])
  145. i_parents = []
  146. self.perm(i_parents, x_parents, origin)
  147. if printout:
  148. print("Initial hyper cube:")
  149. for v in self.C0():
  150. v.print_out()
  151. def perm(self, i_parents, x_parents, xi):
  152. # TODO: Cut out of for if outside linear constraint cutting planes
  153. xi_t = tuple(xi)
  154. # Construct required iterator
  155. iter_range = [x for x in range(self.dim) if x not in i_parents]
  156. for i in iter_range:
  157. i2_parents = copy.copy(i_parents)
  158. i2_parents.append(i)
  159. xi2 = copy.copy(xi)
  160. xi2[i] = 1
  161. # Make new vertex list a hashable tuple
  162. xi2_t = tuple(xi2)
  163. # Append to cell
  164. self.C0.add_vertex(self.V[xi2_t])
  165. # Connect neighbours and vice versa
  166. # Parent point
  167. self.V[xi2_t].connect(self.V[xi_t])
  168. # Connect all family of simplices in parent containers
  169. for x_ip in x_parents:
  170. self.V[xi2_t].connect(self.V[x_ip])
  171. x_parents2 = copy.copy(x_parents)
  172. x_parents2.append(xi_t)
  173. # Permutate
  174. self.perm(i2_parents, x_parents2, xi2)
  175. def perm_symmetry(self, i_s, x_parents, xi):
  176. # TODO: Cut out of for if outside linear constraint cutting planes
  177. xi_t = tuple(xi)
  178. xi2 = copy.copy(xi)
  179. xi2[i_s] = 1
  180. # Make new vertex list a hashable tuple
  181. xi2_t = tuple(xi2)
  182. # Append to cell
  183. self.C0.add_vertex(self.V[xi2_t])
  184. # Connect neighbours and vice versa
  185. # Parent point
  186. self.V[xi2_t].connect(self.V[xi_t])
  187. # Connect all family of simplices in parent containers
  188. for x_ip in x_parents:
  189. self.V[xi2_t].connect(self.V[x_ip])
  190. x_parents2 = copy.copy(x_parents)
  191. x_parents2.append(xi_t)
  192. i_s += 1
  193. if i_s == self.dim:
  194. return
  195. # Permutate
  196. self.perm_symmetry(i_s, x_parents2, xi2)
  197. def add_centroid(self):
  198. """Split the central edge between the origin and supremum of
  199. a cell and add the new vertex to the complex"""
  200. self.centroid = list(
  201. (np.array(self.origin) + np.array(self.supremum)) / 2.0)
  202. self.C0.add_vertex(self.V[tuple(self.centroid)])
  203. self.C0.centroid = self.centroid
  204. # Disconnect origin and supremum
  205. self.V[tuple(self.origin)].disconnect(self.V[tuple(self.supremum)])
  206. # Connect centroid to all other vertices
  207. for v in self.C0():
  208. self.V[tuple(self.centroid)].connect(self.V[tuple(v.x)])
  209. self.centroid_added = True
  210. return
  211. # Construct incidence array:
  212. def incidence(self):
  213. if self.centroid_added:
  214. self.structure = np.zeros([2 ** self.dim + 1, 2 ** self.dim + 1],
  215. dtype=int)
  216. else:
  217. self.structure = np.zeros([2 ** self.dim, 2 ** self.dim],
  218. dtype=int)
  219. for v in self.HC.C0():
  220. for v2 in v.nn:
  221. self.structure[v.index, v2.index] = 1
  222. return
  223. # A more sparse incidence generator:
  224. def graph_map(self):
  225. """ Make a list of size 2**n + 1 where an entry is a vertex
  226. incidence, each list element contains a list of indexes
  227. corresponding to that entries neighbours"""
  228. self.graph = [[v2.index for v2 in v.nn] for v in self.C0()]
  229. # Graph structure method:
  230. # 0. Capture the indices of the initial cell.
  231. # 1. Generate new origin and supremum scalars based on current generation
  232. # 2. Generate a new set of vertices corresponding to a new
  233. # "origin" and "supremum"
  234. # 3. Connected based on the indices of the previous graph structure
  235. # 4. Disconnect the edges in the original cell
  236. def sub_generate_cell(self, C_i, gen):
  237. """Subgenerate a cell `C_i` of generation `gen` and
  238. homology group rank `hgr`."""
  239. origin_new = tuple(C_i.centroid)
  240. centroid_index = len(C_i()) - 1
  241. # If not gen append
  242. try:
  243. self.H[gen]
  244. except IndexError:
  245. self.H.append([])
  246. # Generate subcubes using every extreme vertex in C_i as a supremum
  247. # and the centroid of C_i as the origin
  248. H_new = [] # list storing all the new cubes split from C_i
  249. for i, v in enumerate(C_i()[:-1]):
  250. supremum = tuple(v.x)
  251. H_new.append(
  252. self.construct_hypercube(origin_new, supremum, gen, C_i.hg_n))
  253. for i, connections in enumerate(self.graph):
  254. # Present vertex V_new[i]; connect to all connections:
  255. if i == centroid_index: # Break out of centroid
  256. break
  257. for j in connections:
  258. C_i()[i].disconnect(C_i()[j])
  259. # Destroy the old cell
  260. if C_i is not self.C0: # Garbage collector does this anyway; not needed
  261. del C_i
  262. # TODO: Recalculate all the homology group ranks of each cell
  263. return H_new
  264. def split_generation(self):
  265. """
  266. Run sub_generate_cell for every cell in the current complex self.gen
  267. """
  268. no_splits = False # USED IN SHGO
  269. try:
  270. for c in self.H[self.gen]:
  271. if self.symmetry:
  272. # self.sub_generate_cell_symmetry(c, self.gen + 1)
  273. self.split_simplex_symmetry(c, self.gen + 1)
  274. else:
  275. self.sub_generate_cell(c, self.gen + 1)
  276. except IndexError:
  277. no_splits = True # USED IN SHGO
  278. self.gen += 1
  279. return no_splits # USED IN SHGO
  280. # @lru_cache(maxsize=None)
  281. def construct_hypercube(self, origin, supremum, gen, hgr,
  282. printout=False):
  283. """
  284. Build a hypercube with triangulations symmetric to C0.
  285. Parameters
  286. ----------
  287. origin : vec
  288. supremum : vec (tuple)
  289. gen : generation
  290. hgr : parent homology group rank
  291. """
  292. # Initiate new cell
  293. C_new = Cell(gen, hgr, origin, supremum)
  294. C_new.centroid = tuple(
  295. (np.array(origin) + np.array(supremum)) / 2.0)
  296. # Build new indexed vertex list
  297. V_new = []
  298. # Cached calculation
  299. for i, v in enumerate(self.C0()[:-1]):
  300. t1 = self.generate_sub_cell_t1(origin, v.x)
  301. t2 = self.generate_sub_cell_t2(supremum, v.x)
  302. vec = t1 + t2
  303. vec = tuple(vec)
  304. C_new.add_vertex(self.V[vec])
  305. V_new.append(vec)
  306. # Add new centroid
  307. C_new.add_vertex(self.V[C_new.centroid])
  308. V_new.append(C_new.centroid)
  309. # Connect new vertices #TODO: Thread into other loop; no need for V_new
  310. for i, connections in enumerate(self.graph):
  311. # Present vertex V_new[i]; connect to all connections:
  312. for j in connections:
  313. self.V[V_new[i]].connect(self.V[V_new[j]])
  314. if printout:
  315. print("A sub hyper cube with:")
  316. print("origin: {}".format(origin))
  317. print("supremum: {}".format(supremum))
  318. for v in C_new():
  319. v.print_out()
  320. # Append the new cell to the to complex
  321. self.H[gen].append(C_new)
  322. return C_new
  323. def split_simplex_symmetry(self, S, gen):
  324. """
  325. Split a hypersimplex S into two sub simplices by building a hyperplane
  326. which connects to a new vertex on an edge (the longest edge in
  327. dim = {2, 3}) and every other vertex in the simplex that is not
  328. connected to the edge being split.
  329. This function utilizes the knowledge that the problem is specified
  330. with symmetric constraints
  331. The longest edge is tracked by an ordering of the
  332. vertices in every simplices, the edge between first and second
  333. vertex is the longest edge to be split in the next iteration.
  334. """
  335. # If not gen append
  336. try:
  337. self.H[gen]
  338. except IndexError:
  339. self.H.append([])
  340. # Find new vertex.
  341. # V_new_x = tuple((np.array(C()[0].x) + np.array(C()[1].x)) / 2.0)
  342. s = S()
  343. firstx = s[0].x
  344. lastx = s[-1].x
  345. V_new = self.V[tuple((np.array(firstx) + np.array(lastx)) / 2.0)]
  346. # Disconnect old longest edge
  347. self.V[firstx].disconnect(self.V[lastx])
  348. # Connect new vertices to all other vertices
  349. for v in s[:]:
  350. v.connect(self.V[V_new.x])
  351. # New "lower" simplex
  352. S_new_l = Simplex(gen, S.hg_n, self.generation_cycle,
  353. self.dim)
  354. S_new_l.add_vertex(s[0])
  355. S_new_l.add_vertex(V_new) # Add new vertex
  356. for v in s[1:-1]: # Add all other vertices
  357. S_new_l.add_vertex(v)
  358. # New "upper" simplex
  359. S_new_u = Simplex(gen, S.hg_n, S.generation_cycle, self.dim)
  360. # First vertex on new long edge
  361. S_new_u.add_vertex(s[S_new_u.generation_cycle + 1])
  362. for v in s[1:-1]: # Remaining vertices
  363. S_new_u.add_vertex(v)
  364. for k, v in enumerate(s[1:-1]): # iterate through inner vertices
  365. if k == S.generation_cycle:
  366. S_new_u.add_vertex(V_new)
  367. else:
  368. S_new_u.add_vertex(v)
  369. S_new_u.add_vertex(s[-1]) # Second vertex on new long edge
  370. self.H[gen].append(S_new_l)
  371. self.H[gen].append(S_new_u)
  372. return
  373. @lru_cache(maxsize=None)
  374. def generate_sub_cell_2(self, origin, supremum, v_x_t): # No hits
  375. """
  376. Use the origin and supremum vectors to find a new cell in that
  377. subspace direction
  378. NOTE: NOT CURRENTLY IN USE!
  379. Parameters
  380. ----------
  381. origin : tuple vector (hashable)
  382. supremum : tuple vector (hashable)
  383. Returns
  384. -------
  385. """
  386. t1 = self.generate_sub_cell_t1(origin, v_x_t)
  387. t2 = self.generate_sub_cell_t2(supremum, v_x_t)
  388. vec = t1 + t2
  389. return tuple(vec)
  390. @lru_cache(maxsize=None)
  391. def generate_sub_cell_t1(self, origin, v_x):
  392. # TODO: Calc these arrays outside
  393. v_o = np.array(origin)
  394. return v_o - v_o * np.array(v_x)
  395. @lru_cache(maxsize=None)
  396. def generate_sub_cell_t2(self, supremum, v_x):
  397. v_s = np.array(supremum)
  398. return v_s * np.array(v_x)
  399. # Plots
  400. def plot_complex(self):
  401. """
  402. Here C is the LIST of simplexes S in the
  403. 2 or 3 dimensional complex
  404. To plot a single simplex S in a set C, use ex. [C[0]]
  405. """
  406. from matplotlib import pyplot
  407. if self.dim == 2:
  408. pyplot.figure()
  409. for C in self.H:
  410. for c in C:
  411. for v in c():
  412. if self.bounds is None:
  413. x_a = np.array(v.x, dtype=float)
  414. else:
  415. x_a = np.array(v.x, dtype=float)
  416. for i in range(len(self.bounds)):
  417. x_a[i] = (x_a[i] * (self.bounds[i][1]
  418. - self.bounds[i][0])
  419. + self.bounds[i][0])
  420. # logging.info('v.x_a = {}'.format(x_a))
  421. pyplot.plot([x_a[0]], [x_a[1]], 'o')
  422. xlines = []
  423. ylines = []
  424. for vn in v.nn:
  425. if self.bounds is None:
  426. xn_a = np.array(vn.x, dtype=float)
  427. else:
  428. xn_a = np.array(vn.x, dtype=float)
  429. for i in range(len(self.bounds)):
  430. xn_a[i] = (xn_a[i] * (self.bounds[i][1]
  431. - self.bounds[i][0])
  432. + self.bounds[i][0])
  433. # logging.info('vn.x = {}'.format(vn.x))
  434. xlines.append(xn_a[0])
  435. ylines.append(xn_a[1])
  436. xlines.append(x_a[0])
  437. ylines.append(x_a[1])
  438. pyplot.plot(xlines, ylines)
  439. if self.bounds is None:
  440. pyplot.ylim([-1e-2, 1 + 1e-2])
  441. pyplot.xlim([-1e-2, 1 + 1e-2])
  442. else:
  443. pyplot.ylim(
  444. [self.bounds[1][0] - 1e-2, self.bounds[1][1] + 1e-2])
  445. pyplot.xlim(
  446. [self.bounds[0][0] - 1e-2, self.bounds[0][1] + 1e-2])
  447. pyplot.show()
  448. elif self.dim == 3:
  449. fig = pyplot.figure()
  450. ax = fig.add_subplot(111, projection='3d')
  451. for C in self.H:
  452. for c in C:
  453. for v in c():
  454. x = []
  455. y = []
  456. z = []
  457. # logging.info('v.x = {}'.format(v.x))
  458. x.append(v.x[0])
  459. y.append(v.x[1])
  460. z.append(v.x[2])
  461. for vn in v.nn:
  462. x.append(vn.x[0])
  463. y.append(vn.x[1])
  464. z.append(vn.x[2])
  465. x.append(v.x[0])
  466. y.append(v.x[1])
  467. z.append(v.x[2])
  468. # logging.info('vn.x = {}'.format(vn.x))
  469. ax.plot(x, y, z, label='simplex')
  470. pyplot.show()
  471. else:
  472. print("dimension higher than 3 or wrong complex format")
  473. return
  474. class VertexGroup(object):
  475. def __init__(self, p_gen, p_hgr):
  476. self.p_gen = p_gen # parent generation
  477. self.p_hgr = p_hgr # parent homology group rank
  478. self.hg_n = None
  479. self.hg_d = None
  480. # Maybe add parent homology group rank total history
  481. # This is the sum off all previously split cells
  482. # cumulatively throughout its entire history
  483. self.C = []
  484. def __call__(self):
  485. return self.C
  486. def add_vertex(self, V):
  487. if V not in self.C:
  488. self.C.append(V)
  489. def homology_group_rank(self):
  490. """
  491. Returns the homology group order of the current cell
  492. """
  493. if self.hg_n is None:
  494. self.hg_n = sum(1 for v in self.C if v.minimiser())
  495. return self.hg_n
  496. def homology_group_differential(self):
  497. """
  498. Returns the difference between the current homology group of the
  499. cell and it's parent group
  500. """
  501. if self.hg_d is None:
  502. self.hgd = self.hg_n - self.p_hgr
  503. return self.hgd
  504. def polytopial_sperner_lemma(self):
  505. """
  506. Returns the number of stationary points theoretically contained in the
  507. cell based information currently known about the cell
  508. """
  509. pass
  510. def print_out(self):
  511. """
  512. Print the current cell to console
  513. """
  514. for v in self():
  515. v.print_out()
  516. class Cell(VertexGroup):
  517. """
  518. Contains a cell that is symmetric to the initial hypercube triangulation
  519. """
  520. def __init__(self, p_gen, p_hgr, origin, supremum):
  521. super(Cell, self).__init__(p_gen, p_hgr)
  522. self.origin = origin
  523. self.supremum = supremum
  524. self.centroid = None # (Not always used)
  525. # TODO: self.bounds
  526. class Simplex(VertexGroup):
  527. """
  528. Contains a simplex that is symmetric to the initial symmetry constrained
  529. hypersimplex triangulation
  530. """
  531. def __init__(self, p_gen, p_hgr, generation_cycle, dim):
  532. super(Simplex, self).__init__(p_gen, p_hgr)
  533. self.generation_cycle = (generation_cycle + 1) % (dim - 1)
  534. class Vertex:
  535. def __init__(self, x, bounds=None, func=None, func_args=(), g_cons=None,
  536. g_cons_args=(), nn=None, index=None):
  537. self.x = x
  538. self.order = sum(x)
  539. x_a = np.array(x, dtype=float)
  540. if bounds is not None:
  541. for i, (lb, ub) in enumerate(bounds):
  542. x_a[i] = x_a[i] * (ub - lb) + lb
  543. # TODO: Make saving the array structure optional
  544. self.x_a = x_a
  545. # Note Vertex is only initiated once for all x so only
  546. # evaluated once
  547. if func is not None:
  548. self.feasible = True
  549. if g_cons is not None:
  550. for g, args in zip(g_cons, g_cons_args):
  551. if g(self.x_a, *args) < 0.0:
  552. self.f = np.inf
  553. self.feasible = False
  554. break
  555. if self.feasible:
  556. self.f = func(x_a, *func_args)
  557. if nn is not None:
  558. self.nn = nn
  559. else:
  560. self.nn = set()
  561. self.fval = None
  562. self.check_min = True
  563. # Index:
  564. if index is not None:
  565. self.index = index
  566. def __hash__(self):
  567. return hash(self.x)
  568. def connect(self, v):
  569. if v is not self and v not in self.nn:
  570. self.nn.add(v)
  571. v.nn.add(self)
  572. if self.minimiser():
  573. v._min = False
  574. v.check_min = False
  575. # TEMPORARY
  576. self.check_min = True
  577. v.check_min = True
  578. def disconnect(self, v):
  579. if v in self.nn:
  580. self.nn.remove(v)
  581. v.nn.remove(self)
  582. self.check_min = True
  583. v.check_min = True
  584. def minimiser(self):
  585. """Check whether this vertex is strictly less than all its neighbours"""
  586. if self.check_min:
  587. self._min = all(self.f < v.f for v in self.nn)
  588. self.check_min = False
  589. return self._min
  590. def print_out(self):
  591. print("Vertex: {}".format(self.x))
  592. constr = 'Connections: '
  593. for vc in self.nn:
  594. constr += '{} '.format(vc.x)
  595. print(constr)
  596. print('Order = {}'.format(self.order))
  597. class VertexCache:
  598. def __init__(self, func, func_args=(), bounds=None, g_cons=None,
  599. g_cons_args=(), indexed=True):
  600. self.cache = {}
  601. self.func = func
  602. self.g_cons = g_cons
  603. self.g_cons_args = g_cons_args
  604. self.func_args = func_args
  605. self.bounds = bounds
  606. self.nfev = 0
  607. self.size = 0
  608. if indexed:
  609. self.index = -1
  610. def __getitem__(self, x, indexed=True):
  611. try:
  612. return self.cache[x]
  613. except KeyError:
  614. if indexed:
  615. self.index += 1
  616. xval = Vertex(x, bounds=self.bounds,
  617. func=self.func, func_args=self.func_args,
  618. g_cons=self.g_cons,
  619. g_cons_args=self.g_cons_args,
  620. index=self.index)
  621. else:
  622. xval = Vertex(x, bounds=self.bounds,
  623. func=self.func, func_args=self.func_args,
  624. g_cons=self.g_cons,
  625. g_cons_args=self.g_cons_args)
  626. # logging.info("New generated vertex at x = {}".format(x))
  627. # NOTE: Surprisingly high performance increase if logging is commented out
  628. self.cache[x] = xval
  629. # TODO: Check
  630. if self.func is not None:
  631. if self.g_cons is not None:
  632. if xval.feasible:
  633. self.nfev += 1
  634. self.size += 1
  635. else:
  636. self.size += 1
  637. else:
  638. self.nfev += 1
  639. self.size += 1
  640. return self.cache[x]