[cig-commits] [commit] add_thermodynamic_potentials: Add a bunch of documentation (6002e6b)
cig_noreply at geodynamics.org
cig_noreply at geodynamics.org
Tue Dec 9 09:56:26 PST 2014
Repository : https://github.com/geodynamics/burnman
On branch : add_thermodynamic_potentials
Link : https://github.com/geodynamics/burnman/compare/2e5646d93cedbbf8eae54cc37cffc14e0aa85180...d5ddad03ff9f30f5a4efaddb4e3ec585ea1a7c51
>---------------------------------------------------------------
commit 6002e6b314b14d1854d106fb97ee7b02c0af8c6c
Author: ian-r-rose <ian.r.rose at gmail.com>
Date: Fri Sep 26 19:40:34 2014 -0700
Add a bunch of documentation
>---------------------------------------------------------------
6002e6b314b14d1854d106fb97ee7b02c0af8c6c
burnman/gibbsminimization.py | 94 +++++++++++++++++++++++++++++++++++++++++---
1 file changed, 88 insertions(+), 6 deletions(-)
diff --git a/burnman/gibbsminimization.py b/burnman/gibbsminimization.py
index 6abff1a..f3e4320 100644
--- a/burnman/gibbsminimization.py
+++ b/burnman/gibbsminimization.py
@@ -15,6 +15,27 @@ def assemble_stoichiometric_matrix ( minerals):
the rows are elements and the columns are species (or endmembers).
If a solid solution is passed in, then the endmembers are extracted
from it.
+
+ Parameters
+ ----------
+ minerals : list of minerals
+ List of objects of type :class:`burnman.Mineral` or `burnman.SolidSolution`.
+ Other types cannot be understood, and an exception will be thrown.
+
+ Returns
+ -------
+ stoichiometric_matrix: 2D numpy array
+ 2D numpy array that is n_elements by n_endmembers. The matrix entries
+ are the number of atoms of an element in that particular endmember.
+ The elements are ordered alphabetically, and the endmembers are ordered
+ in the order in which they are passed in.
+ elements: list of strings
+ List of elements that are in the stoichiometric matrix, in alphabetical
+ order.
+ formulae: list of strings
+ List of endmember formulae constructed from the minerals passed in.
+ They are ordered in the same order as they are passed in, but with
+ The endmembers in the solid solutions also included.
"""
elements = set()
@@ -57,40 +78,101 @@ def assemble_stoichiometric_matrix ( minerals):
def compute_nullspace ( stoichiometric_matrix ):
"""
Given a stoichiometric matrix, compute a basis for the nullspace.
- TODO: The basis for the nullspace is nonunique, and in general there
- is no reason to expect that the one returned by svd is in any way
- physically meaningful. We would really like to have the 'sparsest'
- possible basis, which corresponds to the set of independent reactions
- with the fewest possible reactants. Unfortunately this is an NP
- hard problem. There are some ways to do it, but I have not gone for it.
+ This basis corresponds to the subspace that does not change the
+ bulk composition. Therefore the vectors correspond to chemical
+ reactions. This merely identifies a set of linearly independent
+ reactions, without saying anything about which ones are likely to
+ occur.
+
+ The calculation of the nullspace is done with and SVD.
+
+ Parameters
+ ----------
+ stoichiometric_matrix: numpy array
+ The stoichiometric matrix, presumably calculated using :func:`compute_stoichiometric_matrix`.
+
+ Returns
+ -------
+ nullspace: numpy array
+ A 2D array correspnding to the nullspace of the stoichiometric matrix,
+ with the columns corresponding to basis vectors.
"""
eps = 1.e-10
+ # Do an SVD of the stoichiometric matrix.
U, S, Vh = linalg.svd( stoichiometric_matrix)
+
+ # The columns of V that correspond to small singular values
+ # (or do not exist) are the left nullspace for the matrix.
+ # select them, appropriately transpose them, and return them
S=np.append(S, [0. for i in range(len(Vh)-len(S))])
null_mask = (S <= eps)
null_space = np.compress(null_mask, Vh, axis=0)
+
return np.transpose(null_space)
def sparsify_basis ( basis ):
+ """
+ The vectors computed using :func:`compute_nullspace` are non-unique, and are
+ not likely to correspond to the most physically meaningful set of reactions.
+ Instead we would often like to know a set of nullspace vectors that are the
+ "sparsest", meaning that they involve a minimal set of reactions. This is known
+ to be an NP-hard problem. This function makes a fairly crude attempt to
+ take a set of vectors and rotate them to the sparsest coordinate system. It does
+ this by doing many L1 minimizations of the vectors, and as such might be
+ pretty slow, especially for large sets of bases.
+
+ Parameters
+ ----------
+ basis: numpy array
+ A 2D array correspnding to the basis,
+ with the columns corresponding to basis vectors.
+
+ Returns
+ -------
+ new_basis: numpy array
+ A 2D array correspnding to the attempt at a sparser basis,
+ with the columns corresponding to basis vectors.
+ """
+
eps = 1.e-6
new_basis = basis.copy()
n_cols = new_basis.shape[1]
n_rows = new_basis.shape[0]
+ # Okay, this is kind of ugly. The idea is that we want to make a new basis by
+ # making linear combinations of the old basis vectors, while attempting to
+ # minimize the L1 norm of the new basis vectors. So we loop over each basis
+ # vector and try to make a new one of all the vectors AFTER it in the list.
+ # After this minimization is complete, we project that (now sparse) vector
+ # out of the rest of them in a standard Gram-Schmidt way, and move on to the
+ # next vector. After all are done, we return the new basis.
+
+ #lambda function for computing L1 norm of a vector
l1 = lambda x : np.sum( np.abs (x) )
+ # Add a linear combination of all but the first column of B into
+ # the first column of B, according to x
combine = lambda B, x: np.dot( B, np.append( np.array([1.0,]), x) )
+ #Loop over all the columns
for i in range( n_cols ):
+
+ #Find a linear combination of all the columns after the ith one that minimizes
+ # the L1 norm of the ith column
sp = opt.fmin( lambda x : l1(combine( new_basis[:, i:n_cols], x )), np.ones(n_cols-i-1), disp=0, xtol = eps)
new_basis[:,i] = np.reshape(combine( new_basis[:, i:n_cols], sp), (n_rows,))
new_basis[:,i] = new_basis[:,i]/linalg.norm(new_basis[:,i])
+
+ #Now project that column out of all the others.
for j in range (i+1, n_cols):
new_basis[:,j] = new_basis[:,j] - np.dot(new_basis[:,i], new_basis[:,j])*new_basis[:,i]
new_basis[:,j] = new_basis[:,j]/linalg.norm(new_basis[:,j])
+
+ #Finally, there should now be a lot of near-zero entries in the new basis.
+ #Explicitly zero them out.
new_basis[ np.abs(new_basis) < eps ] = 0.
return new_basis
More information about the CIG-COMMITS
mailing list