Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FIX: Model/calculate: Piecewise branching performance and extrapolation #431

Merged
merged 34 commits into from Aug 23, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
9eed7c9
FIX: Model: Optimize away trivial Piecewise branches for performance
richardotis Oct 13, 2022
4c0318f
FIX: Model: Use raw string to avoid escape sequence in docstring
richardotis Oct 13, 2022
e4698a4
TST: test_model: Add newline at end of file
richardotis Oct 13, 2022
ec4bc10
WIP: Model: Try removing unnecessary branch to fix Mac errors
richardotis Oct 13, 2022
6ef8a4c
CI test only, should be reverted
richardotis Oct 13, 2022
671e006
Revert "CI test only, should be reverted"
richardotis Oct 13, 2022
042af2a
FIX/TST: calculate: Do not sample between endmembers when there are t…
richardotis Oct 14, 2022
5c731eb
Debugging code
richardotis Oct 14, 2022
3ff08a0
fix typo
richardotis Oct 14, 2022
4a5e587
more debugging code
richardotis Oct 14, 2022
c38693f
debugging toggle verbosity for problem tests
richardotis Oct 14, 2022
4eed79f
debugging add energy test
richardotis Oct 14, 2022
e662dea
debugging force lambda backend
richardotis Oct 14, 2022
4c51e1b
Debugging focus fcc_l12 phase
richardotis Oct 14, 2022
cacd39a
debugging piecewise atom level
richardotis Oct 14, 2022
3d3a28d
debugging fix
richardotis Oct 14, 2022
5e54abd
debugging force numerical result
richardotis Oct 14, 2022
9fc9bc5
debugging switch to energy level evaluation
richardotis Oct 15, 2022
ddfa33b
WIP: attempt to fix numerical difference through forced type conversion
richardotis Oct 15, 2022
644d3df
debugging see if disordered fcc has the same issue
richardotis Oct 15, 2022
82ec816
WIP: Try not replacing the ordering contribution
richardotis Oct 15, 2022
b5be57c
WIP: Try unwrapping symbols only
richardotis Oct 15, 2022
771564f
BLD: deploy: Bump versions for wheel build/deploy
richardotis Oct 15, 2022
aa279f2
TST: test_calculate: Change test name
richardotis Oct 15, 2022
a54faec
MAINT: test_equilibrium: Remove debug verbosity from equilibrium calls
richardotis Oct 15, 2022
ee308d3
Merge branch 'develop' into fix-large-branching
richardotis Feb 18, 2023
aadfda3
MAINT: Model: Remove unnecessary float coercion code
richardotis Feb 18, 2023
fb140a2
MAINT: Model: Try removing iteration count limit for Piecewise unwrap…
richardotis Feb 18, 2023
0cafa39
FIX: calculate: Still add some extra_points for very complex phases
richardotis Apr 6, 2023
1abd412
Merge branch 'develop' into fix-large-branching
richardotis Apr 22, 2023
acdb576
FIX: calculate: Compute lingrid based on actual number of endmember p…
richardotis May 7, 2023
0701ee6
ENH/FIX: Model: Extrapolate beyond temperature limits by default
richardotis Aug 10, 2023
87df9f5
Merge branch 'develop' into fix-large-branching
richardotis Aug 10, 2023
3541397
TST/FIX: extrapolate_temperature: Confirm Model extrapolation works a…
richardotis Aug 11, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
16 changes: 9 additions & 7 deletions pycalphad/core/calculate.py
Expand Up @@ -19,7 +19,7 @@
from pycalphad.core.utils import endmember_matrix, extract_parameters, \
get_pure_elements, filter_phases, instantiate_models, point_sample, \
unpack_components, unpack_condition, unpack_kwarg
from pycalphad.core.constants import MIN_SITE_FRACTION
from pycalphad.core.constants import MIN_SITE_FRACTION, MAX_ENDMEMBER_PAIRS, MAX_EXTRA_POINTS


