Skip to content

Commit c51f4d5

Browse files
committed
allow fractional representation
1 parent 003fca4 commit c51f4d5

File tree

3 files changed

+66
-48
lines changed

3 files changed

+66
-48
lines changed

burnman/classes/polytope.py

Lines changed: 56 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,23 @@
1212
from scipy.spatial import Delaunay
1313
from scipy.special import comb
1414
from copy import copy
15-
import cdd
15+
import cdd as cdd_float
1616

1717
from .material import cached_property
1818

1919
from ..utils.math import independent_row_indices
2020

21-
cdd_number_type = "float"
21+
cdd_gmp_loaded = False
22+
23+
try:
24+
cdd_fraction = importlib.import_module("cdd.gmp")
25+
cdd_gmp_loaded = True
26+
except ImportError as err:
27+
cdd_fraction = importlib.import_module("cdd")
28+
print(
29+
f"Warning: {err}. Only pycddlib-standalone is installed."
30+
"For precise fractional representations of polytopes, please install pycddlib."
31+
)
2232

2333

2434
class SimplexGrid(object):
@@ -117,7 +127,6 @@ def __init__(
117127
self,
118128
equalities,
119129
inequalities,
120-
number_type="fraction",
121130
return_fractions=False,
122131
independent_endmember_occupancies=None,
123132
):
@@ -133,25 +142,38 @@ def __init__(
133142
:type inequalities: numpy.array (2D)
134143
:param number_type: Whether pycddlib should read the input arrays as
135144
fractions or floats. Valid options are 'fraction' or 'float'.
136-
:type number_type: str
137-
:param return_fractions: Choose whether the generated polytope object
138-
should return fractions or floats.
139145
:type return_fractions: bool
140146
:param independent_endmember_occupancies: If specified, this array
141147
provides the independent endmember set against which the
142148
dependent endmembers are defined.
143149
:type independent_endmember_occupancies: numpy.array (2D) or None
144150
"""
151+
if equalities.dtype != inequalities.dtype:
152+
raise Exception(
153+
f"The dtype of the equalities ({equalities.dtype}) and inequalities ({inequalities.dtype}) arrays should be the same."
154+
)
155+
145156
self.set_return_type(return_fractions)
146157
self.equality_matrix = equalities[:, 1:]
147158
self.equality_vector = -equalities[:, 0]
148159

149-
self.polytope_matrix = cdd.matrix_from_array(equalities)
160+
if equalities.dtype == Fraction and cdd_gmp_loaded is True:
161+
self.number_type = Fraction
162+
self.cdd = cdd_fraction
163+
elif equalities.dtype == float or cdd_gmp_loaded is False:
164+
self.number_type = float
165+
self.cdd = cdd_float
166+
else:
167+
raise Exception("number_type should be either float or Fraction.")
168+
169+
self.polytope_matrix = self.cdd.matrix_from_array(equalities)
150170
self.polytope_matrix.lin_set = set(range(len(equalities)))
151-
self.polytope_matrix.rep_type = cdd.RepType.INEQUALITY
171+
self.polytope_matrix.rep_type = self.cdd.RepType.INEQUALITY
152172

153-
cdd.matrix_append_to(self.polytope_matrix, cdd.matrix_from_array(inequalities))
154-
self.polytope = cdd.polyhedron_from_matrix(self.polytope_matrix)
173+
self.cdd.matrix_append_to(
174+
self.polytope_matrix, self.cdd.matrix_from_array(inequalities)
175+
)
176+
self.polytope = self.cdd.polyhedron_from_matrix(self.polytope_matrix)
155177

156178
if independent_endmember_occupancies is not None:
157179
self.independent_endmember_occupancies = independent_endmember_occupancies
@@ -177,14 +199,14 @@ def raw_vertices(self):
177199
Returns a list of the vertices of the polytope without any
178200
postprocessing. See also endmember_occupancies.
179201
"""
180-
return cdd.copy_generators(self.polytope).array
202+
return self.cdd.copy_generators(self.polytope).array
181203

182204
@cached_property
183205
def limits(self):
184206
"""
185207
Return the limits of the polytope (the set of bounding inequalities).
186208
"""
187-
return np.array(cdd.copy_inequalities(self.polytope).array, dtype=float)
209+
return np.array(self.cdd.copy_inequalities(self.polytope).array, dtype=float)
188210

189211
@cached_property
190212
def n_endmembers(self):
@@ -200,20 +222,15 @@ def endmember_occupancies(self):
200222
Return the endmember occupancies
201223
(a processed list of all of the vertex locations).
202224
"""
203-
if self.return_fractions:
204-
if cdd_number_type == "fraction":
205-
v = np.array(
206-
[[Fraction(value) for value in v] for v in self.raw_vertices]
207-
)
208-
else:
209-
v = np.array(
210-
[
211-
[Rational(value).limit_denominator(1000000) for value in v]
212-
for v in self.raw_vertices
213-
]
214-
)
225+
if self.number_type == float and self.return_fractions:
226+
v = np.array(
227+
[
228+
[Rational(value).limit_denominator(1000000) for value in v]
229+
for v in self.raw_vertices
230+
]
231+
)
215232
else:
216-
v = np.array([[float(value) for value in v] for v in self.raw_vertices])
233+
v = np.array(self.raw_vertices)
217234

218235
if len(v.shape) == 1:
219236
raise ValueError(
@@ -275,9 +292,10 @@ def independent_endmember_polytope(self):
275292
"""
276293
arr = self.endmembers_as_independent_endmember_amounts
277294
arr = np.hstack((np.ones((len(arr), 1)), arr[:, :-1]))
278-
M = cdd.matrix_from_array(arr)
279-
M.rep_type = cdd.RepType.GENERATOR
280-
return cdd.polyhedron_from_matrix(M)
295+
arr = [[Fraction(value).limit_denominator(1000000) for value in v] for v in arr]
296+
M = cdd_fraction.matrix_from_array(arr)
297+
M.rep_type = cdd_fraction.RepType.GENERATOR
298+
return cdd_fraction.polyhedron_from_matrix(M)
281299

282300
@cached_property
283301
def independent_endmember_limits(self):
@@ -286,7 +304,7 @@ def independent_endmember_limits(self):
286304
endmembers.
287305
"""
288306
ind_poly = self.independent_endmember_polytope
289-
inequalities = cdd.copy_inequalities(ind_poly).array
307+
inequalities = cdd_fraction.copy_inequalities(ind_poly).array
290308
return np.array(inequalities, dtype=float)
291309

292310
def subpolytope_from_independent_endmember_limits(self, limits):
@@ -295,18 +313,20 @@ def subpolytope_from_independent_endmember_limits(self, limits):
295313
of the independent endmembers.
296314
"""
297315
ind_poly = self.independent_endmember_polytope
298-
modified_limits = cdd.copy_inequalities(ind_poly)
299-
cdd.matrix_append_to(modified_limits, cdd.matrix_from_array(limits))
300-
return cdd.polyhedron_from_matrix(modified_limits)
316+
modified_limits = cdd_fraction.copy_inequalities(ind_poly)
317+
cdd_fraction.matrix_append_to(
318+
modified_limits, cdd_fraction.matrix_from_array(limits)
319+
)
320+
return cdd_fraction.polyhedron_from_matrix(modified_limits)
301321

302322
def subpolytope_from_site_occupancy_limits(self, limits):
303323
"""
304324
Returns a smaller polytope by applying additional limits to the
305325
individual site occupancies.
306326
"""
307327
modified_limits = copy(self.polytope_matrix)
308-
cdd.matrix_append_to(modified_limits, cdd.matrix_from_array(limits))
309-
return cdd.polyhedron_from_matrix(modified_limits)
328+
self.cdd.matrix_append_to(modified_limits, self.cdd.matrix_from_array(limits))
329+
return self.cdd.polyhedron_from_matrix(modified_limits)
310330

311331
def grid(
312332
self,
@@ -358,7 +378,7 @@ def grid(
358378
else:
359379
if grid_type == "independent endmember proportions":
360380
plims = self.subpolytope_from_site_occupancy_limits(limits)
361-
ppns = np.array(cdd.copy_generators(plims).array)[:, 1:]
381+
ppns = np.array(self.cdd.copy_generators(plims).array)[:, 1:]
362382
last_ppn = np.array([1.0 - sum(p) for p in ppns]).reshape(
363383
(len(ppns), 1)
364384
)
@@ -371,7 +391,7 @@ def grid(
371391

372392
elif grid_type == "site occupancies":
373393
plims = self.subpolytope_from_site_occupancy_limits(limits)
374-
occ = np.array(cdd.copy_generators(plims).array)[:, 1:]
394+
occ = np.array(self.cdd.copy_generators(plims).array)[:, 1:]
375395
f_occ = occ / (points_per_edge - 1)
376396

377397
ind = self.independent_endmember_occupancies

burnman/tools/chemistry.py

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -276,10 +276,12 @@ def reactions_from_stoichiometric_matrix(stoichiometric_matrix):
276276
"""
277277
n_components = stoichiometric_matrix.shape[1]
278278

279-
equalities = np.concatenate(([np.zeros(n_components)], stoichiometric_matrix)).T
279+
equalities = np.concatenate(
280+
([np.zeros(n_components)], np.array(stoichiometric_matrix).astype(float))
281+
).T
280282

281283
polys = [
282-
MaterialPolytope(equalities, np.diag(v))
284+
MaterialPolytope(equalities, np.diag(v).astype(float))
283285
for v in itertools.product(*[[-1, 1]] * len(equalities[0]))
284286
]
285287
reactions = []
@@ -288,19 +290,17 @@ def reactions_from_stoichiometric_matrix(stoichiometric_matrix):
288290
[
289291
[Rational(value).limit_denominator(1000000) for value in v]
290292
for v in p.raw_vertices
291-
]
293+
],
294+
dtype=float,
292295
)
293296

294297
if v is not []:
295298
reactions.extend(v)
296299

297-
# There are many duplicate reactions produced by the above procedure
298-
# Here we pick out the unique values
299-
reactions = np.array(reactions)
300-
_, unique_indices = np.unique(
301-
np.array(reactions, dtype=float), return_index=True, axis=0
300+
reactions = np.unique(np.array(reactions), axis=0)
301+
reactions = np.array(
302+
[[Rational(value).limit_denominator(1000000) for value in v] for v in reactions]
302303
)
303-
reactions = reactions[unique_indices] # return as rationals
304304

305305
assert np.max(reactions[:-1, 0]) == 0
306306
assert np.max(reactions[-1, 1:]) == 0

burnman/tools/polytope.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -164,9 +164,7 @@ def composite_polytope_at_constrained_composition(
164164
eoccs = block_diag(*eoccs)
165165
inequalities = np.concatenate((np.zeros((len(eoccs), 1)), eoccs), axis=1)
166166

167-
return MaterialPolytope(
168-
equalities, inequalities, number_type="float", return_fractions=return_fractions
169-
)
167+
return MaterialPolytope(equalities, inequalities, return_fractions=return_fractions)
170168

171169

172170
def simplify_composite_with_composition(composite, composition):

0 commit comments

Comments
 (0)