@@ -312,3 +312,75 @@ def simplify_composite_with_composition(composite, composition):
312312 return Composite (new_phases )
313313 else :
314314 return composite
315+
316+
317+ def greedy_independent_endmember_selection (
318+ endmember_site_occupancies , site_occupancies , small_fraction_tol = 0.0 , norm_tol = 1e-12
319+ ):
320+ """
321+ Greedy algorithm to select independent endmembers from a solution to approximate given
322+ site occupancies through a non-negative linear combination of endmember site occupancies.
323+
324+ This function starts with the full site occupancies and then iteratively selects endmembers
325+ to approximate those site occupancies. It loops through all possible endmembers in a number of steps.
326+ At each step the algorithm selects the endmember that can be subtracted in the largest amount from the
327+ current residual site occupancies without making any site occupancy negative.
328+ The process continues until either no endmember can be subtracted in an amount greater than fraction_tol,
329+ or the norm of the residual site occupancies is less than tol.
330+
331+ :param endmember_site_occupancies: A 2D array of shape (m, n), where m is the number of endmembers
332+ and n is the number of sites. Each row corresponds to the site occupancies of an endmember.
333+ :type endmember_site_occupancies: np.ndarray
334+
335+ :param site_occupancies: A 1D array of length n, representing the target site occupancies to approximate.
336+ :type site_occupancies: np.ndarray
337+
338+ :param small_fraction_tol: Algorithm stops if no endmember can be added with a molar fraction larger than this value.
339+ :type small_fraction_tol: float, optional, default 0.0
340+
341+ :param norm_tol: Algorithm stops if the norm of the residual site occupancies is less than this value.
342+ :type norm_tol: float, optional, default 1e-12
343+
344+ :return: indices of selected endmembers, their fractions, and the final residual site occupancies.
345+ :rtype: tuple(list[int], list[float], np.ndarray)
346+ """
347+
348+ site_occupancy_residuals = site_occupancies .copy ()
349+ indices = []
350+ fractions = []
351+
352+ for _ in range (endmember_site_occupancies .shape [0 ]):
353+ # compute fraction for each candidate endmember
354+ # fraction_i = min_j r_j / s_ij over s_ij > 0; if no s_ij>0 -> fraction_i = 0
355+ with np .errstate (divide = "ignore" , invalid = "ignore" ):
356+ ratios = np .where (
357+ endmember_site_occupancies > 0 ,
358+ site_occupancy_residuals [np .newaxis , :] / endmember_site_occupancies ,
359+ np .inf ,
360+ ) # shape (m,n)
361+
362+ # For each row, take minimum ratio over columns where S>0; if all entries inf -> set 0
363+ fractions_all = np .min (ratios , axis = 1 )
364+ fractions_all [np .isinf (fractions_all )] = 0.0
365+
366+ # pick largest alpha
367+ i_max = int (np .argmax (fractions_all ))
368+ fraction_max = float (fractions_all [i_max ])
369+ if fraction_max <= small_fraction_tol :
370+ break # no further progress possible or desirable
371+
372+ # update residual
373+ site_occupancy_residuals = (
374+ site_occupancy_residuals - fraction_max * endmember_site_occupancies [i_max ]
375+ )
376+
377+ # ensure non-negativity in elements of the residual
378+ site_occupancy_residuals [site_occupancy_residuals < 0 ] = 0.0
379+ indices .append (i_max )
380+ fractions .append (fraction_max )
381+
382+ # check if done
383+ if np .linalg .norm (site_occupancy_residuals ) <= norm_tol :
384+ break
385+
386+ return indices , fractions , site_occupancy_residuals
0 commit comments