def hr_point_sample(constraint_jac, constraint_rhs, initial_point, num_points):
Expand Down Expand Up @@ -150,12 +150,13 @@ def _sample_phase_constitution(model, sampler, fixed_grid, pdens):
if (fixed_grid is True) and not linearly_constrained_space:
# Sample along the edges of the endmembers
# These constitution space edges are often the equilibrium points!
em_pairs = list(itertools.combinations(points, 2))
lingrid = np.linspace(0, 1, pdens)
extra_points = [first_em * lingrid[np.newaxis].T +
second_em * lingrid[::-1][np.newaxis].T
for first_em, second_em in em_pairs]
points = np.concatenate(list(itertools.chain([points], extra_points)))
em_pairs = list(itertools.combinations(points, 2))[:MAX_ENDMEMBER_PAIRS]
if len(em_pairs) > 0:
lingrid = np.linspace(0, 1, int(min(pdens, MAX_EXTRA_POINTS/len(em_pairs))))
extra_points = [first_em * lingrid[np.newaxis].T +
second_em * lingrid[::-1][np.newaxis].T
for first_em, second_em in em_pairs]
points = np.concatenate(list(itertools.chain([points], extra_points)))
# Sample composition space for more points
if sum(sublattice_dof) > len(sublattice_dof):
if linearly_constrained_space:
Expand Down Expand Up @@ -477,6 +478,7 @@ def calculate(dbf, comps, phases, mode=None, output='GM', fake_points=False, bro
raise ValueError(f"model must contain a Model instance for every active phase. Missing Model objects for {sorted(active_phases_without_models)}")

maximum_internal_dof = max(len(models[phase_name].site_fractions) for phase_name in active_phases)

for phase_name in sorted(active_phases):
mod = models[phase_name]
phase_record = phase_records[phase_name]
Expand Down
5 changes: 5 additions & 0 deletions pycalphad/core/constants.py
Expand Up @@ -12,3 +12,8 @@

# Constraint scaling factors, for numerical stability
INTERNAL_CONSTRAINT_SCALING = 1.0

# Prevent excessive sampling for very complex phase models
# This avoids running out of RAM
MAX_ENDMEMBER_PAIRS = 5000 # ~100 endmembers
MAX_EXTRA_POINTS = 90000
59 changes: 56 additions & 3 deletions pycalphad/model.py
Expand Up @@ -97,6 +97,12 @@ def __repr__(self):
s = "ReferenceState('{}', '{}')".format(self.species.name, self.phase_name)
return s

class classproperty(object):
# https://stackoverflow.com/questions/5189699/how-to-make-a-class-property
def __init__(self, f):
self.f = f
def __get__(self, obj, owner):
return self.f(owner)

class Model(object):
"""
Expand Down Expand Up @@ -154,6 +160,13 @@ class Model(object):
('2st', 'twostate_energy'), ('ein', 'einstein_energy'),
('vol', 'volume_energy'), ('ord', 'atomic_ordering_energy')]

# Behave as if piecewise temperature bounds extend to +-inf (i.e., ignore lower/upper T limits for parameters)
# This follows the behavior of most commercial codes, but may be undesirable in some circumstances
# Designed to be readonly on instances, because mutation after initialization will not work
@classproperty
def extrapolate_temperature_bounds(cls):
return True

def __new__(cls, *args, **kwargs):
target_cls = cls._dispatch_on(*args, **kwargs)
instance = object.__new__(target_cls)
Expand Down Expand Up @@ -261,8 +274,47 @@ def __init__(self, dbe, comps, phase_name, parameters=None):
self.site_fractions = sorted([x for x in self.variables if isinstance(x, v.SiteFraction)], key=str)
self.state_variables = sorted([x for x in self.variables if not isinstance(x, v.SiteFraction)], key=str)

@staticmethod
def symbol_replace(obj, symbols):
@classmethod
def unwrap_piecewise(cls, graph):
from pycalphad.io.tdb import to_interval
replace_dict = {}
for atom in graph.atoms(Piecewise):
args = atom.args
# Unwrap temperature-dependent piecewise with zero-defaults
if len(args) == 4 and args[2] == 0 and args[3] == True and args[1].free_symbols == {v.T}:
replace_dict[atom] = args[0]
elif cls.extrapolate_temperature_bounds:
# Set lower and upper temperature limits to -+infinity
# First filter out default zero-branches
filtered_args = [(x, cond) for x, cond in zip(*[iter(args)]*2) if not ((cond == S.true) and (x == S.Zero))]
if len(filtered_args) == 0:
continue
if not all([cond.free_symbols == {v.T} for _, cond in filtered_args]):
# Only temperature-dependent piecewise conditions are supported for extrapolation
continue
intervals = [to_interval(cond) for _, cond in filtered_args]
sortindices = [i[0] for i in sorted(enumerate(intervals), key=lambda x:x[1].args[0])]
if (intervals[sortindices[0]].args[0] == S.NegativeInfinity) and \
(intervals[sortindices[-1]].args[1] == S.Infinity):
# Nothing to do, temperature range already extrapolated
continue
# First branch is special-cased to negative infinity
exprcondpairs = [(filtered_args[sortindices[0]][0], v.T < intervals[sortindices[0]].args[1])]
for idx in sortindices[1:-1]:
exprcondpairs.append((filtered_args[sortindices[idx]][0],
And(v.T >= intervals[sortindices[idx]].args[0], v.T < intervals[sortindices[idx]].args[1])
))
# Last branch is special-cased to positive infinity
exprcondpairs.append((filtered_args[sortindices[-1]][0],
v.T >= intervals[sortindices[-1]].args[0]
))
# Catch-all branch required for LLVM (should never hit in this formulation)
exprcondpairs.append((0, True))
replace_dict[atom] = Piecewise(*exprcondpairs)
return graph.xreplace(replace_dict)

@classmethod
def symbol_replace(cls, obj, symbols):
"""
Substitute values of symbols into 'obj'.

Expand All @@ -280,6 +332,7 @@ def symbol_replace(obj, symbols):
# of other symbols
for iteration in range(_MAX_PARAM_NESTING):
obj = obj.xreplace(symbols)
obj = cls.unwrap_piecewise(obj)
undefs = [x for x in obj.free_symbols if not isinstance(x, v.StateVariable)]
if len(undefs) == 0:
break
Expand Down Expand Up @@ -1117,7 +1170,7 @@ def _partitioned_expr(disord_expr, ord_expr, disordered_mole_fraction_dict, orde
return disord_expr + ordering_expr

def atomic_ordering_energy(self, dbe):
"""
r"""
Return the atomic ordering contribution in symbolic form.

If the current phase is anything other than the ordered phase in a
Expand Down