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

PottsSubspace #146

Merged
merged 36 commits into from
Feb 1, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
fdfb363
change basis iterators to iterate over all species (correct handling …
lbluque Nov 11, 2020
142c554
implement abstract DiscreteBasis and trivial IndicatorBasis
lbluque Nov 11, 2020
d9aa64c
code cleanup in clusterspace.py
lbluque Nov 12, 2020
b7c9a0b
PottsSubspace in the works
lbluque Nov 12, 2020
bbd61dc
msonable for IndicatorBasis
lbluque Nov 13, 2020
bb17d97
sprinkle some polymorphism attempts for using different types of site…
lbluque Nov 13, 2020
ba8c551
more cosmetic clean up in orbit.py
lbluque Nov 14, 2020
19f33e4
cleanup basis.py move ortho properties to ABC discrete basis
lbluque Nov 14, 2020
5c40531
Merge branch 'master' into potts
lbluque Nov 16, 2020
fb16b11
Merge branch 'master' into potts
lbluque Nov 20, 2020
d566b91
Merge branch 'master' into potts
lbluque Dec 9, 2020
4e65f94
remove TypeError test for basis construction
lbluque Dec 9, 2020
947a1f3
update orbit creation in Potts
lbluque Dec 9, 2020
d59db52
Merge branch 'master' into potts
lbluque Jan 7, 2021
d6d97a5
Merge branch 'master' into potts
lbluque Jan 8, 2021
20da9aa
fix Potts msonable
lbluque Jan 14, 2021
18f9a20
Merge branches 'master' and 'potts' of https://github.com/lbluque/smo…
lbluque Jan 25, 2021
3e3f74b
fix import
lbluque Jan 25, 2021
8b1f0a4
Merge branch 'master' into potts
lbluque Jan 28, 2021
207725d
add option to include/remove last cluster labeling
lbluque Feb 5, 2021
32631fc
fix Potts from_dict
lbluque Feb 9, 2021
208979f
Merge branch 'master' into potts
lbluque Mar 19, 2021
4367aca
Merge branch 'master' into potts
lbluque Jun 30, 2021
3345550
Merge branch 'master' into potts
lbluque Aug 18, 2021
0ac65d8
Merge branch 'master' into potts
lbluque Aug 18, 2021
fbb5655
Merge branch 'master' of https://github.com/CederGroupHub/smol into p…
lbluque Sep 7, 2021
6bfaec7
Merge branch 'master' of https://github.com/lbluque/smol into potts
lbluque Oct 27, 2021
2bebb8e
convenience Potts import
lbluque Oct 27, 2021
7f2fbe5
fix IndicatorBasis import
lbluque Oct 27, 2021
6c25bbb
Merge branch 'master' into potts
lbluque Nov 8, 2021
fe29c95
update orbit generation in Potts to match new clusterspace
lbluque Jan 19, 2022
be675e2
fix bases array generation for Potts
lbluque Jan 22, 2022
7d49c97
Merge branch 'master' into potts
lbluque Jan 28, 2022
f92ac7e
Merge branch 'master' into potts
lbluque Jan 31, 2022
c7d4c02
potts unit tests
lbluque Jan 31, 2022
133ca5d
Merge branch 'master' of https://github.com/CederGroupHub/smol into p…
lbluque Jan 31, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,7 @@ as functions in `cofe.wrangling.filter`.
inherited classes instead of string names. (This is to allow keeping additional
properties for species, such as oxidation state, magnetization, the sky is the
limit.)
* Single `SiteBasis` site basis class that is constructed using a basis
* Single `StandardBasis` site basis class that is constructed using a basis
function iterator for specific basis sets.
* Example notebooks updated accordingly.

Expand All @@ -214,7 +214,7 @@ crystallographic symmetry multiplicity and function decoration multiplicity.
(credits to [qchempku2017](https://github.com/qchempku2017) for pointing this
out.)
* Fixed MSONable serialization of cluster subspaces with orthonormal basis sets
by making `SiteBasis` MSONable and saving corresponding arrays.
by making `StandardBasis` MSONable and saving corresponding arrays.
[\#90](https://github.com/CederGroupHub/smol/pull/90)


Expand Down
8 changes: 4 additions & 4 deletions smol/cofe/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@
functions to define and fit cluster expansions for crystalline materials.
"""

from .space.clusterspace import ClusterSubspace
from .space.clusterspace import ClusterSubspace, PottsSubspace
from .expansion import ClusterExpansion, RegressionData
from smol.cofe.wrangling.wrangler import (StructureWrangler)
from smol.cofe.wrangling.wrangler import StructureWrangler

__all__ = ['ClusterSubspace', 'StructureWrangler', 'ClusterExpansion',
'RegressionData']
__all__ = ['ClusterSubspace', 'PottsSubspace', 'StructureWrangler',
'ClusterExpansion', 'RegressionData']
256 changes: 185 additions & 71 deletions smol/cofe/space/basis.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,8 @@
is concentration of the species in the random structure)
"""

__author__ = "Luis Barroso-Luque"

import warnings
from abc import abstractmethod
from abc import ABCMeta, abstractmethod
from collections import OrderedDict
from collections.abc import Iterator
from functools import partial, wraps
Expand All @@ -20,78 +18,57 @@
from monty.json import MSONable

from .domain import SiteSpace
from smol.utils import derived_class_factory
from smol.utils import derived_class_factory, get_subclasses

ATOL, RTOL = 1E-12, 1E-8


class SiteBasis(MSONable):
r"""Class that represents the basis for a site function space.
__author__ = "Luis Barroso-Luque"

Note that all SiteBasis in theory have the first basis function
:math:`\phi_0 = 1`, but this should not be defined since it is handled
implicitly when computing bit_combos using total no. species - 1 in the
Orbit class.

The particular basis set is set by giving an iterable of basis functions.
See BasisIterator classes for details.
class DiscreteBasis(MSONable, metaclass=ABCMeta):
"""Abstract class to represent a basis set over a discrete finite domain.

In our case the domain is a site space which can take on values of the
allowed species.
"""

def __init__(self, site_space, basis_functions):
"""Initialize a SiteBasis.
"""Initialize a StandardBasis.

Currently also accepts an OrderedDict but if you find yourself creating
one like so for use in production and not debuging know that it will
break MSONable methods in classes that use these, and at any point I
could change this to not allow OrderedDicts.
Currently also accepts an OrderedDict but if you find yourself
creating one like so for use in production and not debuging know that
it will break MSONable methods in classes that use these, and at any
point I could change this to not allow OrderedDicts.

Args:
site_space (OrderedDict or SiteSpace):
Dict representing site space (Specie, measure) or a SiteSpace
object.
basis_functions (Sequence like):
A Sequence of the nonconstant basis functions. Must take the
valuves of species as input.
basis_functions (BasisIterator):
A BasisIterator for the nonconstant basis functions. Must take
the values of species in the site space as input.
"""
if isinstance(site_space, OrderedDict):
if not np.allclose(sum(site_space.values()), 1):
warnings.warn('The measure given does not sum to 1.'
'Are you sure this is what you want?',
RuntimeWarning)
elif not isinstance(site_space, SiteSpace):
raise TypeError('site_space argument must be a SiteSpaces or an '
'OrderedDict.')
warnings.warn(
"The measure given does not sum to 1. Are you sure this "
"is what you want?", RuntimeWarning)

self.flavor = basis_functions.flavor
self._domain = site_space
# add non constant basis functions to array
if len(basis_functions) != len(self.species) - 1:
raise ValueError(f'Must provid {len(self.species) - 1 } total non-'
'constant basis functions.'
f' Got only {len(basis_functions)} basis '
'functions.')

func_arr = np.array([[function(sp) for sp in self.species]
for function in basis_functions])
func_arr[abs(func_arr) < np.finfo(np.float64).eps] = 0
# stack the constant basis function on there for proper normalization
self._f_array = np.vstack((np.ones_like(func_arr[0]), func_arr))
self._r_array = None # array from QR in basis orthonormalization

@property
def function_array(self):
"""Get array with the non-constant site functions as rows."""
return self._f_array[1:]
if set(site_space) != set(basis_functions.species):
raise ValueError(
"Basis function iterator provided does not contain all "
f"species {site_space} in the site space provided.")

@property
def measure_vector(self):
"""Get vector of site species measures."""
return np.array(list(self._domain.values()))
self._f_array = self._construct_function_array(basis_functions)

@property
def orthonormalization_array(self):
"""Get R array from QR factorization."""
return self._r_array
@abstractmethod
def _construct_function_array(self, basis_functions):
"""Construct function array with basis functions as rows."""
return

@property
def species(self):
Expand All @@ -108,6 +85,21 @@ def site_space(self):
"""
return self._domain

@property
def function_array(self):
"""Get function array with site functions as rows."""
return self._f_array

@property
def measure_array(self):
"""Get diagonal array with site species measures."""
return np.diag(list(self._domain.values()))

@property
def measure_vector(self):
"""Get vector of site species measures."""
return np.array(list(self._domain.values()))

@property
def is_orthogonal(self):
"""Test if the basis is orthogonal."""
Expand All @@ -122,6 +114,94 @@ def is_orthonormal(self):
prods = (self.measure_vector * self._f_array) @ self._f_array.T
return np.allclose(prods, np.eye(*prods.shape))

def as_dict(self) -> dict:
"""Get MSONable dict representation of a DiscreteBasis."""
d = {"@module": self.__class__.__module__,
"@class": self.__class__.__name__,
"site_space": self._domain.as_dict(),
"flavor": self.flavor}
return d

@classmethod
def from_dict(cls, d):
"""Create a DiscreteBasis from dict representation.

Args:
d (dict):
MSONable dict representation
Returns:
DiscreteBasis: A subclass of DiscreteBasis
"""
try:
subclass = get_subclasses(cls)[d['@class']]
except KeyError:
if d['@class'] == 'SiteBasis':
warnings.warn(
"The object you have loaded was saved with an older "
"version of smol.\n Please save this object again to "
"prevent these warnings.", FutureWarning)
subclass = StandardBasis
else:
raise NameError(
f"{d['@class']} is not implemented or is not a subclass "
f"of {cls}.")

return subclass.from_dict(d)


class StandardBasis(DiscreteBasis):
r"""Class that represents the basis for a site function space.

Note that all StandardBasis in theory have the first basis function
:math:`\phi_0 = 1`, but this should not be defined since it is handled
implicitly when computing bit_combos using total no. species - 1 in the
Orbit class. As such a StandardBasis as implemented here represents a
Standard and/or Fourier site basis (the standard basis using indicator
functions is not a Fourier basis but can be used as "cluster site basis")

The particular basis set is set by giving an iterable of basis functions.
See BasisIterator classes for details.
"""

def __init__(self, site_space, basis_functions):
"""Initialize a StandardBasis.

Currently also accepts an OrderedDict but if you find yourself creating
one like so for use in production and not debuging know that it will
break MSONable methods in classes that use these, and at any point I
could change this to not allow OrderedDicts.

Args:
site_space (OrderedDict or SiteSpace):
Dict representing site space (Specie, measure) or a SiteSpace
object.
basis_functions (BasisIterator):
A BasisIterator for the nonconstant basis functions. Must take
the values of species in the site space as input.
"""
super().__init__(site_space, basis_functions)
self._r_array = None # array from QR in basis orthonormalization

def _construct_function_array(self, basis_functions):
"""Construct function array with basis functions as rows."""
# exclude the last basis function since the constant phi_0 will
# take its place
nconst_functions = [function for function in basis_functions][:-1]
func_arr = np.array([[function(sp) for sp in self.species]
for function in nconst_functions])
# stack the constant basis function on there for proper normalization
return np.vstack((np.ones_like(func_arr[0]), func_arr))

@property
def function_array(self):
"""Get array with the non-constant site functions as rows."""
return self._f_array[1:]

@property
def orthonormalization_array(self):
"""Get R array from QR factorization."""
return self._r_array

def orthonormalize(self):
"""Orthonormalizes basis function set based on initial basis set.

Expand Down Expand Up @@ -201,38 +281,72 @@ def rotate(self, angle, index1=0, index2=1):

def as_dict(self) -> dict:
"""Get MSONable dict representation."""
d = {"@module": self.__class__.__module__,
"@class": self.__class__.__name__,
"site_space": self._domain.as_dict(),
"flavor": self.flavor,
"func_array": self._f_array.tolist(),
"orthonorm_array":
None if self._r_array is None else self._r_array.tolist()
}
d = super().as_dict()
d["func_array"] = self._f_array.tolist()
d["orthonorm_array"] = None if self._r_array is None \
else self._r_array.tolist()
return d

@classmethod
def from_dict(cls, d):
"""Create a SiteSpace from dict representation.
"""Create a StandardBasis from dict representation.

Args:
d (dict):
MSONable dict representation
Returns:
SiteBasis
StandardBasis
"""
site_space = SiteSpace.from_dict(d['site_space'])
# Only using indicator iterator as a proxy to
# initialiaze class any other iterator would do. Perhaps a cleaner
# solution would be to allow initialization without an iterator...
site_basis = cls(site_space,
IndicatorIterator(tuple(site_space.keys())))
site_basis.flavor = d['flavor']
site_basis = basis_factory(d['flavor'], site_space)
# restore arrays
site_basis._f_array = np.array(d['func_array'])
site_basis._r_array = np.array(d['orthonorm_array'])
return site_basis


class IndicatorBasis(DiscreteBasis, MSONable):
"""Class that represents a full indicator basis for a site space.

This class represents the "trivial" indicator basis, wich includes an
indicator function for every species in the site space, and does NOT
include a contant function.
NOT to be confuse with a cluster indicator basis used for a Cluster
Expansion (that is represented in smol by a StandardBasis with a
IndicatorIterator).

@lbluque takes full responsibility for the confusing terminilogy...
"""

def __init__(self, site_space):
"""Initialize an indicator basis for give site space.

Args:
site_space (OrderedDict or SiteSpace):
dict representing site space (Specie, measure) or a SiteSpace
object.
"""
super().__init__(site_space,
IndicatorIterator(tuple(site_space.keys())))

def _construct_function_array(self, basis_functions):
func_array = np.array([[function(sp) for sp in self.species]
for function in basis_functions])
return func_array

@classmethod
def from_dict(cls, d):
"""Create a SiteSpace from dict representation.

Args:
d (dict):
MSONable dict representation
Returns:
StandardBasis
"""
return cls(SiteSpace.from_dict(d['site_space']))


class BasisIterator(Iterator):
r"""Abstract basis iterator class.

Expand All @@ -254,12 +368,12 @@ def __init__(self, species):
species (tuple):
tuple of allowed species in site spaces
"""
self.species_iter = iter(species[:-1]) # all but one species iterator
self.species_iter = iter(species)
self.species = species

def __len__(self):
"""Get length of sequence."""
return len(self.species) - 1
return len(self.species)


class IndicatorIterator(BasisIterator):
Expand Down Expand Up @@ -428,12 +542,12 @@ def basis_factory(basis_name, site_space):
Site space over which the basis set is defined.

Returns:
SiteBasis
StandardBasis
"""
if isinstance(site_space, OrderedDict):
species = tuple(site_space.keys())
else:
species = tuple(site_space)
iterator_name = basis_name.capitalize() + 'Iterator'
basis_funcs = derived_class_factory(iterator_name, BasisIterator, species)
return SiteBasis(site_space, basis_funcs)
return StandardBasis(site_space, basis_funcs)
Loading