12
12
from scipy .spatial import Delaunay
13
13
from scipy .special import comb
14
14
from copy import copy
15
+ import cdd as cdd_float
15
16
16
17
from .material import cached_property
17
18
18
19
from ..utils .math import independent_row_indices
19
20
21
+
20
22
try :
21
- cdd = importlib .import_module ("cdd" )
22
- except ImportError as err :
23
- print (
24
- f"Warning: { err } . "
25
- "For full functionality of BurnMan, please install pycddlib."
26
- )
23
+ cdd_fraction = importlib .import_module ("cdd.gmp" )
24
+ cdd_gmp_loaded = True
25
+ except ImportError :
26
+ cdd_fraction = importlib .import_module ("cdd" )
27
+ cdd_gmp_loaded = False
27
28
28
29
29
30
class SimplexGrid (object ):
@@ -111,9 +112,7 @@ def n_points(self):
111
112
112
113
class MaterialPolytope (object ):
113
114
"""
114
- A class that can be instantiated to create pycddlib polytope objects.
115
- These objects can be interrogated to provide the vertices satisfying the
116
- input constraints.
115
+ A class for creating and manipulating polytope objects using pycddlib.
117
116
118
117
This class is available as :class:`burnman.polytope.MaterialPolytope`.
119
118
"""
@@ -122,7 +121,6 @@ def __init__(
122
121
self ,
123
122
equalities ,
124
123
inequalities ,
125
- number_type = "fraction" ,
126
124
return_fractions = False ,
127
125
independent_endmember_occupancies = None ,
128
126
):
@@ -132,31 +130,45 @@ def __init__(
132
130
133
131
:param equalities: A numpy array containing all the
134
132
equalities defining the polytope. Each row should evaluate to 0.
135
- :type equalities: numpy.array (2D)
133
+ :type equalities: numpy.array (2D) of floats or Fractions
136
134
:param inequalities: A numpy array containing all the inequalities
137
135
defining the polytope. Each row should evaluate to <= 0.
138
- :type inequalities: numpy.array (2D)
139
- :param number_type: Whether pycddlib should read the input arrays as
140
- fractions or floats. Valid options are 'fraction' or 'float'.
141
- :type number_type: str
142
- :param return_fractions: Choose whether the generated polytope object
143
- should return fractions or floats.
136
+ :type inequalities: numpy.array (2D) of the same type as equalities
137
+ :param return_fractions: Whether or not to return fractions.
144
138
:type return_fractions: bool
145
- :param independent_endmember_occupancies: If specified, this array provides
146
- the independent endmember set against which the dependent endmembers
147
- are defined.
139
+ :param independent_endmember_occupancies: If specified, this array
140
+ provides the independent endmember set against which the
141
+ dependent endmembers are defined.
148
142
:type independent_endmember_occupancies: numpy.array (2D) or None
149
143
"""
144
+ if equalities .dtype != inequalities .dtype :
145
+ raise Exception (
146
+ f"The equalities and inequalities arrays should have the same type ({ equalities .dtype } != { inequalities .dtype } )."
147
+ )
148
+
150
149
self .set_return_type (return_fractions )
151
150
self .equality_matrix = equalities [:, 1 :]
152
151
self .equality_vector = - equalities [:, 0 ]
153
152
154
- self .polytope_matrix = cdd .Matrix (
155
- equalities , linear = True , number_type = number_type
153
+ if equalities .dtype == Fraction and cdd_gmp_loaded is True :
154
+ self .number_type = Fraction
155
+ self .cdd = cdd_fraction
156
+ elif equalities .dtype == float or cdd_gmp_loaded is False :
157
+ self .number_type = float
158
+ self .cdd = cdd_float
159
+ else :
160
+ raise Exception (
161
+ "equalities should be arrays of either floats or Fractions."
162
+ )
163
+
164
+ self .polytope_matrix = self .cdd .matrix_from_array (equalities )
165
+ self .polytope_matrix .lin_set = set (range (len (equalities )))
166
+ self .polytope_matrix .rep_type = self .cdd .RepType .INEQUALITY
167
+
168
+ self .cdd .matrix_append_to (
169
+ self .polytope_matrix , self .cdd .matrix_from_array (inequalities )
156
170
)
157
- self .polytope_matrix .rep_type = cdd .RepType .INEQUALITY
158
- self .polytope_matrix .extend (inequalities , linear = False )
159
- self .polytope = cdd .Polyhedron (self .polytope_matrix )
171
+ self .polytope = self .cdd .polyhedron_from_matrix (self .polytope_matrix )
160
172
161
173
if independent_endmember_occupancies is not None :
162
174
self .independent_endmember_occupancies = independent_endmember_occupancies
@@ -182,14 +194,14 @@ def raw_vertices(self):
182
194
Returns a list of the vertices of the polytope without any
183
195
postprocessing. See also endmember_occupancies.
184
196
"""
185
- return self .polytope . get_generators ()[:]
197
+ return self .cdd . copy_generators ( self . polytope ). array
186
198
187
199
@cached_property
188
200
def limits (self ):
189
201
"""
190
202
Return the limits of the polytope (the set of bounding inequalities).
191
203
"""
192
- return np .array (self .polytope . get_inequalities () , dtype = float )
204
+ return np .array (self .cdd . copy_inequalities ( self . polytope ). array , dtype = float )
193
205
194
206
@cached_property
195
207
def n_endmembers (self ):
@@ -205,27 +217,23 @@ def endmember_occupancies(self):
205
217
Return the endmember occupancies
206
218
(a processed list of all of the vertex locations).
207
219
"""
208
- if self .return_fractions :
209
- if self .polytope .number_type == "fraction" :
210
- v = np .array (
211
- [[Fraction (value ) for value in v ] for v in self .raw_vertices ]
212
- )
213
- else :
214
- v = np .array (
215
- [
216
- [Rational (value ).limit_denominator (1000000 ) for value in v ]
217
- for v in self .raw_vertices
218
- ]
219
- )
220
+ if self .number_type == float and self .return_fractions :
221
+ v = np .array (
222
+ [
223
+ [Fraction (value ).limit_denominator (1000000 ) for value in v ]
224
+ for v in self .raw_vertices
225
+ ]
226
+ )
227
+ elif self .number_type == Fraction and not self .return_fractions :
228
+ v = np .array (self .raw_vertices ).astype (float )
220
229
else :
221
- v = np .array ([[ float ( value ) for value in v ] for v in self .raw_vertices ] )
230
+ v = np .array (self .raw_vertices )
222
231
223
232
if len (v .shape ) == 1 :
224
233
raise ValueError (
225
234
"The combined equality and positivity "
226
235
"constraints result in a null polytope."
227
236
)
228
-
229
237
return v [:, 1 :] / v [:, 0 , np .newaxis ]
230
238
231
239
@cached_property
@@ -281,37 +289,41 @@ def independent_endmember_polytope(self):
281
289
"""
282
290
arr = self .endmembers_as_independent_endmember_amounts
283
291
arr = np .hstack ((np .ones ((len (arr ), 1 )), arr [:, :- 1 ]))
284
- M = cdd .Matrix (arr , number_type = "fraction" )
285
- M .rep_type = cdd .RepType .GENERATOR
286
- return cdd .Polyhedron (M )
292
+ arr = [[Fraction (value ).limit_denominator (1000000 ) for value in v ] for v in arr ]
293
+ M = cdd_fraction .matrix_from_array (arr )
294
+ M .rep_type = cdd_fraction .RepType .GENERATOR
295
+ return cdd_fraction .polyhedron_from_matrix (M )
287
296
288
297
@cached_property
289
298
def independent_endmember_limits (self ):
290
299
"""
291
300
Gets the limits of the polytope as a function of the independent
292
301
endmembers.
293
302
"""
294
- return np . array (
295
- self . independent_endmember_polytope . get_inequalities (), dtype = float
296
- )
303
+ ind_poly = self . independent_endmember_polytope
304
+ inequalities = cdd_fraction . copy_inequalities ( ind_poly ). array
305
+ return np . array ( inequalities , dtype = float )
297
306
298
307
def subpolytope_from_independent_endmember_limits (self , limits ):
299
308
"""
300
309
Returns a smaller polytope by applying additional limits to the amounts
301
310
of the independent endmembers.
302
311
"""
303
- modified_limits = self .independent_endmember_polytope .get_inequalities ().copy ()
304
- modified_limits .extend (limits , linear = False )
305
- return cdd .Polyhedron (modified_limits )
312
+ ind_poly = self .independent_endmember_polytope
313
+ modified_limits = cdd_fraction .copy_inequalities (ind_poly )
314
+ cdd_fraction .matrix_append_to (
315
+ modified_limits , cdd_fraction .matrix_from_array (limits )
316
+ )
317
+ return cdd_fraction .polyhedron_from_matrix (modified_limits )
306
318
307
319
def subpolytope_from_site_occupancy_limits (self , limits ):
308
320
"""
309
321
Returns a smaller polytope by applying additional limits to the
310
322
individual site occupancies.
311
323
"""
312
- modified_limits = self .polytope_matrix . copy ( )
313
- modified_limits . extend ( limits , linear = False )
314
- return cdd .Polyhedron (modified_limits )
324
+ modified_limits = copy ( self .polytope_matrix )
325
+ self . cdd . matrix_append_to ( modified_limits , self . cdd . matrix_from_array ( limits ) )
326
+ return self . cdd .polyhedron_from_matrix (modified_limits )
315
327
316
328
def grid (
317
329
self ,
@@ -325,15 +337,16 @@ def grid(
325
337
326
338
:param points_per_edge: Number of points per edge of the polytope.
327
339
:type points_per_edge: int
328
- :param unique_sorted: The gridding is done by splitting the polytope into
329
- a set of simplices. This means that points will be duplicated along
330
- vertices, faces etc. If unique_sorted is True, this function
340
+ :param unique_sorted: The gridding is done by splitting the polytope
341
+ into a set of simplices. This means that points will be duplicated
342
+ along vertices, faces etc. If unique_sorted is True, this function
331
343
will sort and make the points unique. This is an expensive
332
344
operation for large polytopes, and may not always be necessary.
333
345
:type unique_sorted: bool
334
346
:param grid_type: Whether to grid the polytope in terms of
335
347
independent endmember proportions or site occupancies.
336
- Choices are 'independent endmember proportions' or 'site occupancies'
348
+ Choices are 'independent endmember proportions' or
349
+ 'site occupancies'
337
350
:type grid_type: str
338
351
:param limits: Additional inequalities restricting the
339
352
gridded area of the polytope.
@@ -361,11 +374,8 @@ def grid(
361
374
)
362
375
else :
363
376
if grid_type == "independent endmember proportions" :
364
- ppns = np .array (
365
- self .subpolytope_from_independent_endmember_limits (
366
- limits
367
- ).get_generators ()[:]
368
- )[:, 1 :]
377
+ plims = self .subpolytope_from_site_occupancy_limits (limits )
378
+ ppns = np .array (self .cdd .copy_generators (plims ).array )[:, 1 :]
369
379
last_ppn = np .array ([1.0 - sum (p ) for p in ppns ]).reshape (
370
380
(len (ppns ), 1 )
371
381
)
@@ -377,11 +387,8 @@ def grid(
377
387
)
378
388
379
389
elif grid_type == "site occupancies" :
380
- occ = np .array (
381
- self .subpolytope_from_site_occupancy_limits (
382
- limits
383
- ).get_generators ()[:]
384
- )[:, 1 :]
390
+ plims = self .subpolytope_from_site_occupancy_limits (limits )
391
+ occ = np .array (self .cdd .copy_generators (plims ).array )[:, 1 :]
385
392
f_occ = occ / (points_per_edge - 1 )
386
393
387
394
ind = self .independent_endmember_occupancies
0 commit comments