From c03973c564790a83ba0e496eab12d6218b2e97bc Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Thu, 1 Aug 2024 15:09:18 -0400 Subject: [PATCH 01/65] Run black diffpy --- diffpy/snmf/__init__.py | 4 +- diffpy/snmf/containers.py | 12 +- diffpy/snmf/io.py | 18 +- diffpy/snmf/optimizers.py | 7 +- diffpy/snmf/stretchednmfapp.py | 52 ++- diffpy/snmf/subroutines.py | 74 +++- diffpy/snmf/tests/test_containers.py | 123 ++++-- diffpy/snmf/tests/test_factorizers.py | 12 +- diffpy/snmf/tests/test_optimizers.py | 8 +- diffpy/snmf/tests/test_polynomials.py | 2 +- diffpy/snmf/tests/test_subroutines.py | 599 +++++++++++++++++++------- diffpy/snmf/version.py | 4 +- pyproject.toml | 22 + 13 files changed, 681 insertions(+), 256 deletions(-) create mode 100644 pyproject.toml diff --git a/diffpy/snmf/__init__.py b/diffpy/snmf/__init__.py index 0fd51682..9e9f1b60 100644 --- a/diffpy/snmf/__init__.py +++ b/diffpy/snmf/__init__.py @@ -17,9 +17,9 @@ """ # obtain version information -__version__ = '0.0.1' +__version__ = "0.0.1" # top-level import -#from diffpy.snmf.pdfmorph_api import pdfmorph, morph_default_config, plot_morph +# from diffpy.snmf.pdfmorph_api import pdfmorph, morph_default_config, plot_morph # End of file diff --git a/diffpy/snmf/containers.py b/diffpy/snmf/containers.py index e25b8d59..947b07a4 100644 --- a/diffpy/snmf/containers.py +++ b/diffpy/snmf/containers.py @@ -40,8 +40,9 @@ def apply_stretch(self, m): stretching operation, and one vector is the second derivative of the stretching operation. """ normalized_grid = np.arange(len(self.grid)) - func = lambda stretching_factor: np.interp(normalized_grid / stretching_factor, normalized_grid, self.iq, - left=0, right=0) + func = lambda stretching_factor: np.interp( + normalized_grid / stretching_factor, normalized_grid, self.iq, left=0, right=0 + ) derivative_func = numdifftools.Derivative(func) second_derivative_func = numdifftools.Derivative(derivative_func) @@ -49,8 +50,11 @@ def apply_stretch(self, m): stretched_component_gra = derivative_func(self.stretching_factors[m]) stretched_component_hess = second_derivative_func(self.stretching_factors[m]) - return np.asarray(stretched_component), np.asarray(stretched_component_gra), np.asarray( - stretched_component_hess) + return ( + np.asarray(stretched_component), + np.asarray(stretched_component_gra), + np.asarray(stretched_component_hess), + ) def apply_weight(self, m, stretched_component=None): """Applies as weight factor to a component signal. diff --git a/diffpy/snmf/io.py b/diffpy/snmf/io.py index 60635692..7132d703 100644 --- a/diffpy/snmf/io.py +++ b/diffpy/snmf/io.py @@ -42,11 +42,21 @@ def initialize_variables(data_input, number_of_components, data_type, sparsity=1 signal_length = data_input.shape[0] number_of_signals = data_input.shape[1] - diagonals = [np.ones(number_of_signals - 2), -2 * np.ones(number_of_signals - 2), np.ones(number_of_signals - 2)] - smoothness_term = .25 * scipy.sparse.diags(diagonals, [0, 1, 2], shape=(number_of_signals - 2, number_of_signals)) + diagonals = [ + np.ones(number_of_signals - 2), + -2 * np.ones(number_of_signals - 2), + np.ones(number_of_signals - 2), + ] + smoothness_term = 0.25 * scipy.sparse.diags( + diagonals, [0, 1, 2], shape=(number_of_signals - 2, number_of_signals) + ) hessian_helper_matrix = scipy.sparse.block_diag([smoothness_term.T @ smoothness_term] * number_of_components) - sequence = np.arange(number_of_signals * number_of_components).reshape(number_of_components, number_of_signals).T.flatten() + sequence = ( + np.arange(number_of_signals * number_of_components) + .reshape(number_of_components, number_of_signals) + .T.flatten() + ) hessian_helper_matrix = hessian_helper_matrix[sequence, :][:, sequence] return { @@ -57,7 +67,7 @@ def initialize_variables(data_input, number_of_components, data_type, sparsity=1 "smoothness": smoothness, "sparsity": sparsity, "smoothness_term": smoothness_term, - "hessian_helper_matrix": hessian_helper_matrix + "hessian_helper_matrix": hessian_helper_matrix, } diff --git a/diffpy/snmf/optimizers.py b/diffpy/snmf/optimizers.py index 722c1025..8c94c583 100644 --- a/diffpy/snmf/optimizers.py +++ b/diffpy/snmf/optimizers.py @@ -20,7 +20,7 @@ def get_weights(stretched_component_gram_matrix, linear_coefficient, lower_bound Has length C. lower_bound: 1d array like - The lower bound on the values of the output weights. Has the same dimensions of the function output. Each + The lower bound on the values of the output weights. Has the same dimensions of the function output. Each element in 'lower_bound' determines the minimum value the corresponding element in the function output may take. upper_bound: 1d array like @@ -43,8 +43,9 @@ def get_weights(stretched_component_gram_matrix, linear_coefficient, lower_bound solution_variable = cvxpy.Variable(problem_size) objective = cvxpy.Minimize( - linear_coefficient.T @ solution_variable + 0.5 * cvxpy.quad_form(solution_variable, - stretched_component_gram_matrix)) + linear_coefficient.T @ solution_variable + + 0.5 * cvxpy.quad_form(solution_variable, stretched_component_gram_matrix) + ) constraints = [lower_bound <= solution_variable, solution_variable <= upper_bound] cvxpy.Problem(objective, constraints).solve() diff --git a/diffpy/snmf/stretchednmfapp.py b/diffpy/snmf/stretchednmfapp.py index 067d5279..7feb5ce5 100644 --- a/diffpy/snmf/stretchednmfapp.py +++ b/diffpy/snmf/stretchednmfapp.py @@ -5,25 +5,47 @@ from diffpy.snmf.containers import ComponentSignal from diffpy.snmf.io import load_input_signals, initialize_variables -ALLOWED_DATA_TYPES = ['powder_diffraction', 'pd', 'pair_distribution_function', 'pdf'] +ALLOWED_DATA_TYPES = ["powder_diffraction", "pd", "pair_distribution_function", "pdf"] def create_parser(): parser = argparse.ArgumentParser( - prog="stretched_nmf", - description="Stretched Nonnegative Matrix Factorization" + prog="stretched_nmf", description="Stretched Nonnegative Matrix Factorization" ) - parser.add_argument('-i', '--input-directory', type=str, default=None, - help="Directory containing experimental data. Defaults to current working directory.") - parser.add_argument('-o', '--output-directory', type=str, - help="The directory where the results will be written. Defaults to '/snmf_results'.") - parser.add_argument('t', '--data-type', type=str, default=None, choices=ALLOWED_DATA_TYPES, - help="The type of the experimental data.") - parser.add_argument('-l', '--lift-factor', type=float, default=1, - help="The lifting factor. Data will be lifted by lifted_data = data + abs(min(data) * lift). Default is 1.") - parser.add_argument('number-of-components', type=int, - help="The number of component signals for the NMF decomposition. Must be an integer greater than 0") - parser.add_argument('-v', '--version', action='version', help='Print the software version number') + parser.add_argument( + "-i", + "--input-directory", + type=str, + default=None, + help="Directory containing experimental data. Defaults to current working directory.", + ) + parser.add_argument( + "-o", + "--output-directory", + type=str, + help="The directory where the results will be written. Defaults to '/snmf_results'.", + ) + parser.add_argument( + "t", + "--data-type", + type=str, + default=None, + choices=ALLOWED_DATA_TYPES, + help="The type of the experimental data.", + ) + parser.add_argument( + "-l", + "--lift-factor", + type=float, + default=1, + help="The lifting factor. Data will be lifted by lifted_data = data + abs(min(data) * lift). Default is 1.", + ) + parser.add_argument( + "number-of-components", + type=int, + help="The number of component signals for the NMF decomposition. Must be an integer greater than 0", + ) + parser.add_argument("-v", "--version", action="version", help="Print the software version number") args = parser.parse_args() return args @@ -35,5 +57,5 @@ def main(): grid, input_data = load_input_signals(args.input_directory) lifted_input_data = lift_data(input_data, args.lift_factor) variables = initialize_variables(lifted_input_data, args.number_of_components, args.data_type) - components = initialize_components(variables['number_of_components'], variables['number_of_signals'], grid) + components = initialize_components(variables["number_of_components"], variables["number_of_signals"], grid) return components diff --git a/diffpy/snmf/subroutines.py b/diffpy/snmf/subroutines.py index c0052bbb..e3acafbc 100644 --- a/diffpy/snmf/subroutines.py +++ b/diffpy/snmf/subroutines.py @@ -170,11 +170,15 @@ def update_weights(components, data_input, method=None): stretched_components = np.zeros((signal_length, number_of_components)) for i, component in enumerate(components): stretched_components[:, i] = component.apply_stretch(signal)[0] - if method == 'align': + if method == "align": weights = lsqnonneg(stretched_components, data_input[:, signal]) else: - weights = get_weights(stretched_components.T @ stretched_components, - -stretched_components.T @ data_input[:, signal], 0, 1) + weights = get_weights( + stretched_components.T @ stretched_components, + -stretched_components.T @ data_input[:, signal], + 0, + 1, + ) weight_matrix[:, signal] = weights return weight_matrix @@ -236,13 +240,16 @@ def initialize_arrays(number_of_components, number_of_moments, signal_length): """ component_matrix_guess = np.random.rand(signal_length, number_of_components) weight_matrix_guess = np.random.rand(number_of_components, number_of_moments) - stretching_matrix_guess = np.ones(number_of_components, number_of_moments) + np.random.randn(number_of_components, - number_of_moments) * 1e-3 + stretching_matrix_guess = ( + np.ones(number_of_components, number_of_moments) + + np.random.randn(number_of_components, number_of_moments) * 1e-3 + ) return component_matrix_guess, weight_matrix_guess, stretching_matrix_guess -def objective_function(residual_matrix, stretching_factor_matrix, smoothness, smoothness_term, component_matrix, - sparsity): +def objective_function( + residual_matrix, stretching_factor_matrix, smoothness, smoothness_term, component_matrix, sparsity +): """Defines the objective function of the algorithm and returns its value. Calculates the value of '(||residual_matrix||_F) ** 2 + smoothness * (||smoothness_term * @@ -284,8 +291,11 @@ def objective_function(residual_matrix, stretching_factor_matrix, smoothness, sm residual_matrix = np.asarray(residual_matrix) stretching_factor_matrix = np.asarray(stretching_factor_matrix) component_matrix = np.asarray(component_matrix) - return .5 * np.linalg.norm(residual_matrix, 'fro') ** 2 + .5 * smoothness * np.linalg.norm( - smoothness_term @ stretching_factor_matrix.T, 'fro') ** 2 + sparsity * np.sum(np.sqrt(component_matrix)) + return ( + 0.5 * np.linalg.norm(residual_matrix, "fro") ** 2 + + 0.5 * smoothness * np.linalg.norm(smoothness_term @ stretching_factor_matrix.T, "fro") ** 2 + + sparsity * np.sum(np.sqrt(component_matrix)) + ) def get_stretched_component(stretching_factor, component, signal_length): @@ -325,11 +335,23 @@ def stretched_component_func(stretching_factor): stretched_component_gra = derivative_func(stretching_factor) stretched_component_hess = second_derivative_func(stretching_factor) - return np.asarray(stretched_component), np.asarray(stretched_component_gra), np.asarray(stretched_component_hess) - - -def update_weights_matrix(component_amount, signal_length, stretching_factor_matrix, component_matrix, data_input, - moment_amount, weights_matrix, method): + return ( + np.asarray(stretched_component), + np.asarray(stretched_component_gra), + np.asarray(stretched_component_hess), + ) + + +def update_weights_matrix( + component_amount, + signal_length, + stretching_factor_matrix, + component_matrix, + data_input, + moment_amount, + weights_matrix, + method, +): """Update the weight factors matrix. Parameters @@ -376,21 +398,25 @@ def update_weights_matrix(component_amount, signal_length, stretching_factor_mat for i in range(moment_amount): stretched_components = np.zeros((signal_length, component_amount)) for n in range(component_amount): - stretched_components[:, n] = get_stretched_component(stretching_factor_matrix[n, i], component_matrix[:, n], - signal_length)[0] - if method == 'align': + stretched_components[:, n] = get_stretched_component( + stretching_factor_matrix[n, i], component_matrix[:, n], signal_length + )[0] + if method == "align": weight = lsqnonneg(stretched_components[0:signal_length, :], data_input[0:signal_length, i]) else: weight = get_weights( stretched_components[0:signal_length, :].T @ stretched_components[0:signal_length, :], -1 * stretched_components[0:signal_length, :].T @ data_input[0:signal_length, i], - 0, 1) + 0, + 1, + ) weights_matrix[:, i] = weight return weights_matrix -def get_residual_matrix(component_matrix, weights_matrix, stretching_matrix, data_input, moment_amount, - component_amount, signal_length): +def get_residual_matrix( + component_matrix, weights_matrix, stretching_matrix, data_input, moment_amount, component_amount, signal_length +): """Obtains the residual matrix between the experimental data and calculated data Calculates the difference between the experimental data and the reconstructed experimental data created from the @@ -442,9 +468,11 @@ def get_residual_matrix(component_matrix, weights_matrix, stretching_matrix, dat for m in range(moment_amount): residual = residual_matrx[:, m] for k in range(component_amount): - residual = residual + weights_matrix[k, m] * get_stretched_component(stretching_matrix[k, m], - component_matrix[:, k], signal_length)[ - 0] + residual = ( + residual + + weights_matrix[k, m] + * get_stretched_component(stretching_matrix[k, m], component_matrix[:, k], signal_length)[0] + ) residual_matrx[:, m] = residual return residual_matrx diff --git a/diffpy/snmf/tests/test_containers.py b/diffpy/snmf/tests/test_containers.py index a80a8767..2ccb0360 100644 --- a/diffpy/snmf/tests/test_containers.py +++ b/diffpy/snmf/tests/test_containers.py @@ -2,32 +2,84 @@ import numpy as np from diffpy.snmf.containers import ComponentSignal -tas = [([np.arange(10), 3, 0, [6.55, .357, 8.49, 9.33, 6.78, 7.57, 7.43, 3.92, 6.55, 1.71], .25], - [[6.55, 6.78, 6.55, 0, 0, 0, 0, 0, 0, 0], [0, 14.07893122, 35.36478086, 0, 0, 0, 0, 0, 0, 0], - [0, -19.92049156, 11.6931482, 0, 0, 0, 0, 0, 0, 0]]), - ([np.arange(5), 10, 0, [-11.47, -10.688, -8.095, -29.44, 14.38], 1.25], - [[-11.47, -10.8444, -9.1322, -16.633, -20.6760], [0, -.50048, -3.31904, 40.9824, -112.1792], - [0, .800768, 5.310464, -65.57184, 179.48672]]), - ([np.arange(5), 2, 0, [-11.47, -10.688, -8.095, -29.44, 14.38], .88], - [[-11.47, -10.3344, -13.9164, -11.5136, 0], [0, -3.3484, 55.1265, -169.7572, 0], - [0, 7.609997, -125.2876, 385.81189, 0]]), - ([np.arange(10), 1, 2, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], .88], - [[1, 2.1364, 3.2727, 4.4091, 5.5455, 6.6818, 7.8182, 8.9545, 0, 0], - [0, -1.29, -2.58, -3.87, -5.165, -6.45, -7.74, -9.039, 0, 0], - [0, 2.93, 5.869, 8.084, 11.739, 14.674, 17.608, 20.5437, 0, 0]]), - ([np.arange(14), 100, 3, - [-2.9384, -1.4623, -2.0913, 4.6304, -1.2127, 1.4737, -0.3791, 1.7506, -1.5068, -2.7625, .9617, -.3494, -.3862, - 2.7960], .55], [[-2.9384, -1.9769, 0.9121, .6314, .8622, -2.4239, -.2302, 1.9281, 0, 0, 0, 0, 0, 0], - [0, 2.07933, 38.632, 18.3748, 43.07305, -61.557, 26.005, -73.637, 0, 0, 0, 0, 0, 0], - [0, -7.56, -140.480, -66.81, -156.6293, 223.84, -94.564, 267.7734, 0, 0, 0, 0, 0, 0]]), - ([np.arange(11), 20, 4, [0, .25, .5, .75, 1, 1.25, 1.5, 1.75, 2, 2.25, 2.5], .987], - [[0, .2533, .5066, .7599, 1.0132, 1.2665, 1.5198, 1.7730, 2.0263, 2.2796, 0], - [0, -.2566, -.5132, -.7699, -1.0265, -1.2831, -1.5398, -1.7964, -2.0530, -2.3097, 0], - [0, .5200, 1.0400, 1.56005, 2.08007, 2.6000, 3.1201, 3.6401, 4.1601, 4.6801, 0]]), - ([np.arange(9), 15, 3, [-1, -2, -3, -4, -5, -6, -7, -8, -9], -0.4], - [[-1, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0]]) - ] -@pytest.mark.parametrize('tas', tas) +tas = [ + ( + [np.arange(10), 3, 0, [6.55, 0.357, 8.49, 9.33, 6.78, 7.57, 7.43, 3.92, 6.55, 1.71], 0.25], + [ + [6.55, 6.78, 6.55, 0, 0, 0, 0, 0, 0, 0], + [0, 14.07893122, 35.36478086, 0, 0, 0, 0, 0, 0, 0], + [0, -19.92049156, 11.6931482, 0, 0, 0, 0, 0, 0, 0], + ], + ), + ( + [np.arange(5), 10, 0, [-11.47, -10.688, -8.095, -29.44, 14.38], 1.25], + [ + [-11.47, -10.8444, -9.1322, -16.633, -20.6760], + [0, -0.50048, -3.31904, 40.9824, -112.1792], + [0, 0.800768, 5.310464, -65.57184, 179.48672], + ], + ), + ( + [np.arange(5), 2, 0, [-11.47, -10.688, -8.095, -29.44, 14.38], 0.88], + [ + [-11.47, -10.3344, -13.9164, -11.5136, 0], + [0, -3.3484, 55.1265, -169.7572, 0], + [0, 7.609997, -125.2876, 385.81189, 0], + ], + ), + ( + [np.arange(10), 1, 2, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], 0.88], + [ + [1, 2.1364, 3.2727, 4.4091, 5.5455, 6.6818, 7.8182, 8.9545, 0, 0], + [0, -1.29, -2.58, -3.87, -5.165, -6.45, -7.74, -9.039, 0, 0], + [0, 2.93, 5.869, 8.084, 11.739, 14.674, 17.608, 20.5437, 0, 0], + ], + ), + ( + [ + np.arange(14), + 100, + 3, + [ + -2.9384, + -1.4623, + -2.0913, + 4.6304, + -1.2127, + 1.4737, + -0.3791, + 1.7506, + -1.5068, + -2.7625, + 0.9617, + -0.3494, + -0.3862, + 2.7960, + ], + 0.55, + ], + [ + [-2.9384, -1.9769, 0.9121, 0.6314, 0.8622, -2.4239, -0.2302, 1.9281, 0, 0, 0, 0, 0, 0], + [0, 2.07933, 38.632, 18.3748, 43.07305, -61.557, 26.005, -73.637, 0, 0, 0, 0, 0, 0], + [0, -7.56, -140.480, -66.81, -156.6293, 223.84, -94.564, 267.7734, 0, 0, 0, 0, 0, 0], + ], + ), + ( + [np.arange(11), 20, 4, [0, 0.25, 0.5, 0.75, 1, 1.25, 1.5, 1.75, 2, 2.25, 2.5], 0.987], + [ + [0, 0.2533, 0.5066, 0.7599, 1.0132, 1.2665, 1.5198, 1.7730, 2.0263, 2.2796, 0], + [0, -0.2566, -0.5132, -0.7699, -1.0265, -1.2831, -1.5398, -1.7964, -2.0530, -2.3097, 0], + [0, 0.5200, 1.0400, 1.56005, 2.08007, 2.6000, 3.1201, 3.6401, 4.1601, 4.6801, 0], + ], + ), + ( + [np.arange(9), 15, 3, [-1, -2, -3, -4, -5, -6, -7, -8, -9], -0.4], + [[-1, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0]], + ), +] + + +@pytest.mark.parametrize("tas", tas) def test_apply_stretch(tas): component = ComponentSignal(tas[0][0], tas[0][1], tas[0][2]) component.iq = tas[0][3] @@ -37,14 +89,17 @@ def test_apply_stretch(tas): np.testing.assert_allclose(actual, expected, rtol=1e-01) -taw = [([np.arange(5), 2, 0, [0, 1, 2, 3, 4], .5], [0, .5, 1, 1.5, 2]), - ([np.arange(5), 20, 2, [0, -1, -2, -3, -4], .25], [0, -.25, -.5, -.75, -1]), - ([np.arange(40), 200, 4, np.arange(0, 10, .25), .3], np.arange(0, 10, .25) * .3), - ([np.arange(1), 10, 2, [10.5, 11.5, -10.5], 0], [0, 0, 0]), - ([[-12, -10, -15], 5, 2, [-.5, -1, -1.2], .9], [-.45, -.9, -1.08]), - ([[-12, -10, -15], 5, 2, [0, 0, 0], .9], [0, 0, 0]) - ] -@pytest.mark.parametrize('taw', taw) +taw = [ + ([np.arange(5), 2, 0, [0, 1, 2, 3, 4], 0.5], [0, 0.5, 1, 1.5, 2]), + ([np.arange(5), 20, 2, [0, -1, -2, -3, -4], 0.25], [0, -0.25, -0.5, -0.75, -1]), + ([np.arange(40), 200, 4, np.arange(0, 10, 0.25), 0.3], np.arange(0, 10, 0.25) * 0.3), + ([np.arange(1), 10, 2, [10.5, 11.5, -10.5], 0], [0, 0, 0]), + ([[-12, -10, -15], 5, 2, [-0.5, -1, -1.2], 0.9], [-0.45, -0.9, -1.08]), + ([[-12, -10, -15], 5, 2, [0, 0, 0], 0.9], [0, 0, 0]), +] + + +@pytest.mark.parametrize("taw", taw) def test_apply_weight(taw): component = ComponentSignal(taw[0][0], taw[0][1], taw[0][2]) component.iq = np.array(taw[0][3]) diff --git a/diffpy/snmf/tests/test_factorizers.py b/diffpy/snmf/tests/test_factorizers.py index 65aa0ac2..a0c45467 100644 --- a/diffpy/snmf/tests/test_factorizers.py +++ b/diffpy/snmf/tests/test_factorizers.py @@ -4,17 +4,17 @@ from diffpy.snmf.factorizers import lsqnonneg tl = [ - ([[[1, 0], [1, 0], [0, 1]], [2, 1, 1]], [1.5, 1.]), + ([[[1, 0], [1, 0], [0, 1]], [2, 1, 1]], [1.5, 1.0]), ([[[2, 3], [1, 2], [0, 0]], [7, 7, 2]], [0, 2.6923]), - ([[[3, 2, 4, 1]], [3.2]], [0, 0, .8, 0]), - ([[[-.4, 0], [0, 0], [-9, -18]], [-2, -3, -4.9]], [.5532, 0]), - ([[[-.1, -.2], [-.8, -.9]], [0, 0]], [0, 0]), + ([[[3, 2, 4, 1]], [3.2]], [0, 0, 0.8, 0]), + ([[[-0.4, 0], [0, 0], [-9, -18]], [-2, -3, -4.9]], [0.5532, 0]), + ([[[-0.1, -0.2], [-0.8, -0.9]], [0, 0]], [0, 0]), ([[[0, 0], [0, 0]], [10, 10]], [0, 0]), - ([[[2], [1], [-4], [-.3]], [6, 4, .33, -5]], 0.767188240872451), + ([[[2], [1], [-4], [-0.3]], [6, 4, 0.33, -5]], 0.767188240872451), ] -@pytest.mark.parametrize('tl', tl) +@pytest.mark.parametrize("tl", tl) def test_lsqnonneg(tl): actual = lsqnonneg(tl[0][0], tl[0][1]) expected = tl[1] diff --git a/diffpy/snmf/tests/test_optimizers.py b/diffpy/snmf/tests/test_optimizers.py index e686250e..25d90e73 100644 --- a/diffpy/snmf/tests/test_optimizers.py +++ b/diffpy/snmf/tests/test_optimizers.py @@ -5,15 +5,15 @@ ([[[1, 0], [0, 1]], [1, 1], [0, 0], [1, 1]], [0, 0]), ([[[1, 0], [0, 1]], [1, 1], -1, 1], [-1, -1]), ([[[1.75, 0], [0, 1.5]], [1, 1.2], -1, 1], [-0.571428571428571, -0.8]), - ([[[.75, .2], [.2, .75]], [-.1, -.2], -1, 1], [0.066985645933014, 0.248803827751196]), + ([[[0.75, 0.2], [0.2, 0.75]], [-0.1, -0.2], -1, 1], [0.066985645933014, 0.248803827751196]), ([[[2, -1, 0], [-1, 2, -1], [0, -1, 2]], [1, 1, 1], -10, 12], [-1.5, -2, -1.5]), ([[[2, -1, 0], [-1, 2, -1], [0, -1, 2]], [1, -1, -1], -10, 12], [0, 1, 1]), - ([[[4, 0, 0, 0], [0, 3, 0, 0], [0, 0, 2, 0], [0, 0, 0, 1]], [-2, -3, -4, -1], 0, 1000], [.5, 1, 2, 1]), + ([[[4, 0, 0, 0], [0, 3, 0, 0], [0, 0, 2, 0], [0, 0, 0, 1]], [-2, -3, -4, -1], 0, 1000], [0.5, 1, 2, 1]), ] -@pytest.mark.parametrize('tm', tm) +@pytest.mark.parametrize("tm", tm) def test_get_weights(tm): expected = tm[1] actual = get_weights(tm[0][0], tm[0][1], tm[0][2], tm[0][3]) - assert (actual == pytest.approx(expected, rel=1e-4, abs=1e-6)) + assert actual == pytest.approx(expected, rel=1e-4, abs=1e-6) diff --git a/diffpy/snmf/tests/test_polynomials.py b/diffpy/snmf/tests/test_polynomials.py index a75baca1..6078b26f 100644 --- a/diffpy/snmf/tests/test_polynomials.py +++ b/diffpy/snmf/tests/test_polynomials.py @@ -15,7 +15,7 @@ ([-9, 3], 2.8169), ([[2, 2], 2], [0, 0]), ([[[2, 2], [2, 2]], 2], [[0, 0], [0, 0]]), - ([[[[3, 2], [-2, -2], [100, 0]]], 2], [[[0, 0], [0, 0], [0, 0]]]) + ([[[[3, 2], [-2, -2], [100, 0]]], 2], [[[0, 0], [0, 0], [0, 0]]]), ] diff --git a/diffpy/snmf/tests/test_subroutines.py b/diffpy/snmf/tests/test_subroutines.py index 1a93971d..bdb721fe 100644 --- a/diffpy/snmf/tests/test_subroutines.py +++ b/diffpy/snmf/tests/test_subroutines.py @@ -1,13 +1,25 @@ import pytest import numpy as np from diffpy.snmf.containers import ComponentSignal -from diffpy.snmf.subroutines import objective_function, get_stretched_component, reconstruct_data, get_residual_matrix, \ - update_weights_matrix, initialize_arrays, lift_data, initialize_components, construct_stretching_matrix, \ - construct_component_matrix, construct_weight_matrix, update_weights, reconstruct_signal +from diffpy.snmf.subroutines import ( + objective_function, + get_stretched_component, + reconstruct_data, + get_residual_matrix, + update_weights_matrix, + initialize_arrays, + lift_data, + initialize_components, + construct_stretching_matrix, + construct_component_matrix, + construct_weight_matrix, + update_weights, + reconstruct_signal, +) to = [ ([[[1, 2], [3, 4]], [[5, 6], [7, 8]], 1e11, [[1, 2], [3, 4]], [[1, 2], [3, 4]], 1], 2.574e14), - ([[[11, 2], [31, 4]], [[5, 63], [7, 18]], .001, [[21, 2], [3, 4]], [[11, 22], [3, 40]], 1], 650.4576), + ([[[11, 2], [31, 4]], [[5, 63], [7, 18]], 0.001, [[21, 2], [3, 4]], [[11, 22], [3, 40]], 1], 650.4576), ([[[1, 2], [3, 4]], [[5, 6], [7, 8]], 1e11, [[1, 2], [3, 4]], [[1, 2], [3, 4]], 0], 2.574e14), ] @@ -20,31 +32,77 @@ def test_objective_function(to): tgso = [ - ([.25, [6.55, .357, 8.49, 9.33, 6.78, 7.57, 7.43, 3.92, 6.55, 1.71], 10], ( - [6.55, 6.78, 6.55, 0, 0, 0, 0, 0, 0, 0], [0, 14.07893122, 35.36478086, 0, 0, 0, 0, 0, 0, 0], - [0, -19.92049156, 11.6931482, 0, 0, 0, 0, 0, 0, 0])), - ([1.25, [-11.47, -10.688, -8.095, -29.44, 14.38], 5], ( - [-11.47, -10.8444, -9.1322, -16.633, -20.6760], [0, -.50048, -3.31904, 40.9824, -112.1792], - [0, .800768, 5.310464, -65.57184, 179.48672])), - ([.88, [-11.47, -10.688, -8.095, -29.44, 14.38], 5], ( - [-11.47, -10.3344, -13.9164, -11.5136, 0], [0, -3.3484, 55.1265, -169.7572, 0], - [0, 7.609997, -125.2876, 385.81189, 0])), - ( - [.88, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], 10], - ([1, 2.1364, 3.2727, 4.4091, 5.5455, 6.6818, 7.8182, 8.9545, 0, 0], - [0, -1.29, -2.58, -3.87, -5.165, -6.45, -7.74, -9.039, 0, 0], - [0, 2.93, 5.869, 8.084, 11.739, 14.674, 17.608, 20.5437, 0, 0])), - ([.55, - [-2.9384, -1.4623, -2.0913, 4.6304, -1.2127, 1.4737, -0.3791, 1.7506, -1.5068, -2.7625, .9617, -.3494, -.3862, - 2.7960], 14], ([-2.9384, -1.9769, 0.9121, .6314, .8622, -2.4239, -.2302, 1.9281, 0, 0, 0, 0, 0, 0], - [0, 2.07933, 38.632, 18.3748, 43.07305, -61.557, 26.005, -73.637, 0, 0, 0, 0, 0, 0], - [0, -7.56, -140.480, -66.81, -156.6293, 223.84, -94.564, 267.7734, 0, 0, 0, 0, 0, 0])), - ([.987, [0, .25, .5, .75, 1, 1.25, 1.5, 1.75, 2, 2.25, 2.5], 11], - ([0, .2533, .5066, .7599, 1.0132, 1.2665, 1.5198, 1.7730, 2.0263, 2.2796, 0], - [0, -.2566, -.5132, -.7699, -1.0265, -1.2831, -1.5398, -1.7964, -2.0530, -2.3097, 0], - [0, .5200, 1.0400, 1.56005, 2.08007, 2.6000, 3.1201, 3.6401, 4.1601, 4.6801, 0])), - ([-0.4, [-1, -2, -3, -4, -5, -6, -7, -8, -9], 9], - ([-1, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0])) + ( + [0.25, [6.55, 0.357, 8.49, 9.33, 6.78, 7.57, 7.43, 3.92, 6.55, 1.71], 10], + ( + [6.55, 6.78, 6.55, 0, 0, 0, 0, 0, 0, 0], + [0, 14.07893122, 35.36478086, 0, 0, 0, 0, 0, 0, 0], + [0, -19.92049156, 11.6931482, 0, 0, 0, 0, 0, 0, 0], + ), + ), + ( + [1.25, [-11.47, -10.688, -8.095, -29.44, 14.38], 5], + ( + [-11.47, -10.8444, -9.1322, -16.633, -20.6760], + [0, -0.50048, -3.31904, 40.9824, -112.1792], + [0, 0.800768, 5.310464, -65.57184, 179.48672], + ), + ), + ( + [0.88, [-11.47, -10.688, -8.095, -29.44, 14.38], 5], + ( + [-11.47, -10.3344, -13.9164, -11.5136, 0], + [0, -3.3484, 55.1265, -169.7572, 0], + [0, 7.609997, -125.2876, 385.81189, 0], + ), + ), + ( + [0.88, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], 10], + ( + [1, 2.1364, 3.2727, 4.4091, 5.5455, 6.6818, 7.8182, 8.9545, 0, 0], + [0, -1.29, -2.58, -3.87, -5.165, -6.45, -7.74, -9.039, 0, 0], + [0, 2.93, 5.869, 8.084, 11.739, 14.674, 17.608, 20.5437, 0, 0], + ), + ), + ( + [ + 0.55, + [ + -2.9384, + -1.4623, + -2.0913, + 4.6304, + -1.2127, + 1.4737, + -0.3791, + 1.7506, + -1.5068, + -2.7625, + 0.9617, + -0.3494, + -0.3862, + 2.7960, + ], + 14, + ], + ( + [-2.9384, -1.9769, 0.9121, 0.6314, 0.8622, -2.4239, -0.2302, 1.9281, 0, 0, 0, 0, 0, 0], + [0, 2.07933, 38.632, 18.3748, 43.07305, -61.557, 26.005, -73.637, 0, 0, 0, 0, 0, 0], + [0, -7.56, -140.480, -66.81, -156.6293, 223.84, -94.564, 267.7734, 0, 0, 0, 0, 0, 0], + ), + ), + ( + [0.987, [0, 0.25, 0.5, 0.75, 1, 1.25, 1.5, 1.75, 2, 2.25, 2.5], 11], + ( + [0, 0.2533, 0.5066, 0.7599, 1.0132, 1.2665, 1.5198, 1.7730, 2.0263, 2.2796, 0], + [0, -0.2566, -0.5132, -0.7699, -1.0265, -1.2831, -1.5398, -1.7964, -2.0530, -2.3097, 0], + [0, 0.5200, 1.0400, 1.56005, 2.08007, 2.6000, 3.1201, 3.6401, 4.1601, 4.6801, 0], + ), + ), + ( + [-0.4, [-1, -2, -3, -4, -5, -6, -7, -8, -9], 9], + ([-1, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0]), + ), ] @@ -55,139 +113,279 @@ def test_get_stretched_component(tgso): np.testing.assert_allclose(actual, expected, rtol=1e-01) -tuwm = [([2, 2, [[.5, .6], [.7, .8]], [[1, 2], [4, 8]], [[1.6, 2.8], [5, 8.8]], 2, [[.78, .12], [.5, .5]], None], - [[0, 1], [1, 1]]), - ([2, 3, [[.5], [.5]], [[1, 2.5], [1.5, 3], [2, 3.5]], [[1, 2], [3, 4], [5, 6]], 1, [[.5], [.5]], None], - [[1], [.1892]]), - ([2, 3, [[.5, .6, .7], [.5, .6, .7]], [[1, 2.5], [1.5, 3], [2, 3.5]], [[1, 2, 3], [3, 4, 5], [5, 6, 7]], 3, - [[.5, .45, .4], [.5, .45, .4]], None], [[1, 1, 1], [.1892, .5600, .938]]), - ([3, 3, [[.7, .8, .9], [.71, .72, .73], [.8, .85, .9]], [[-1, -2.7, -3], [-11, -6, -5.1], [0, -1, -.5]], - [[-2, -3, -4], [-9, -5, -5], [0, -2, -1]], 3, [[.9, .4, .5], [1, 0, .4], [0, 0, .98]], None], - [[1, .0651, 0], [.5848, .0381, .1857], [0, 1, 1]]), - ([2, 2, [[.5], [.5]], [[0, 0], [0, 0]], [[0, 0], [0, 0]], 1, [[.6], [.4]], 'align'], [[0], [0]]), - ([1, 3, [[.5, .3]], [[1], [1.1], [1.3]], [[1, 2], [2, 3], [3, 2]], 2, [[.6, .4]], None], [[1, 1]]), - ([2, 2, [[.5, .6], [.7, .8]], [[1, 2], [4, 8]], [[1.6, 2.8], [5, 8.8]], 2, [[.78, .12], [.5, .5]], 'align'], - [[0, 0], [1.0466, 1.46]]), - ([2, 3, [[.5], [.5]], [[1, 2.5], [1.5, 3], [2, 3.5]], [[1, 2], [3, 4], [5, 6]], 1, [[.5], [.5]], 'align'], - [[1.4], [0]]), - ([3, 3, [[.7, .8, .9], [.71, .72, .73], [.8, .85, .9]], [[-1, -2.7, -3], [-11, -6, -5.1], [0, -1, -.5]], - [[-2, -3, -4], [-9, -5, -5], [0, -2, -1]], 3, [[.9, .4, .5], [1, 0, .4], [0, 0, .98]], 'align'], - [[1.2605, .0552, 0], [.2723, 0, 0], [0, 1.0538, 1.1696]]), - ([2, 2, [[.5], [.5]], [[0, 0], [0, 0]], [[0, 0], [0, 0]], 1, [[.6], [.4]], 'align'], [[0], [0]]), - ([1, 3, [[.5, .3]], [[1], [1.1], [1.3]], [[1, 2], [2, 3], [3, 2]], 2, [[.6, .4]], 'align'], [[1.3383, 2]]) - ] +tuwm = [ + ( + [ + 2, + 2, + [[0.5, 0.6], [0.7, 0.8]], + [[1, 2], [4, 8]], + [[1.6, 2.8], [5, 8.8]], + 2, + [[0.78, 0.12], [0.5, 0.5]], + None, + ], + [[0, 1], [1, 1]], + ), + ( + [2, 3, [[0.5], [0.5]], [[1, 2.5], [1.5, 3], [2, 3.5]], [[1, 2], [3, 4], [5, 6]], 1, [[0.5], [0.5]], None], + [[1], [0.1892]], + ), + ( + [ + 2, + 3, + [[0.5, 0.6, 0.7], [0.5, 0.6, 0.7]], + [[1, 2.5], [1.5, 3], [2, 3.5]], + [[1, 2, 3], [3, 4, 5], [5, 6, 7]], + 3, + [[0.5, 0.45, 0.4], [0.5, 0.45, 0.4]], + None, + ], + [[1, 1, 1], [0.1892, 0.5600, 0.938]], + ), + ( + [ + 3, + 3, + [[0.7, 0.8, 0.9], [0.71, 0.72, 0.73], [0.8, 0.85, 0.9]], + [[-1, -2.7, -3], [-11, -6, -5.1], [0, -1, -0.5]], + [[-2, -3, -4], [-9, -5, -5], [0, -2, -1]], + 3, + [[0.9, 0.4, 0.5], [1, 0, 0.4], [0, 0, 0.98]], + None, + ], + [[1, 0.0651, 0], [0.5848, 0.0381, 0.1857], [0, 1, 1]], + ), + ([2, 2, [[0.5], [0.5]], [[0, 0], [0, 0]], [[0, 0], [0, 0]], 1, [[0.6], [0.4]], "align"], [[0], [0]]), + ([1, 3, [[0.5, 0.3]], [[1], [1.1], [1.3]], [[1, 2], [2, 3], [3, 2]], 2, [[0.6, 0.4]], None], [[1, 1]]), + ( + [ + 2, + 2, + [[0.5, 0.6], [0.7, 0.8]], + [[1, 2], [4, 8]], + [[1.6, 2.8], [5, 8.8]], + 2, + [[0.78, 0.12], [0.5, 0.5]], + "align", + ], + [[0, 0], [1.0466, 1.46]], + ), + ( + [ + 2, + 3, + [[0.5], [0.5]], + [[1, 2.5], [1.5, 3], [2, 3.5]], + [[1, 2], [3, 4], [5, 6]], + 1, + [[0.5], [0.5]], + "align", + ], + [[1.4], [0]], + ), + ( + [ + 3, + 3, + [[0.7, 0.8, 0.9], [0.71, 0.72, 0.73], [0.8, 0.85, 0.9]], + [[-1, -2.7, -3], [-11, -6, -5.1], [0, -1, -0.5]], + [[-2, -3, -4], [-9, -5, -5], [0, -2, -1]], + 3, + [[0.9, 0.4, 0.5], [1, 0, 0.4], [0, 0, 0.98]], + "align", + ], + [[1.2605, 0.0552, 0], [0.2723, 0, 0], [0, 1.0538, 1.1696]], + ), + ([2, 2, [[0.5], [0.5]], [[0, 0], [0, 0]], [[0, 0], [0, 0]], 1, [[0.6], [0.4]], "align"], [[0], [0]]), + ([1, 3, [[0.5, 0.3]], [[1], [1.1], [1.3]], [[1, 2], [2, 3], [3, 2]], 2, [[0.6, 0.4]], "align"], [[1.3383, 2]]), +] -@pytest.mark.parametrize('tuwm', tuwm) +@pytest.mark.parametrize("tuwm", tuwm) def test_update_weights_matrix(tuwm): - actual = update_weights_matrix(tuwm[0][0], tuwm[0][1], tuwm[0][2], tuwm[0][3], tuwm[0][4], tuwm[0][5], tuwm[0][6], - tuwm[0][7]) + actual = update_weights_matrix( + tuwm[0][0], tuwm[0][1], tuwm[0][2], tuwm[0][3], tuwm[0][4], tuwm[0][5], tuwm[0][6], tuwm[0][7] + ) expected = tuwm[1] - np.testing.assert_allclose(actual, expected, rtol=1e-03, atol=.5) + np.testing.assert_allclose(actual, expected, rtol=1e-03, atol=0.5) tgrm = [ - ([[[1, 2], [3, 4]], [[.25], [.75]], [[.9], [.7]], [[11, 22], [33, 44]], 1, 2, 2], [[-9, -22], [-33, -44]]), + ([[[1, 2], [3, 4]], [[0.25], [0.75]], [[0.9], [0.7]], [[11, 22], [33, 44]], 1, 2, 2], [[-9, -22], [-33, -44]]), ([[[1, 2], [3, 4]], [[1], [1]], [[1], [1]], [[11, 22], [33, 44]], 1, 2, 2], [[-8, -22], [-26, -44]]), - ([[[1.1, 4.4], [1.2, 4.5], [14, 7.8]], [[.4, .6], [.75, .25]], [[.9, .89], [.98, .88]], - [[10, 20], [-10.5, -20.6], [0.6, .9]], 2, 2, 3], [[-6.26, -18.24], [14.9744, 23.5067], [-.6, -.9]]), + ( + [ + [[1.1, 4.4], [1.2, 4.5], [14, 7.8]], + [[0.4, 0.6], [0.75, 0.25]], + [[0.9, 0.89], [0.98, 0.88]], + [[10, 20], [-10.5, -20.6], [0.6, 0.9]], + 2, + 2, + 3, + ], + [[-6.26, -18.24], [14.9744, 23.5067], [-0.6, -0.9]], + ), # positive float - ([[[-1.1, -4.4], [-1.2, -4.5], [-14, -7.8]], [[.4, .6], [.75, .25]], [[.9, .89], [.98, .88]], - [[10, 20], [-10.5, -20.6], [0.6, .9]], 2, 2, 3], [[-13.74, -21.76], [6.0256, 17.6933], [-.6, -.9]]), + ( + [ + [[-1.1, -4.4], [-1.2, -4.5], [-14, -7.8]], + [[0.4, 0.6], [0.75, 0.25]], + [[0.9, 0.89], [0.98, 0.88]], + [[10, 20], [-10.5, -20.6], [0.6, 0.9]], + 2, + 2, + 3, + ], + [[-13.74, -21.76], [6.0256, 17.6933], [-0.6, -0.9]], + ), # negative floats - ([[[0, 0, 0, 0], [0, 0, 0, 0]], [[.4], [.2], [.3], [.3]], [[.9], [.9], [.9], [.9]], [[0, 0, 0, 0], [0, 0, 0, 0]], 1, - 4, 2], [[0, 0, 0, 0], [0, 0, 0, 0]]) + ( + [ + [[0, 0, 0, 0], [0, 0, 0, 0]], + [[0.4], [0.2], [0.3], [0.3]], + [[0.9], [0.9], [0.9], [0.9]], + [[0, 0, 0, 0], [0, 0, 0, 0]], + 1, + 4, + 2, + ], + [[0, 0, 0, 0], [0, 0, 0, 0]], + ), ] -@pytest.mark.parametrize('tgrm', tgrm) +@pytest.mark.parametrize("tgrm", tgrm) def test_get_residual_matrix(tgrm): - actual = get_residual_matrix(tgrm[0][0], tgrm[0][1], tgrm[0][2], tgrm[0][3], tgrm[0][4], tgrm[0][5], tgrm[0][6]) + actual = get_residual_matrix( + tgrm[0][0], tgrm[0][1], tgrm[0][2], tgrm[0][3], tgrm[0][4], tgrm[0][5], tgrm[0][6] + ) expected = tgrm[1] np.testing.assert_allclose(actual, expected, rtol=1e-04) trd = [ - ([ComponentSignal([0, .25, .5, .75, 1], 2, 0), ComponentSignal([0, .25, .5, .75, 1], 2, 1), - ComponentSignal([0, .25, .5, .75, 1], 2, 2)]), - ([ComponentSignal([0, .25, .5, .75, 1], 2, 0)]), - ([ComponentSignal([0, .25, .5, .75, 1], 2, 0), ComponentSignal([0, .25, .5, .75, 1], 2, 1), - ComponentSignal([0, .25, .5, .75, 1], 2, 2), ComponentSignal([0, .25, .5, .75, 1], 2, 3), - ComponentSignal([0, .25, .5, .75, 1], 2, 4)]), - #([]) # Exception expected + ( + [ + ComponentSignal([0, 0.25, 0.5, 0.75, 1], 2, 0), + ComponentSignal([0, 0.25, 0.5, 0.75, 1], 2, 1), + ComponentSignal([0, 0.25, 0.5, 0.75, 1], 2, 2), + ] + ), + ([ComponentSignal([0, 0.25, 0.5, 0.75, 1], 2, 0)]), + ( + [ + ComponentSignal([0, 0.25, 0.5, 0.75, 1], 2, 0), + ComponentSignal([0, 0.25, 0.5, 0.75, 1], 2, 1), + ComponentSignal([0, 0.25, 0.5, 0.75, 1], 2, 2), + ComponentSignal([0, 0.25, 0.5, 0.75, 1], 2, 3), + ComponentSignal([0, 0.25, 0.5, 0.75, 1], 2, 4), + ] + ), + # ([]) # Exception expected ] -@pytest.mark.parametrize('trd', trd) + + +@pytest.mark.parametrize("trd", trd) def test_reconstruct_data(trd): actual = reconstruct_data(trd) - assert actual.shape == (len(trd[0].iq),len(trd[0].weights)) + assert actual.shape == (len(trd[0].iq), len(trd[0].weights)) print(actual) -tld = [(([[[1, -1, 1], [0, 0, 0], [2, 10, -3]], 1]), ([[4, 2, 4], [3, 3, 3], [5, 13, 0]])), - (([[[1, -1, 1], [0, 0, 0], [2, 10, -3]], 0]), ([[1, -1, 1], [0, 0, 0], [2, 10, -3]])), - (([[[1, -1, 1], [0, 0, 0], [2, 10, -3]], .5]), ([[2.5, .5, 2.5], [1.5, 1.5, 1.5], [3.5, 11.5, -1.5]])), - (([[[1, -1, 1], [0, 0, 0], [2, 10, -3]], -1]), ([[4, 2, 4], [3, 3, 3], [5, 13, 0]])), - (([[[0, 0, 0], [0, 0, 0], [0, 0, 0]], 100]), ([[0, 0, 0], [0, 0, 0], [0, 0, 0]])), - (([[[1.5, 2], [10.5, 1], [0.5, 2]], 1]), ([[2, 2.5], [11, 1.5], [1, 2.5]])), - (([[[-10, -10.5], [-12.2, -12.2], [0, 0]], 1]), ([[2.2, 1.7], [0, 0], [12.2, 12.2]])), - ] +tld = [ + (([[[1, -1, 1], [0, 0, 0], [2, 10, -3]], 1]), ([[4, 2, 4], [3, 3, 3], [5, 13, 0]])), + (([[[1, -1, 1], [0, 0, 0], [2, 10, -3]], 0]), ([[1, -1, 1], [0, 0, 0], [2, 10, -3]])), + (([[[1, -1, 1], [0, 0, 0], [2, 10, -3]], 0.5]), ([[2.5, 0.5, 2.5], [1.5, 1.5, 1.5], [3.5, 11.5, -1.5]])), + (([[[1, -1, 1], [0, 0, 0], [2, 10, -3]], -1]), ([[4, 2, 4], [3, 3, 3], [5, 13, 0]])), + (([[[0, 0, 0], [0, 0, 0], [0, 0, 0]], 100]), ([[0, 0, 0], [0, 0, 0], [0, 0, 0]])), + (([[[1.5, 2], [10.5, 1], [0.5, 2]], 1]), ([[2, 2.5], [11, 1.5], [1, 2.5]])), + (([[[-10, -10.5], [-12.2, -12.2], [0, 0]], 1]), ([[2.2, 1.7], [0, 0], [12.2, 12.2]])), +] -@pytest.mark.parametrize('tld', tld) +@pytest.mark.parametrize("tld", tld) def test_lift_data(tld): actual = lift_data(tld[0][0], tld[0][1]) expected = tld[1] np.testing.assert_allclose(actual, expected) -tcc = [(2, 3,[0, .5, 1, 1.5]), # Regular usage - #(0, 3,[0, .5, 1, 1.5]), # Zero components raise an exception. Not tested - ] -@pytest.mark.parametrize('tcc', tcc) + +tcc = [ + (2, 3, [0, 0.5, 1, 1.5]), # Regular usage + # (0, 3,[0, .5, 1, 1.5]), # Zero components raise an exception. Not tested +] + + +@pytest.mark.parametrize("tcc", tcc) def test_initialize_components(tcc): actual = initialize_components(tcc[0], tcc[1], tcc[2]) assert len(actual) == tcc[0] assert len(actual[0].weights) == tcc[1] assert (actual[0].grid == np.array(tcc[2])).all() -tcso =[([ComponentSignal([0,.5,1,1.5],20,0)],1,20), - ([ComponentSignal([0,.5,1,1.5],20,0)],4,20), - # ([ComponentSignal([0,.5,1,1.5],20,0)],0,20), # Raises an exception - # ([ComponentSignal([0,.5,1,1.5],20,0)],-2,20), # Raises an exception - # ([ComponentSignal([0,.5,1,1.5],20,0)],1,0), # Raises an Exception - # ([ComponentSignal([0,.5,1,1.5],20,0)],1,-3), # Raises an exception - ([ComponentSignal([0,.5,1,1.5],20,0),ComponentSignal([0,.5,1,1.5],20,1)],2,20), - ([ComponentSignal([0,.5,1,1.5],20,0),ComponentSignal([0,.5,1,21.5],20,1)],2,20), - ([ComponentSignal([0,1,1.5],20,0),ComponentSignal([0,.5,1,21.5],20,1)],2,20), - # ([ComponentSignal([0,.5,1,1.5],20,0),ComponentSignal([0,.5,1,1.5],20,1)],1,-3), # Negative signal length. Raises an exception - #([],1,20), # Empty components. Raises an Exception - #([],-1,20), # Empty components with negative number of components. Raises an exception - #([],0,20), # Empty components with zero number of components. Raises an exception - #([],1,0), # Empty components with zero signal length. Raises an exception. - #([],-1,-2), # Empty components with negative number of components and signal length Raises an exception. +tcso = [ + ([ComponentSignal([0, 0.5, 1, 1.5], 20, 0)], 1, 20), + ([ComponentSignal([0, 0.5, 1, 1.5], 20, 0)], 4, 20), + # ([ComponentSignal([0,.5,1,1.5],20,0)],0,20), # Raises an exception + # ([ComponentSignal([0,.5,1,1.5],20,0)],-2,20), # Raises an exception + # ([ComponentSignal([0,.5,1,1.5],20,0)],1,0), # Raises an Exception + # ([ComponentSignal([0,.5,1,1.5],20,0)],1,-3), # Raises an exception + ([ComponentSignal([0, 0.5, 1, 1.5], 20, 0), ComponentSignal([0, 0.5, 1, 1.5], 20, 1)], 2, 20), + ([ComponentSignal([0, 0.5, 1, 1.5], 20, 0), ComponentSignal([0, 0.5, 1, 21.5], 20, 1)], 2, 20), + ([ComponentSignal([0, 1, 1.5], 20, 0), ComponentSignal([0, 0.5, 1, 21.5], 20, 1)], 2, 20), + # ([ComponentSignal([0,.5,1,1.5],20,0),ComponentSignal([0,.5,1,1.5],20,1)],1,-3), # Negative signal length. Raises an exception + # ([],1,20), # Empty components. Raises an Exception + # ([],-1,20), # Empty components with negative number of components. Raises an exception + # ([],0,20), # Empty components with zero number of components. Raises an exception + # ([],1,0), # Empty components with zero signal length. Raises an exception. + # ([],-1,-2), # Empty components with negative number of components and signal length Raises an exception. ] -@pytest.mark.parametrize('tcso',tcso) + + +@pytest.mark.parametrize("tcso", tcso) def test_construct_stretching_matrix(tcso): - actual = construct_stretching_matrix(tcso[0],tcso[1],tcso[2]) + actual = construct_stretching_matrix(tcso[0], tcso[1], tcso[2]) for component in tcso[0]: - np.testing.assert_allclose(actual[component.id,:], component.stretching_factors) - #assert actual[component.id, :] == component.stretching_factors + np.testing.assert_allclose(actual[component.id, :], component.stretching_factors) + # assert actual[component.id, :] == component.stretching_factors + tccm = [ - ([ComponentSignal([0,.25,.5,.75,1],20,0)]), - ([ComponentSignal([0,.25,.5,.75,1],0,0)]), - ([ComponentSignal([0,.25,.5,.75,1],20,0),ComponentSignal([0,.25,.5,.75,1],20,1),ComponentSignal([0,.25,.5,.75,1],20,2)]), - ([ComponentSignal([0, .25, .5, .75, 1], 20, 0), ComponentSignal([0, .25, .5, .75, 1], 20, 1), - ComponentSignal([0, .25, .5, .75, 1], 20, 2)]), - ([ComponentSignal([0, .25, .5, .75, 1], 20, 0), ComponentSignal([0, .25, .5, 2.75, 1], 20, 1), - ComponentSignal([0, .25, .5, .75, 1], 20, 2)]), - ([ComponentSignal([.25], 20, 0), ComponentSignal([.25], 20, 1), ComponentSignal([.25], 20, 2)]), - ([ComponentSignal([0, .25, .5, .75, 1], 20, 0), ComponentSignal([0, .25, .5, .75, 1], 20, 1)]), - # ([ComponentSignal([[0, .25, .5, .75, 1],[0, .25, .5, .75, 1]], 20, 0), ComponentSignal([[0, .25, .5, .75, 1],[0, .25, .5, .75, 1]], 20, 1)]), # iq is multidimensional. Expected to fail - # (ComponentSignal([], 20, 0)), # Expected to fail - # ([]), #Expected to fail + ([ComponentSignal([0, 0.25, 0.5, 0.75, 1], 20, 0)]), + ([ComponentSignal([0, 0.25, 0.5, 0.75, 1], 0, 0)]), + ( + [ + ComponentSignal([0, 0.25, 0.5, 0.75, 1], 20, 0), + ComponentSignal([0, 0.25, 0.5, 0.75, 1], 20, 1), + ComponentSignal([0, 0.25, 0.5, 0.75, 1], 20, 2), + ] + ), + ( + [ + ComponentSignal([0, 0.25, 0.5, 0.75, 1], 20, 0), + ComponentSignal([0, 0.25, 0.5, 0.75, 1], 20, 1), + ComponentSignal([0, 0.25, 0.5, 0.75, 1], 20, 2), ] -@pytest.mark.parametrize('tccm',tccm) + ), + ( + [ + ComponentSignal([0, 0.25, 0.5, 0.75, 1], 20, 0), + ComponentSignal([0, 0.25, 0.5, 2.75, 1], 20, 1), + ComponentSignal([0, 0.25, 0.5, 0.75, 1], 20, 2), + ] + ), + ([ComponentSignal([0.25], 20, 0), ComponentSignal([0.25], 20, 1), ComponentSignal([0.25], 20, 2)]), + ([ComponentSignal([0, 0.25, 0.5, 0.75, 1], 20, 0), ComponentSignal([0, 0.25, 0.5, 0.75, 1], 20, 1)]), + # ([ComponentSignal([[0, .25, .5, .75, 1],[0, .25, .5, .75, 1]], 20, 0), ComponentSignal([[0, .25, .5, .75, 1],[0, .25, .5, .75, 1]], 20, 1)]), # iq is multidimensional. Expected to fail + # (ComponentSignal([], 20, 0)), # Expected to fail + # ([]), #Expected to fail +] + + +@pytest.mark.parametrize("tccm", tccm) def test_construct_component_matrix(tccm): actual = construct_component_matrix(tccm) for component in tccm: @@ -195,59 +393,144 @@ def test_construct_component_matrix(tccm): tcwm = [ - ([ComponentSignal([0,.25,.5,.75,1],20,0)]), - # ([ComponentSignal([0,.25,.5,.75,1],0,0)]), # 0 signal length. Failure expected - ([ComponentSignal([0,.25,.5,.75,1],20,0),ComponentSignal([0,.25,.5,.75,1],20,1),ComponentSignal([0,.25,.5,.75,1],20,2)]), - ([ComponentSignal([0, .25, .5, .75, 1], 20, 0), ComponentSignal([0, .25, .5, .75, 1], 20, 1), - ComponentSignal([0, .25, .5, .75, 1], 20, 2)]), - ([ComponentSignal([0, .25, .5, .75, 1], 20, 0), ComponentSignal([0, .25, .5, 2.75, 1], 20, 1), - ComponentSignal([0, .25, .5, .75, 1], 20, 2)]), - ([ComponentSignal([.25], 20, 0), ComponentSignal([.25], 20, 1), ComponentSignal([.25], 20, 2)]), - ([ComponentSignal([0, .25, .5, .75, 1], 20, 0), ComponentSignal([0, .25, .5, .75, 1], 20, 1)]), - #(ComponentSignal([], 20, 0)), # Expected to fail - #([]), #Expected to fail + ([ComponentSignal([0, 0.25, 0.5, 0.75, 1], 20, 0)]), + # ([ComponentSignal([0,.25,.5,.75,1],0,0)]), # 0 signal length. Failure expected + ( + [ + ComponentSignal([0, 0.25, 0.5, 0.75, 1], 20, 0), + ComponentSignal([0, 0.25, 0.5, 0.75, 1], 20, 1), + ComponentSignal([0, 0.25, 0.5, 0.75, 1], 20, 2), + ] + ), + ( + [ + ComponentSignal([0, 0.25, 0.5, 0.75, 1], 20, 0), + ComponentSignal([0, 0.25, 0.5, 0.75, 1], 20, 1), + ComponentSignal([0, 0.25, 0.5, 0.75, 1], 20, 2), + ] + ), + ( + [ + ComponentSignal([0, 0.25, 0.5, 0.75, 1], 20, 0), + ComponentSignal([0, 0.25, 0.5, 2.75, 1], 20, 1), + ComponentSignal([0, 0.25, 0.5, 0.75, 1], 20, 2), + ] + ), + ([ComponentSignal([0.25], 20, 0), ComponentSignal([0.25], 20, 1), ComponentSignal([0.25], 20, 2)]), + ([ComponentSignal([0, 0.25, 0.5, 0.75, 1], 20, 0), ComponentSignal([0, 0.25, 0.5, 0.75, 1], 20, 1)]), + # (ComponentSignal([], 20, 0)), # Expected to fail + # ([]), #Expected to fail ] -@pytest.mark.parametrize('tcwm',tcwm) + + +@pytest.mark.parametrize("tcwm", tcwm) def test_construct_weight_matrix(tcwm): actual = construct_weight_matrix(tcwm) for component in tcwm: np.testing.assert_allclose(actual[component.id], component.weights) -tuw = [([ComponentSignal([0, .25, .5, .75, 1], 2, 0), ComponentSignal([0, .25, .5, .75, 1], 2, 1), - ComponentSignal([0, .25, .5, .75, 1], 2, 2)], [[1, 1], [1.2, 1.3], [1.3, 1.4], [1.4, 1.5], [2, 2.1]], None), - ([ComponentSignal([0, .25, .5, .75, 1], 2, 0), ComponentSignal([0, .25, .5, .75, 1], 2, 1), - ComponentSignal([0, .25, .5, .75, 1], 2, 2)], [[1, 1], [1.2, 1.3], [1.3, 1.4], [1.4, 1.5], [2, 2.1]], "align"), - ([ComponentSignal([0, .25, .5, .75, 1], 2, 0), ComponentSignal([0, .25, .5, .75, 1], 2, 1), - ComponentSignal([0, .25, .5, .75, 1], 2, 2)], [[0, 0], [0, 0], [0, 0], [0, 0], [0, 0]], None), - ([ComponentSignal([0, .25, .5, .75, 1], 2, 0), ComponentSignal([0, .25, .5, .75, 1], 2, 1), - ComponentSignal([0, .25, .5, .75, 1], 2, 2)], [[0, 0], [0, 0], [0, 0], [0, 0], [0, 0]], "align"), - ([ComponentSignal([0, .25, .5, .75, 1], 2, 0), ComponentSignal([0, .25, .5, .75, 1], 2, 1), - ComponentSignal([0, .25, .5, .75, 1], 2, 2)], [[-.5, 1], [1.2, -1.3], [1.1, -1], [0, -1.5], [0, .1]], None), - ([ComponentSignal([0, .25, .5, .75, 1], 2, 0), ComponentSignal([0, .25, .5, .75, 1], 2, 1), - ComponentSignal([0, .25, .5, .75, 1], 2, 2)], [[-.5, 1], [1.2, -1.3], [1.1, -1], [0, -1.5], [0, .1]], "align"), - # ([ComponentSignal([0, .25, .5, .75, 1], 0, 0), ComponentSignal([0, .25, .5, .75, 1], 0, 1), - # ComponentSignal([0, .25, .5, .75, 1], 0, 2)], [[1, 1], [1.2, 1.3], [1.3, 1.4], [1.4, 1.5], [2, 2.1]], None), - # ([ComponentSignal([0, .25, .5, .75, 1], 0, 0), ComponentSignal([0, .25, .5, .75, 1], 0, 1), - # ComponentSignal([0, .25, .5, .75, 1], 0, 2)], [], None), - # ([ComponentSignal([0, .25, .5, .75, 1], 2, 0), ComponentSignal([0, .25, .5, .75, 1], 2, 1), - # ComponentSignal([0, .25, .5, .75, 1], 2, 2)], [], 170), - ] -@pytest.mark.parametrize('tuw', tuw) +tuw = [ + ( + [ + ComponentSignal([0, 0.25, 0.5, 0.75, 1], 2, 0), + ComponentSignal([0, 0.25, 0.5, 0.75, 1], 2, 1), + ComponentSignal([0, 0.25, 0.5, 0.75, 1], 2, 2), + ], + [[1, 1], [1.2, 1.3], [1.3, 1.4], [1.4, 1.5], [2, 2.1]], + None, + ), + ( + [ + ComponentSignal([0, 0.25, 0.5, 0.75, 1], 2, 0), + ComponentSignal([0, 0.25, 0.5, 0.75, 1], 2, 1), + ComponentSignal([0, 0.25, 0.5, 0.75, 1], 2, 2), + ], + [[1, 1], [1.2, 1.3], [1.3, 1.4], [1.4, 1.5], [2, 2.1]], + "align", + ), + ( + [ + ComponentSignal([0, 0.25, 0.5, 0.75, 1], 2, 0), + ComponentSignal([0, 0.25, 0.5, 0.75, 1], 2, 1), + ComponentSignal([0, 0.25, 0.5, 0.75, 1], 2, 2), + ], + [[0, 0], [0, 0], [0, 0], [0, 0], [0, 0]], + None, + ), + ( + [ + ComponentSignal([0, 0.25, 0.5, 0.75, 1], 2, 0), + ComponentSignal([0, 0.25, 0.5, 0.75, 1], 2, 1), + ComponentSignal([0, 0.25, 0.5, 0.75, 1], 2, 2), + ], + [[0, 0], [0, 0], [0, 0], [0, 0], [0, 0]], + "align", + ), + ( + [ + ComponentSignal([0, 0.25, 0.5, 0.75, 1], 2, 0), + ComponentSignal([0, 0.25, 0.5, 0.75, 1], 2, 1), + ComponentSignal([0, 0.25, 0.5, 0.75, 1], 2, 2), + ], + [[-0.5, 1], [1.2, -1.3], [1.1, -1], [0, -1.5], [0, 0.1]], + None, + ), + ( + [ + ComponentSignal([0, 0.25, 0.5, 0.75, 1], 2, 0), + ComponentSignal([0, 0.25, 0.5, 0.75, 1], 2, 1), + ComponentSignal([0, 0.25, 0.5, 0.75, 1], 2, 2), + ], + [[-0.5, 1], [1.2, -1.3], [1.1, -1], [0, -1.5], [0, 0.1]], + "align", + ), + # ([ComponentSignal([0, .25, .5, .75, 1], 0, 0), ComponentSignal([0, .25, .5, .75, 1], 0, 1), + # ComponentSignal([0, .25, .5, .75, 1], 0, 2)], [[1, 1], [1.2, 1.3], [1.3, 1.4], [1.4, 1.5], [2, 2.1]], None), + # ([ComponentSignal([0, .25, .5, .75, 1], 0, 0), ComponentSignal([0, .25, .5, .75, 1], 0, 1), + # ComponentSignal([0, .25, .5, .75, 1], 0, 2)], [], None), + # ([ComponentSignal([0, .25, .5, .75, 1], 2, 0), ComponentSignal([0, .25, .5, .75, 1], 2, 1), + # ComponentSignal([0, .25, .5, .75, 1], 2, 2)], [], 170), +] + + +@pytest.mark.parametrize("tuw", tuw) def test_update_weights(tuw): actual = update_weights(tuw[0], tuw[1], tuw[2]) assert np.shape(actual) == (len(tuw[0]), len(tuw[0][0].weights)) -trs = [([ComponentSignal([0, .25, .5, .75, 1], 2, 0), ComponentSignal([0, .25, .5, .75, 1], 2, 1), - ComponentSignal([0, .25, .5, .75, 1], 2, 2)], 1), - ([ComponentSignal([0, .25, .5, .75, 1], 2, 0), ComponentSignal([0, .25, .5, .75, 1], 2, 1), - ComponentSignal([0, .25, .5, .75, 1], 2, 2)], 0), - ([ComponentSignal([0, .25, .5, .75, 1], 3, 0), ComponentSignal([0, .25, .5, .75, 1], 3, 1), - ComponentSignal([0, .25, .5, .75, 1], 3, 2)], 2), - # ([ComponentSignal([0, .25, .5, .75, 1], 2, 0), ComponentSignal([0, .25, .5, .75, 1], 2, 1), - # ComponentSignal([0, .25, .5, .75, 1], 2, 2)], -1), + +trs = [ + ( + [ + ComponentSignal([0, 0.25, 0.5, 0.75, 1], 2, 0), + ComponentSignal([0, 0.25, 0.5, 0.75, 1], 2, 1), + ComponentSignal([0, 0.25, 0.5, 0.75, 1], 2, 2), + ], + 1, + ), + ( + [ + ComponentSignal([0, 0.25, 0.5, 0.75, 1], 2, 0), + ComponentSignal([0, 0.25, 0.5, 0.75, 1], 2, 1), + ComponentSignal([0, 0.25, 0.5, 0.75, 1], 2, 2), + ], + 0, + ), + ( + [ + ComponentSignal([0, 0.25, 0.5, 0.75, 1], 3, 0), + ComponentSignal([0, 0.25, 0.5, 0.75, 1], 3, 1), + ComponentSignal([0, 0.25, 0.5, 0.75, 1], 3, 2), + ], + 2, + ), + # ([ComponentSignal([0, .25, .5, .75, 1], 2, 0), ComponentSignal([0, .25, .5, .75, 1], 2, 1), + # ComponentSignal([0, .25, .5, .75, 1], 2, 2)], -1), ] -@pytest.mark.parametrize('trs',trs) + + +@pytest.mark.parametrize("trs", trs) def test_reconstruct_signal(trs): actual = reconstruct_signal(trs[0], trs[1]) assert len(actual) == len(trs[0][0].grid) diff --git a/diffpy/snmf/version.py b/diffpy/snmf/version.py index bf3c0801..d7a5c1cf 100644 --- a/diffpy/snmf/version.py +++ b/diffpy/snmf/version.py @@ -20,9 +20,9 @@ # obtain version information from pkg_resources import get_distribution -__version__ = get_distribution('diffpy.snmf').version +__version__ = get_distribution("diffpy.snmf").version # we assume that tag_date was used and __version__ ends in YYYYMMDD -__date__ = __version__[-8:-4] + '-' + __version__[-4:-2] + '-' + __version__[-2:] +__date__ = __version__[-8:-4] + "-" + __version__[-4:-2] + "-" + __version__[-2:] # End of file diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 00000000..e537e24a --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,22 @@ +[tool.black] +line-length = 115 +include = '\.pyi?$' +exclude = ''' +/( + \.git + | \.hg + | \.mypy_cache + | \.tox + | \.venv + | \.rst + | \.txt + | _build + | buck-out + | build + | dist + + # The following are specific to Black, you probably don't want those. + | blib2to3 + | tests/data +)/ +''' From 3f12010f40ae828e13fc5f5ca1729007e2c2e43e Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Thu, 1 Aug 2024 15:09:44 -0400 Subject: [PATCH 02/65] Run black . --- doc/source/conf.py | 61 +++++++++++++++++++++++++++------------------- run_tests.py | 7 +++--- setup.py | 56 +++++++++++++++++++++--------------------- 3 files changed, 67 insertions(+), 57 deletions(-) diff --git a/doc/source/conf.py b/doc/source/conf.py index a6cf4b5d..4c28b45a 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -12,17 +12,18 @@ # import sys import os -sys.path.insert(0, os.path.abspath('../..')) + +sys.path.insert(0, os.path.abspath("../..")) # -- Project information ----------------------------------------------------- -project = 'snmf' -copyright = '2009-2023, Trustees of Columbia University in the City of New York, all rights reserved.' -author = 'Ran Gu, Adeolu Ajayi, Qiang Du, Simon J.L. Billinge' +project = "snmf" +copyright = "2009-2023, Trustees of Columbia University in the City of New York, all rights reserved." +author = "Ran Gu, Adeolu Ajayi, Qiang Du, Simon J.L. Billinge" # The full version, including alpha/beta/rc tags -release = '0.1.0' +release = "0.1.0" # -- General configuration --------------------------------------------------- @@ -31,29 +32,35 @@ # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. import sphinx_rtd_theme -extensions = ['sphinx.ext.autodoc', 'sphinx.ext.napoleon', - 'sphinx.ext.todo', 'sphinx.ext.viewcode', - 'sphinx.ext.intersphinx', 'm2r'] + +extensions = [ + "sphinx.ext.autodoc", + "sphinx.ext.napoleon", + "sphinx.ext.todo", + "sphinx.ext.viewcode", + "sphinx.ext.intersphinx", + "m2r", +] napoleon_google_docstring = False napoleon_use_param = False napoleon_use_ivar = False # Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] +templates_path = ["_templates"] from jinja2 import Template, Environment, FileSystemLoader -source_suffix = '.rst' +source_suffix = ".rst" -master_doc = 'index' +master_doc = "index" -language = 'en' +language = "en" # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. # This pattern also affects html_static_path and html_extra_path. -exclude_patterns = ['Thumbs.db', '.DS_Store'] +exclude_patterns = ["Thumbs.db", ".DS_Store"] -pygments_style = 'sphinx' +pygments_style = "sphinx" todo_include_todos = True @@ -61,7 +68,7 @@ # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. -html_theme = 'sphinx_rtd_theme' +html_theme = "sphinx_rtd_theme" html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] html_theme_options = {} @@ -69,22 +76,26 @@ # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] +html_static_path = ["_static"] -htmlhelp_basename = 'snmfdoc' +htmlhelp_basename = "snmfdoc" latex_documents = [ - (master_doc, 'snmf.tex', 'Snmf Documentation', - 'author', 'manual'), + (master_doc, "snmf.tex", "Snmf Documentation", "author", "manual"), ] -man_pages = [ - (master_doc, 'snmf', 'Snmf Documentation', [author], 1) -] +man_pages = [(master_doc, "snmf", "Snmf Documentation", [author], 1)] texinfo_documents = [ - (master_doc, 'snmf', 'Snmf Documentation', author, - 'snmf', 'One line description of project.', 'Miscellaneous'), + ( + master_doc, + "snmf", + "Snmf Documentation", + author, + "snmf", + "One line description of project.", + "Miscellaneous", + ), ] epub_title = project @@ -92,4 +103,4 @@ epub_publisher = author epub_copyright = copyright -epub_exclude_files = ['search.html'] +epub_exclude_files = ["search.html"] diff --git a/run_tests.py b/run_tests.py index 108efd3f..69cde5f0 100644 --- a/run_tests.py +++ b/run_tests.py @@ -3,16 +3,15 @@ import sys import pytest -if __name__ == '__main__': +if __name__ == "__main__": # show output results from every test function - args = ['-v'] + args = ["-v"] # show the message output for skipped and expected failure tests if len(sys.argv) > 1: args.extend(sys.argv[1:]) - print('pytest arguments: {}'.format(args)) + print("pytest arguments: {}".format(args)) # # compute coverage stats for xpdAcq # call pytest and exit with the return code from pytest so that # travis will fail correctly if tests fail exit_res = pytest.main(args) sys.exit(exit_res) - diff --git a/setup.py b/setup.py index 4e022e37..54d138c8 100644 --- a/setup.py +++ b/setup.py @@ -16,49 +16,49 @@ # with open(os.path.join(MYDIR, 'requirements/run.txt')) as fp: # requirements = [line.strip() for line in fp] -with open(os.path.join(MYDIR, 'README.md')) as fp: +with open(os.path.join(MYDIR, "README.md")) as fp: long_description = fp.read() # define distribution setup( name="diffpy.snmf", - version='0.0.1', - packages=find_packages(exclude=['tests', 'applications']), + version="0.0.1", + packages=find_packages(exclude=["tests", "applications"]), entry_points={ # define console_scripts here, see setuptools docs for details. - 'console_scripts': [ - 'snmf = diffpy.snmf.stretchednmfapp:main', + "console_scripts": [ + "snmf = diffpy.snmf.stretchednmfapp:main", ], }, - test_suite='tests', + test_suite="tests", # install_requires=requirements, - author='Ran Gu, Simon J.L. Billinge', - author_email='sb2896@columbia.edu', - maintainer='Simon J.L. Billinge', - maintainer_email='sb2896@columbia.edu', - url='https://github.com/diffpy/diffpy.snmf', + author="Ran Gu, Simon J.L. Billinge", + author_email="sb2896@columbia.edu", + maintainer="Simon J.L. Billinge", + maintainer_email="sb2896@columbia.edu", + url="https://github.com/diffpy/diffpy.snmf", description="A python package implementing the stretched NMF algorithm.", - long_description = long_description, - long_description_content_type = 'text/x-rst', - license='BSD', + long_description=long_description, + long_description_content_type="text/x-rst", + license="BSD", keywords="diffpy PDF", - classifiers = [ + classifiers=[ # List of possible values at # http://pypi.python.org/pypi?:action=list_classifiers - 'Development Status :: 4 - Beta', - 'Environment :: Console', - 'Intended Audience :: Science/Research', - 'License :: OSI Approved :: BSD License', - 'Operating System :: MacOS :: MacOS X', - 'Operating System :: Microsoft :: Windows', - 'Operating System :: POSIX', - 'Operating System :: Unix', - 'Programming Language :: Python :: 3.9', - 'Programming Language :: Python :: 3.10', - 'Programming Language :: Python :: 3.11', - 'Topic :: Scientific/Engineering :: Chemistry', - 'Topic :: Scientific/Engineering :: Physics', + "Development Status :: 4 - Beta", + "Environment :: Console", + "Intended Audience :: Science/Research", + "License :: OSI Approved :: BSD License", + "Operating System :: MacOS :: MacOS X", + "Operating System :: Microsoft :: Windows", + "Operating System :: POSIX", + "Operating System :: Unix", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Topic :: Scientific/Engineering :: Chemistry", + "Topic :: Scientific/Engineering :: Physics", ], ) From 6764bef8483959f910693af720e48ce7b63749d0 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Thu, 1 Aug 2024 15:11:32 -0400 Subject: [PATCH 03/65] Add flake8 file --- .flake8 | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 .flake8 diff --git a/.flake8 b/.flake8 new file mode 100644 index 00000000..26ddf50a --- /dev/null +++ b/.flake8 @@ -0,0 +1,16 @@ +[flake8] +exclude = + .git, + __pycache__, + build, + dist, + doc/source/conf.py, + */doc/source/conf.py, + hooks/post_gen_project.py, + */__init__.py, + */debug.py +builtins = cookiecutter +max-line-length = 115 +# Ignore some style 'errors' produced while formatting by 'black' (see link below) +# https://black.readthedocs.io/en/stable/guides/using_black_with_other_tools.html#labels-why-pycodestyle-warnings +extend-ignore = E203 \ No newline at end of file From 157f7ed8c5e337c7a527b85a4280b909dea0da76 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Thu, 1 Aug 2024 16:06:13 -0400 Subject: [PATCH 04/65] Fix flake8 file --- .flake8 | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/.flake8 b/.flake8 index 26ddf50a..076b9924 100644 --- a/.flake8 +++ b/.flake8 @@ -4,13 +4,8 @@ exclude = __pycache__, build, dist, - doc/source/conf.py, - */doc/source/conf.py, - hooks/post_gen_project.py, - */__init__.py, - */debug.py -builtins = cookiecutter + doc/source/conf.py max-line-length = 115 -# Ignore some style 'errors' produced while formatting by 'black' (see link below) +# Ignore some style 'errors' produced while formatting by 'black' # https://black.readthedocs.io/en/stable/guides/using_black_with_other_tools.html#labels-why-pycodestyle-warnings extend-ignore = E203 \ No newline at end of file From 90dd06037b57ca51168ad139f79ebea6449aacf5 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Thu, 1 Aug 2024 22:23:57 -0400 Subject: [PATCH 05/65] Fix all flake8 issues --- diffpy/snmf/containers.py | 19 ++-- diffpy/snmf/factorizers.py | 17 ++-- diffpy/snmf/io.py | 46 ++++----- diffpy/snmf/optimizers.py | 28 +++--- diffpy/snmf/polynomials.py | 6 +- diffpy/snmf/stretchednmfapp.py | 9 +- diffpy/snmf/subroutines.py | 137 ++++++++++++-------------- diffpy/snmf/tests/test_containers.py | 3 +- diffpy/snmf/tests/test_factorizers.py | 2 +- diffpy/snmf/tests/test_optimizers.py | 1 + diffpy/snmf/tests/test_polynomials.py | 3 +- diffpy/snmf/tests/test_subroutines.py | 29 +++--- 12 files changed, 154 insertions(+), 146 deletions(-) diff --git a/diffpy/snmf/containers.py b/diffpy/snmf/containers.py index 947b07a4..41bf03fe 100644 --- a/diffpy/snmf/containers.py +++ b/diffpy/snmf/containers.py @@ -1,5 +1,5 @@ -import numpy as np import numdifftools +import numpy as np class ComponentSignal: @@ -8,7 +8,7 @@ class ComponentSignal: ---------- grid: 1d array of floats The vector containing the grid points of the component. - iq: 1d array of floats + The intensity/g(r) values of the component. weights: 1d array of floats The vector containing the weight of the component signal for each signal. @@ -36,13 +36,18 @@ def apply_stretch(self, m): Returns ------- tuple of 1d arrays - The tuple of vectors where one vector is the stretched component, one vector is the 1st derivative of the - stretching operation, and one vector is the second derivative of the stretching operation. + The tuple of vectors where one vector is the stretched component, one vector is the 1st derivative + of the stretching operation, and one vector is the second derivative of the stretching operation. """ normalized_grid = np.arange(len(self.grid)) - func = lambda stretching_factor: np.interp( - normalized_grid / stretching_factor, normalized_grid, self.iq, left=0, right=0 - ) + # func = lambda stretching_factor: np.interp( + # normalized_grid / stretching_factor, normalized_grid, self.iq, left=0, right=0 + # ) + + # E731 do not assign a lambda expression, use a def + def func(stretching_factor): + return np.interp(normalized_grid / stretching_factor, normalized_grid, self.iq, left=0, right=0) + derivative_func = numdifftools.Derivative(func) second_derivative_func = numdifftools.Derivative(derivative_func) diff --git a/diffpy/snmf/factorizers.py b/diffpy/snmf/factorizers.py index ef6b2c0a..b4620fd4 100644 --- a/diffpy/snmf/factorizers.py +++ b/diffpy/snmf/factorizers.py @@ -5,27 +5,26 @@ def lsqnonneg(stretched_component_matrix, target_signal): """Finds the weights of stretched component signals under one-sided constraint. - Solves ``argmin_x || Ax - b ||_2`` for ``x>=0`` where A is the stretched_component_matrix and b is the target_signal - vector. Finds the weights of component signals given undecomposed signal data and stretched components under a - one-sided constraint on the weights. + Solves ``argmin_x || Ax - b ||_2`` for ``x>=0`` where A is the stretched_component_matrix and b is the + target_signal vector. Finds the weights of component signals given undecomposed signal data and stretched + components under a one-sided constraint on the weights. Parameters ---------- stretched_component_matrix: 2d array like The component matrix where each column contains a stretched component signal. Has dimensions R x C where R is - the length of the signal and C is the number of components. Does not need to be nonnegative. Corresponds with 'A' - from the objective function. + the length of the signal and C is the number of components. Does not need to be nonnegative. Corresponds with + 'A' from the objective function. target_signal: 1d array like - The signal that is used as reference against which weight factors will be determined. Any column from the matrix - of the entire, unfactorized input data could be used. Has length R. Does not need to be nonnegative. Corresponds - with 'b' from the objective function. + The signal that is used as reference against which weight factors will be determined. Any column from the + matrix of the entire, unfactorized input data could be used. Has length R. Does not need to be nonnegative. + Corresponds with 'b' from the objective function. Returns ------- 1d array like The vector containing component signal weights at a moment. Has length C. - """ stretched_component_matrix = np.asarray(stretched_component_matrix) target_signal = np.asarray(target_signal) diff --git a/diffpy/snmf/io.py b/diffpy/snmf/io.py index 7132d703..12eb1b23 100644 --- a/diffpy/snmf/io.py +++ b/diffpy/snmf/io.py @@ -1,6 +1,8 @@ +from pathlib import Path + import numpy as np import scipy.sparse -from pathlib import Path + from diffpy.utils.parsers.loaddata import loadData @@ -10,8 +12,8 @@ def initialize_variables(data_input, number_of_components, data_type, sparsity=1 Parameters ---------- data_input: 2d array like - The observed or simulated PDF or XRD data provided by the user. Has dimensions R x N where R is the signal length - and N is the number of PDF/XRD signals. + The observed or simulated PDF or XRD data provided by the user. Has dimensions R x N where R is the signa + length and N is the number of PDF/XRD signals. number_of_components: int The number of component signals the user would like to decompose 'data_input' into. @@ -20,23 +22,23 @@ def initialize_variables(data_input, number_of_components, data_type, sparsity=1 The type of data the user has passed into the program. Can assume the value of 'PDF' or 'XRD.' sparsity: float, optional - The regularization parameter that behaves as the coefficient of a "sparseness" regularization term that enhances - the ability to decompose signals in the case of sparse data e.g. X-ray Diffraction data. A non-zero value - indicates sparsity in the data; greater magnitudes indicate greater amounts of sparsity. + The regularization parameter that behaves as the coefficient of a "sparseness" regularization term that + enhances the ability to decompose signals in the case of sparse data e.g. X-ray Diffraction data. + A non-zero value indicates sparsity in the data; greater magnitudes indicate greater amounts of sparsity. smoothness: float, optional - The regularization parameter that behaves as the coefficient of a "smoothness" term that ensures that component - signal weightings change smoothly with time. Assumes a default value of 1e18. + The regularization parameter that behaves as the coefficient of a "smoothness" term that ensures that + component signal weightings change smoothly with time. Assumes a default value of 1e18. Returns ------- dictionary - The collection of the names and values of the constants used in the algorithm. Contains the number of observed PDF - /XRD patterns, the length of each pattern, the type of the data, the number of components the user would like to - decompose the data into, an initial guess for the component matrix, and initial guess for the weight factor matrix - ,an initial guess for the stretching factor matrix, a parameter controlling smoothness of the solution, a - parameter controlling sparseness of the solution, the matrix representing the smoothness term, and a matrix used - to construct a hessian matrix. + The collection of the names and values of the constants used in the algorithm. Contains the number of + observed PDF/XRD patterns, the length of each pattern, the type of the data, the number of components + the user would like to decompose the data into, an initial guess for the component matrix, and initial + guess for the weight factor matrix, an initial guess for the stretching factor matrix, a parameter + controlling smoothness of the solution, a parameter controlling sparseness of the solution, the matrix + representing the smoothness term, and a matrix used to construct a hessian matrix. """ signal_length = data_input.shape[0] @@ -74,22 +76,22 @@ def initialize_variables(data_input, number_of_components, data_type, sparsity=1 def load_input_signals(file_path=None): """Processes a directory of a series of PDF/XRD patterns into a usable format. - Constructs a 2d array out of a directory of PDF/XRD patterns containing each files dependent variable column in a - new column. Constructs a 1d array containing the grid values. + Constructs a 2d array out of a directory of PDF/XRD patterns containing each files dependent variable + column in a new column. Constructs a 1d array containing the grid values. Parameters ---------- file_path: str or Path object, optional - The path to the directory containing the input XRD/PDF data. If no path is specified, defaults to the current - working directory. Accepts a string or a pathlib.Path object. Input data not on the same grid as the first file - read will be ignored. + The path to the directory containing the input XRD/PDF data. If no path is specified, defaults to the + current working directory. Accepts a string or a pathlib.Path object. Input data not on the same grid + as the first file read will be ignored. Returns ------- tuple - The tuple whose first element is an R x M 2d array made of PDF/XRD patterns as each column; R is the length of the - signal and M is the number of patterns. The tuple contains a 1d array containing the values of the grid points as - its second element; Has length R. + The tuple whose first element is an R x M 2d array made of PDF/XRD patterns as each column; R is the + length of the signal and M is the number of patterns. The tuple contains a 1d array containing the values + of the grid points as its second element; Has length R. """ diff --git a/diffpy/snmf/optimizers.py b/diffpy/snmf/optimizers.py index 8c94c583..4198907d 100644 --- a/diffpy/snmf/optimizers.py +++ b/diffpy/snmf/optimizers.py @@ -1,37 +1,39 @@ -import numpy as np import cvxpy +import numpy as np def get_weights(stretched_component_gram_matrix, linear_coefficient, lower_bound, upper_bound): """Finds the weights of stretched component signals under a two-sided constraint - Solves min J(y) = (linear_coefficient)' * y + (1/2) * y' * (quadratic coefficient) * y where lower_bound <= y <= - upper_bound and stretched_component_gram_matrix is symmetric positive definite. Finds the weightings of stretched - component signals under a two-sided constraint. + Solves min J(y) = (linear_coefficient)' * y + (1/2) * y' * (quadratic coefficient) * y where + lower_bound <= y <= upper_bound and stretched_component_gram_matrix is symmetric positive definite. + Finds the weightings of stretched component signals under a two-sided constraint. Parameters ---------- stretched_component_gram_matrix: 2d array like - The Gram matrix constructed from the stretched component matrix. It is a square positive definite matrix. It has - dimensions C x C where C is the number of component signals. Must be symmetric positive definite. + The Gram matrix constructed from the stretched component matrix. It is a square positive definite matrix. + It has dimensions C x C where C is the number of component signals. Must be symmetric positive definite. linear_coefficient: 1d array like - The vector containing the product of the stretched component matrix and the transpose of the observed data matrix. - Has length C. + The vector containing the product of the stretched component matrix and the transpose of the observed + data matrix. Has length C. lower_bound: 1d array like The lower bound on the values of the output weights. Has the same dimensions of the function output. Each - element in 'lower_bound' determines the minimum value the corresponding element in the function output may take. + element in 'lower_bound' determines the minimum value the corresponding element in the function output may + take. upper_bound: 1d array like - The upper bound on the values of the output weights. Has the same dimensions of the function output. Each element - in 'upper_bound' determines the maximum value the corresponding element in the function output may take. + The upper bound on the values of the output weights. Has the same dimensions of the function output. Each + element in 'upper_bound' determines the maximum value the corresponding element in the function output may + take. Returns ------- 1d array like - The vector containing the weightings of the components needed to reconstruct a given input signal from the input - set. Has length C + The vector containing the weightings of the components needed to reconstruct a given input signal from the + input set. Has length C """ stretched_component_gram_matrix = np.asarray(stretched_component_gram_matrix) diff --git a/diffpy/snmf/polynomials.py b/diffpy/snmf/polynomials.py index 3c8d5dd4..265be050 100644 --- a/diffpy/snmf/polynomials.py +++ b/diffpy/snmf/polynomials.py @@ -3,7 +3,8 @@ def rooth(linear_coefficient, constant_term): """ - Returns the largest real root of x^3+(linear_coefficient) * x + constant_term. If there are no real roots return 0. + Returns the largest real root of x^3+(linear_coefficient) * x + constant_term. If there are no real roots + return 0. Parameters ---------- @@ -15,7 +16,8 @@ def rooth(linear_coefficient, constant_term): Returns ------- ndarray of floats - The largest real root of x^3+(linear_coefficient) * x + constant_term if roots are real, else return 0 array + The largest real root of x^3+(linear_coefficient) * x + constant_term if roots are real, else + return 0 array """ diff --git a/diffpy/snmf/stretchednmfapp.py b/diffpy/snmf/stretchednmfapp.py index 7feb5ce5..7080e0c4 100644 --- a/diffpy/snmf/stretchednmfapp.py +++ b/diffpy/snmf/stretchednmfapp.py @@ -1,9 +1,8 @@ -import numpy as np import argparse from pathlib import Path -from diffpy.snmf.subroutines import lift_data, initialize_components -from diffpy.snmf.containers import ComponentSignal -from diffpy.snmf.io import load_input_signals, initialize_variables + +from diffpy.snmf.io import initialize_variables, load_input_signals +from diffpy.snmf.subroutines import initialize_components, lift_data ALLOWED_DATA_TYPES = ["powder_diffraction", "pd", "pair_distribution_function", "pdf"] @@ -38,7 +37,7 @@ def create_parser(): "--lift-factor", type=float, default=1, - help="The lifting factor. Data will be lifted by lifted_data = data + abs(min(data) * lift). Default is 1.", + help="The lifting factor. Data will be lifted by lifted_data = data + abs(min(data) * lift). Default 1.", ) parser.add_argument( "number-of-components", diff --git a/diffpy/snmf/subroutines.py b/diffpy/snmf/subroutines.py index e3acafbc..e4f6a5f7 100644 --- a/diffpy/snmf/subroutines.py +++ b/diffpy/snmf/subroutines.py @@ -1,12 +1,13 @@ +import numdifftools import numpy as np -from diffpy.snmf.optimizers import get_weights -from diffpy.snmf.factorizers import lsqnonneg + from diffpy.snmf.containers import ComponentSignal -import numdifftools +from diffpy.snmf.factorizers import lsqnonneg +from diffpy.snmf.optimizers import get_weights def initialize_components(number_of_components, number_of_signals, grid_vector): - """Initializes ComponentSignals for each of the components in the decomposition + """Initializes ComponentSignals for each of the components in the decomposition. Parameters ---------- @@ -31,15 +32,15 @@ def initialize_components(number_of_components, number_of_signals, grid_vector): def lift_data(data_input, lift=1): - """Lifts values of data_input + """Lifts values of data_input. Adds 'lift' * the minimum value in data_input to data_input element-wise. Parameters ---------- data_input: 2d array like - The matrix containing a series of signals to be decomposed. Has dimensions N x M where N is the length of each - signal and M is the number of signals. + The matrix containing a series of signals to be decomposed. Has dimensions N x M where N is the length + of each signal and M is the number of signals. lift: float The factor representing how much to lift 'data_input'. @@ -48,14 +49,13 @@ def lift_data(data_input, lift=1): ------- 2d array like The matrix that contains data_input - (min(data_input) * lift). - """ data_input = np.asarray(data_input) return data_input + np.abs(np.min(data_input) * lift) def construct_stretching_matrix(components, number_of_components, number_of_signals): - """Constructs the stretching factor matrix + """Constructs the stretching factor matrix. Parameters ---------- @@ -67,9 +67,8 @@ def construct_stretching_matrix(components, number_of_components, number_of_sign Returns ------- 2d array - The matrix containing the stretching factors for the component signals for each of the signals in the raw data. - Has dimensions `component_signal` x `number_of_signals` - + The matrix containing the stretching factors for the component signals for each of the signals in the + raw data. Has dimensions `component_signal` x `number_of_signals` """ if (len(components)) == 0: raise ValueError(f"Number of components = {number_of_components}. Number_of_components must be >= 1.") @@ -85,7 +84,7 @@ def construct_stretching_matrix(components, number_of_components, number_of_sign def construct_component_matrix(components): - """Constructs the component matrix + """Constructs the component matrix. Parameters ---------- @@ -96,7 +95,6 @@ def construct_component_matrix(components): ------- 2d array The matrix containing the component signal values. Has dimensions `signal_length` x `number_of_components`. - """ signal_length = len(components[0].iq) number_of_components = len(components) @@ -112,7 +110,7 @@ def construct_component_matrix(components): def construct_weight_matrix(components): - """Constructs the weights matrix + """Constructs the weights matrix. Constructs a Ķ x M matrix where K is the number of components and M is the number of signals. Each element is the stretching factor for a specific @@ -150,16 +148,16 @@ def update_weights(components, data_input, method=None): components: tuple of ComponentSignal objects The tuple containing the component signals. method: str - The string specifying which method should be used to find a new weight matrix: non-negative least squares or a - quadratic program. + The string specifying which method should be used to find a new weight matrix: non-negative least squares + or a quadratic program. data_input: 2d array The 2d array containing the user-provided signals. Returns ------- 2d array - The 2d array containing the weight factors for each component for each signal from `data_input`. Has dimensions - K x M where K is the number of components and M is the number of signals in `data_input.` + The 2d array containing the weight factors for each component for each signal from `data_input`. + Has dimensions K x M where K is the number of components and M is the number of signals in `data_input.` """ data_input = np.asarray(data_input) weight_matrix = construct_weight_matrix(components) @@ -200,7 +198,6 @@ def reconstruct_signal(components, signal_idx): ------- 1d array like The reconstruction of a signal from calculated weights, stretching factors, and iq values. - """ signal_length = len(components[0].grid) reconstruction = np.zeros(signal_length) @@ -212,13 +209,14 @@ def reconstruct_signal(components, signal_idx): def initialize_arrays(number_of_components, number_of_moments, signal_length): - """Generates the initial guesses for the weight, stretching, and component matrices + """Generates the initial guesses for the weight, stretching, and component matrices. - Calculates the initial guesses for the component matrix, stretching factor matrix, and weight matrix. The initial - guess for the component matrix is a random (signal_length) x (number_of_components) matrix where each element is - between 0 and 1. The initial stretching factor matrix is a random (number_of_components) x (number_of_moments) - matrix where each element is number slightly perturbed from 1. The initial weight matrix guess is a random - (number_of_components) x (number_of_moments) matrix where each element is between 0 and 1. + Calculates the initial guesses for the component matrix, stretching factor matrix, and weight matrix. The + initial guess for the component matrix is a random (signal_length) x (number_of_components) matrix where + each element is between 0 and 1. The initial stretching factor matrix is a random + (number_of_components) x (number_of_moments) matrix where each element is number slightly perturbed from 1. + The initial weight matrix guess is a random (number_of_components) x (number_of_moments) matrix where + each element is between 0 and 1. Parameters ---------- @@ -236,7 +234,6 @@ def initialize_arrays(number_of_components, number_of_moments, signal_length): tuple of 2d arrays of floats The tuple containing three elements: the initial component matrix guess, the initial stretching factor matrix guess, and the initial weight factor matrix guess in that order. - """ component_matrix_guess = np.random.rand(signal_length, number_of_components) weight_matrix_guess = np.random.rand(number_of_components, number_of_moments) @@ -258,25 +255,26 @@ def objective_function( Parameters ---------- residual_matrix: 2d array like - The matrix where each column is the difference between an experimental PDF/XRD pattern and a calculated PDF/XRD - pattern at each grid point. Has dimensions R x M where R is the length of each pattern and M is the amount of - patterns. + The matrix where each column is the difference between an experimental PDF/XRD pattern and a calculated + PDF/XRD pattern at each grid point. Has dimensions R x M where R is the length of each pattern and M is + the amount of patterns. stretching_factor_matrix: 2d array like - The matrix containing the stretching factors of the calculated component signal. Has dimensions K x M where K is - the amount of components and M is the number of experimental PDF/XRD patterns. + The matrix containing the stretching factors of the calculated component signal. Has dimensions K x M where + K is the amount of components and M is the number of experimental PDF/XRD patterns. smoothness: float - The coefficient of the smoothness term which determines the intensity of the smoothness term and its behavior. - It is not very sensitive and is usually adjusted by multiplying it by ten. + The coefficient of the smoothness term which determines the intensity of the smoothness term and its + behavior. It is not very sensitive and is usually adjusted by multiplying it by ten. smoothness_term: 2d array like The regularization term that ensures that smooth changes in the component stretching signals are favored. - Has dimensions (M-2) x M where M is the amount of experimentally obtained PDF/XRD patterns, the moment amount. + Has dimensions (M-2) x M where M is the amount of experimentally obtained PDF/XRD patterns, the moment + amount. component_matrix: 2d array like - The matrix containing the calculated component signals of the experimental PDF/XRD patterns. Has dimensions R x K - where R is the signal length and K is the number of component signals. + The matrix containing the calculated component signals of the experimental PDF/XRD patterns. Has dimensions + R x K where R is the signal length and K is the number of component signals. sparsity: float The parameter determining the intensity of the sparsity regularization term which enables the algorithm to @@ -286,7 +284,6 @@ def objective_function( ------- float The value of the objective function. - """ residual_matrix = np.asarray(residual_matrix) stretching_factor_matrix = np.asarray(stretching_factor_matrix) @@ -301,10 +298,10 @@ def objective_function( def get_stretched_component(stretching_factor, component, signal_length): """Applies a stretching factor to a component signal. - Computes a stretched signal and reinterpolates it onto the original grid of points. Uses a normalized grid of evenly - spaced integers counting from 0 to signal_length (exclusive) to approximate values in between grid nodes. Once this - grid is stretched, values at grid nodes past the unstretched signal's domain are set to zero. Returns the - approximate values of x(r/a) from x(r) where x is a component signal. + Computes a stretched signal and reinterpolates it onto the original grid of points. Uses a normalized grid + of evenly spaced integers counting from 0 to signal_length (exclusive) to approximate values in between grid + nodes. Once this grid is stretched, values at grid nodes past the unstretched signal's domain are set to zero. + Returns the approximate values of x(r/a) from x(r) where x is a component signal. Parameters ---------- @@ -320,7 +317,6 @@ def get_stretched_component(stretching_factor, component, signal_length): tuple of 1d array of floats The calculated component signal with stretching factors applied. Has length N, the length of the unstretched component signal. Also returns the gradient and hessian of the stretching transformation. - """ component = np.asarray(component) normalized_grid = np.arange(signal_length) @@ -363,23 +359,23 @@ def update_weights_matrix( The length of the experimental signal patterns stretching_factor_matrix: 2d array like - The matrx containing the stretching factors of the calculated component signals. Has dimensions K x M where K is - the number of component signals and M is the number of XRD/PDF patterns. + The matrx containing the stretching factors of the calculated component signals. Has dimensions K x M + where K is the number of component signals and M is the number of XRD/PDF patterns. - component_matrix: 2d array like - The matrix containing the unstretched calculated component signals. Has dimensions N x K where N is the length of - the signals and K is the number of component signals. + component_matrix: 2d array lik + The matrix containing the unstretched calculated component signals. Has dimensions N x K where N is the + length of the signals and K is the number of component signals. data_input: 2d array like - The experimental series of PDF/XRD patterns. Has dimensions N x M where N is the length of the PDF/XRD signals and - M is the number of PDF/XRD patterns. + The experimental series of PDF/XRD patterns. Has dimensions N x M where N is the length of the PDF/XRD + signals and M is the number of PDF/XRD patterns. moment_amount: int The number of PDF/XRD patterns from the experimental data. weights_matrix: 2d array like - The matrix containing the weights of the stretched component signals. Has dimensions K x M where K is the number - of component signals and M is the number of XRD/PDF patterns. + The matrix containing the weights of the stretched component signals. Has dimensions K x M where K is + the number of component signals and M is the number of XRD/PDF patterns. method: str The string specifying the method for obtaining individual weights. @@ -388,7 +384,6 @@ def update_weights_matrix( ------- 2d array like The matrix containing the new weight factors of the stretched component signals. - """ stretching_factor_matrix = np.asarray(stretching_factor_matrix) component_matrix = np.asarray(component_matrix) @@ -417,29 +412,29 @@ def update_weights_matrix( def get_residual_matrix( component_matrix, weights_matrix, stretching_matrix, data_input, moment_amount, component_amount, signal_length ): - """Obtains the residual matrix between the experimental data and calculated data + """Obtains the residual matrix between the experimental data and calculated data. - Calculates the difference between the experimental data and the reconstructed experimental data created from the - calculated components, weights, and stretching factors. For each experimental pattern, the stretched and weighted - components making up that pattern are subtracted. + Calculates the difference between the experimental data and the reconstructed experimental data created from + the calculated components, weights, and stretching factors. For each experimental pattern, the stretched and + weighted components making up that pattern are subtracted. Parameters ---------- component_matrix: 2d array like - The matrix containing the calculated component signals. Has dimensions N x K where N is the length of the signal - and K is the number of calculated component signals. + The matrix containing the calculated component signals. Has dimensions N x K where N is the length of the + signal and K is the number of calculated component signals. weights_matrix: 2d array like - The matrix containing the calculated weights of the stretched component signals. Has dimensions K x M where K is - the number of components and M is the number of moments or experimental PDF/XRD patterns. + The matrix containing the calculated weights of the stretched component signals. Has dimensions K x M where + K is the number of components and M is the number of moments or experimental PDF/XRD patterns. stretching_matrix: 2d array like - The matrix containing the calculated stretching factors of the calculated component signals. Has dimensions K x M - where K is the number of components and M is the number of moments or experimental PDF/XRD patterns. + The matrix containing the calculated stretching factors of the calculated component signals. Has dimensions + K x M where K is the number of components and M is the number of moments or experimental PDF/XRD patterns. data_input: 2d array like - The matrix containing the experimental PDF/XRD data. Has dimensions N x M where N is the length of the signals and - M is the number of signal patterns. + The matrix containing the experimental PDF/XRD data. Has dimensions N x M where N is the length of the + signals and M is the number of signal patterns. moment_amount: int The number of patterns in the experimental data. Represents the number of moments in time in the data series @@ -454,11 +449,10 @@ def get_residual_matrix( Returns ------- 2d array like - The matrix containing the residual between the experimental data and reconstructed data from calculated values. - Has dimensions N x M where N is the signal length and M is the number of moments. Each column contains the - difference between an experimental signal and a reconstruction of that signal from the calculated weights, - components, and stretching factors. - + The matrix containing the residual between the experimental data and reconstructed data from calculated + values. Has dimensions N x M where N is the signal length and M is the number of moments. Each column + contains the difference between an experimental signal and a reconstruction of that signal from the + calculated weights, components, and stretching factors. """ component_matrix = np.asarray(component_matrix) weights_matrix = np.asarray(weights_matrix) @@ -478,7 +472,7 @@ def get_residual_matrix( def reconstruct_data(components): - """Reconstructs the `input_data` matrix + """Reconstructs the `input_data` matrix. Reconstructs the `input_data` matrix from calculated component signals, weights, and stretching factors. @@ -491,7 +485,6 @@ def reconstruct_data(components): ------- 2d array The 2d array containing the reconstruction of input_data. - """ signal_length = len(components[0].iq) number_of_signals = len(components[0].weights) diff --git a/diffpy/snmf/tests/test_containers.py b/diffpy/snmf/tests/test_containers.py index 2ccb0360..1c78be02 100644 --- a/diffpy/snmf/tests/test_containers.py +++ b/diffpy/snmf/tests/test_containers.py @@ -1,5 +1,6 @@ -import pytest import numpy as np +import pytest + from diffpy.snmf.containers import ComponentSignal tas = [ diff --git a/diffpy/snmf/tests/test_factorizers.py b/diffpy/snmf/tests/test_factorizers.py index a0c45467..c20d1926 100644 --- a/diffpy/snmf/tests/test_factorizers.py +++ b/diffpy/snmf/tests/test_factorizers.py @@ -1,6 +1,6 @@ import numpy as np -import scipy import pytest + from diffpy.snmf.factorizers import lsqnonneg tl = [ diff --git a/diffpy/snmf/tests/test_optimizers.py b/diffpy/snmf/tests/test_optimizers.py index 25d90e73..ab7a71f4 100644 --- a/diffpy/snmf/tests/test_optimizers.py +++ b/diffpy/snmf/tests/test_optimizers.py @@ -1,4 +1,5 @@ import pytest + from diffpy.snmf.optimizers import get_weights tm = [ diff --git a/diffpy/snmf/tests/test_polynomials.py b/diffpy/snmf/tests/test_polynomials.py index 6078b26f..d9358341 100644 --- a/diffpy/snmf/tests/test_polynomials.py +++ b/diffpy/snmf/tests/test_polynomials.py @@ -1,5 +1,6 @@ -import pytest import numpy as np +import pytest + from diffpy.snmf.polynomials import rooth tr = [ diff --git a/diffpy/snmf/tests/test_subroutines.py b/diffpy/snmf/tests/test_subroutines.py index bdb721fe..66029d2e 100644 --- a/diffpy/snmf/tests/test_subroutines.py +++ b/diffpy/snmf/tests/test_subroutines.py @@ -1,20 +1,20 @@ -import pytest import numpy as np +import pytest + from diffpy.snmf.containers import ComponentSignal from diffpy.snmf.subroutines import ( - objective_function, - get_stretched_component, - reconstruct_data, - get_residual_matrix, - update_weights_matrix, - initialize_arrays, - lift_data, - initialize_components, - construct_stretching_matrix, construct_component_matrix, + construct_stretching_matrix, construct_weight_matrix, - update_weights, + get_residual_matrix, + get_stretched_component, + initialize_components, + lift_data, + objective_function, + reconstruct_data, reconstruct_signal, + update_weights, + update_weights_matrix, ) to = [ @@ -336,7 +336,8 @@ def test_initialize_components(tcc): ([ComponentSignal([0, 0.5, 1, 1.5], 20, 0), ComponentSignal([0, 0.5, 1, 1.5], 20, 1)], 2, 20), ([ComponentSignal([0, 0.5, 1, 1.5], 20, 0), ComponentSignal([0, 0.5, 1, 21.5], 20, 1)], 2, 20), ([ComponentSignal([0, 1, 1.5], 20, 0), ComponentSignal([0, 0.5, 1, 21.5], 20, 1)], 2, 20), - # ([ComponentSignal([0,.5,1,1.5],20,0),ComponentSignal([0,.5,1,1.5],20,1)],1,-3), # Negative signal length. Raises an exception + # ([ComponentSignal([0,.5,1,1.5],20,0),ComponentSignal([0,.5,1,1.5],20,1)],1,-3), + # Negative signal length. Raises an exception # ([],1,20), # Empty components. Raises an Exception # ([],-1,20), # Empty components with negative number of components. Raises an exception # ([],0,20), # Empty components with zero number of components. Raises an exception @@ -379,7 +380,9 @@ def test_construct_stretching_matrix(tcso): ), ([ComponentSignal([0.25], 20, 0), ComponentSignal([0.25], 20, 1), ComponentSignal([0.25], 20, 2)]), ([ComponentSignal([0, 0.25, 0.5, 0.75, 1], 20, 0), ComponentSignal([0, 0.25, 0.5, 0.75, 1], 20, 1)]), - # ([ComponentSignal([[0, .25, .5, .75, 1],[0, .25, .5, .75, 1]], 20, 0), ComponentSignal([[0, .25, .5, .75, 1],[0, .25, .5, .75, 1]], 20, 1)]), # iq is multidimensional. Expected to fail + # ([ComponentSignal([[0, .25, .5, .75, 1],[0, .25, .5, .75, 1]], 20, 0), + # ComponentSignal([[0, .25, .5, .75, 1],[0, .25, .5, .75, 1]], 20, 1)]), + # iq is multidimensional. Expected to fail # (ComponentSignal([], 20, 0)), # Expected to fail # ([]), #Expected to fail ] From f3f8ae32cef2ccd23a165e3a40353f047e59a8c9 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Thu, 1 Aug 2024 22:52:32 -0400 Subject: [PATCH 06/65] Run precommit --- .coveragerc | 2 +- .flake8 | 2 +- .pre-commit-config.yaml | 43 +++++++++++++++++++++++++++++++++++++++++ CHANGELOG.rst | 1 - doc/source/conf.py | 5 +++-- doc/source/release.rst | 1 - requirements/docs.txt | 1 - run_tests.py | 1 + setup.py | 2 +- 9 files changed, 50 insertions(+), 8 deletions(-) create mode 100644 .pre-commit-config.yaml diff --git a/.coveragerc b/.coveragerc index b14e6750..72a8c926 100644 --- a/.coveragerc +++ b/.coveragerc @@ -1,5 +1,5 @@ [run] -source = +source = diffpy/snmf/ [report] omit = diff --git a/.flake8 b/.flake8 index 076b9924..2d2cb168 100644 --- a/.flake8 +++ b/.flake8 @@ -8,4 +8,4 @@ exclude = max-line-length = 115 # Ignore some style 'errors' produced while formatting by 'black' # https://black.readthedocs.io/en/stable/guides/using_black_with_other_tools.html#labels-why-pycodestyle-warnings -extend-ignore = E203 \ No newline at end of file +extend-ignore = E203 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 00000000..14d7a2eb --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,43 @@ +default_language_version: + python: python3 +ci: + autofix_commit_msg: | + [pre-commit.ci] auto fixes from pre-commit hooks + autofix_prs: true + autoupdate_branch: 'pre-commit-autoupdate' + autoupdate_commit_msg: '[pre-commit.ci] pre-commit autoupdate' + autoupdate_schedule: monthly + skip: [no-commit-to-branch] + submodules: false +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.6.0 + hooks: + - id: check-yaml + - id: end-of-file-fixer + - id: trailing-whitespace + exclude: '\.(rst|txt)$' + - repo: https://github.com/psf/black + rev: 24.4.2 + hooks: + - id: black + - repo: https://github.com/pycqa/flake8 + rev: 7.0.0 + hooks: + - id: flake8 + - repo: https://github.com/pycqa/isort + rev: 5.13.2 + hooks: + - id: isort + args: ["--profile", "black"] + - repo: https://github.com/kynan/nbstripout + rev: 0.7.1 + hooks: + - id: nbstripout + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.4.0 + hooks: + - id: no-commit-to-branch + name: Prevent Commit to Main Branch + args: ["--branch", "main"] + stages: [pre-commit] \ No newline at end of file diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 7bc09537..f01f1336 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -3,4 +3,3 @@ =========== .. current developments - diff --git a/doc/source/conf.py b/doc/source/conf.py index 4c28b45a..c4508858 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -6,12 +6,13 @@ # -- Path setup -------------------------------------------------------------- +import os + # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. # import sys -import os sys.path.insert(0, os.path.abspath("../..")) @@ -48,7 +49,7 @@ # Add any paths that contain templates here, relative to this directory. templates_path = ["_templates"] -from jinja2 import Template, Environment, FileSystemLoader +from jinja2 import Environment, FileSystemLoader, Template source_suffix = ".rst" diff --git a/doc/source/release.rst b/doc/source/release.rst index ca8fa0b6..8956c788 100644 --- a/doc/source/release.rst +++ b/doc/source/release.rst @@ -1,4 +1,3 @@ .. index:: release notes .. include:: /../../CHANGELOG.rst - diff --git a/requirements/docs.txt b/requirements/docs.txt index 661d44df..8b087850 100644 --- a/requirements/docs.txt +++ b/requirements/docs.txt @@ -1,4 +1,3 @@ make sphinx_rtd_theme m2r - diff --git a/run_tests.py b/run_tests.py index 69cde5f0..829a2e31 100644 --- a/run_tests.py +++ b/run_tests.py @@ -1,6 +1,7 @@ #!/usr/bin/env python import sys + import pytest if __name__ == "__main__": diff --git a/setup.py b/setup.py index 54d138c8..7a021885 100644 --- a/setup.py +++ b/setup.py @@ -8,8 +8,8 @@ """ import os -from setuptools import setup, find_packages +from setuptools import find_packages, setup MYDIR = os.path.dirname(os.path.abspath(__file__)) From b435314f2760bc37d98a8096571b1140469837c6 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Thu, 1 Aug 2024 22:59:04 -0400 Subject: [PATCH 07/65] Re-run pre-commit --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 14d7a2eb..c4588061 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -40,4 +40,4 @@ repos: - id: no-commit-to-branch name: Prevent Commit to Main Branch args: ["--branch", "main"] - stages: [pre-commit] \ No newline at end of file + stages: [pre-commit] From b70628b3b9a1969dd6de67fc29ceab992624586e Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Fri, 2 Aug 2024 11:52:05 -0400 Subject: [PATCH 08/65] Avoid lamdbda flake8 erorr with private function --- diffpy/snmf/containers.py | 29 +++++++++++++++++++---------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/diffpy/snmf/containers.py b/diffpy/snmf/containers.py index 41bf03fe..ed8c9672 100644 --- a/diffpy/snmf/containers.py +++ b/diffpy/snmf/containers.py @@ -25,6 +25,23 @@ def __init__(self, grid, number_of_signals, id_number, perturbation=1e-3): self.stretching_factors = np.ones(number_of_signals) + np.random.randn(number_of_signals) * perturbation self.id = int(id_number) + def _interpolate_stretching_factor(self, stretching_factor): + """Interpolates the intensity values over a normalized grid scaled by a given stretching factor. + + Parameters + ---------- + stretching_factor : float + The factor by which to stretch the grid. + + Returns + ------- + NDArray[float64] + The interpolated values over the stretched grid. + """ + + normalized_grid = np.arange(len(self.grid)) + return np.interp(normalized_grid / stretching_factor, normalized_grid, self.iq, left=0, right=0) + def apply_stretch(self, m): """Applies a stretching factor to a component @@ -39,19 +56,11 @@ def apply_stretch(self, m): The tuple of vectors where one vector is the stretched component, one vector is the 1st derivative of the stretching operation, and one vector is the second derivative of the stretching operation. """ - normalized_grid = np.arange(len(self.grid)) - # func = lambda stretching_factor: np.interp( - # normalized_grid / stretching_factor, normalized_grid, self.iq, left=0, right=0 - # ) - - # E731 do not assign a lambda expression, use a def - def func(stretching_factor): - return np.interp(normalized_grid / stretching_factor, normalized_grid, self.iq, left=0, right=0) - derivative_func = numdifftools.Derivative(func) + derivative_func = numdifftools.Derivative(self._interpolate_stretching_factor) second_derivative_func = numdifftools.Derivative(derivative_func) - stretched_component = func(self.stretching_factors[m]) + stretched_component = self._interpolate_stretching_factor(self.stretching_factors[m]) stretched_component_gra = derivative_func(self.stretching_factors[m]) stretched_component_hess = second_derivative_func(self.stretching_factors[m]) From 9ca7caf84ac951e3e36cc176c664359ef119091a Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Fri, 2 Aug 2024 11:55:39 -0400 Subject: [PATCH 09/65] Fix return description of private func --- diffpy/snmf/containers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/diffpy/snmf/containers.py b/diffpy/snmf/containers.py index ed8c9672..b85bf705 100644 --- a/diffpy/snmf/containers.py +++ b/diffpy/snmf/containers.py @@ -36,7 +36,7 @@ def _interpolate_stretching_factor(self, stretching_factor): Returns ------- NDArray[float64] - The interpolated values over the stretched grid. + Interpolated values of the self.iq values over the grid that has been adjusted by the stretching_factor """ normalized_grid = np.arange(len(self.grid)) From 1243f1a40d7530b44d656fc2d6789a8f309cba39 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Tue, 6 Aug 2024 21:51:38 -0400 Subject: [PATCH 10/65] Add noqa: E731 for lambda func --- diffpy/snmf/containers.py | 32 +++++++++----------------------- 1 file changed, 9 insertions(+), 23 deletions(-) diff --git a/diffpy/snmf/containers.py b/diffpy/snmf/containers.py index b85bf705..04405154 100644 --- a/diffpy/snmf/containers.py +++ b/diffpy/snmf/containers.py @@ -8,7 +8,7 @@ class ComponentSignal: ---------- grid: 1d array of floats The vector containing the grid points of the component. - + iq: 1d array of floats The intensity/g(r) values of the component. weights: 1d array of floats The vector containing the weight of the component signal for each signal. @@ -25,23 +25,6 @@ def __init__(self, grid, number_of_signals, id_number, perturbation=1e-3): self.stretching_factors = np.ones(number_of_signals) + np.random.randn(number_of_signals) * perturbation self.id = int(id_number) - def _interpolate_stretching_factor(self, stretching_factor): - """Interpolates the intensity values over a normalized grid scaled by a given stretching factor. - - Parameters - ---------- - stretching_factor : float - The factor by which to stretch the grid. - - Returns - ------- - NDArray[float64] - Interpolated values of the self.iq values over the grid that has been adjusted by the stretching_factor - """ - - normalized_grid = np.arange(len(self.grid)) - return np.interp(normalized_grid / stretching_factor, normalized_grid, self.iq, left=0, right=0) - def apply_stretch(self, m): """Applies a stretching factor to a component @@ -53,14 +36,17 @@ def apply_stretch(self, m): Returns ------- tuple of 1d arrays - The tuple of vectors where one vector is the stretched component, one vector is the 1st derivative - of the stretching operation, and one vector is the second derivative of the stretching operation. + The tuple of vectors where one vector is the stretched component, one vector is the 1st derivative of the + stretching operation, and one vector is the second derivative of the stretching operation. """ - - derivative_func = numdifftools.Derivative(self._interpolate_stretching_factor) + normalized_grid = np.arange(len(self.grid)) + func = lambda stretching_factor: np.interp( # noqa: E731 + normalized_grid / stretching_factor, normalized_grid, self.iq, left=0, right=0 + ) + derivative_func = numdifftools.Derivative(func) second_derivative_func = numdifftools.Derivative(derivative_func) - stretched_component = self._interpolate_stretching_factor(self.stretching_factors[m]) + stretched_component = func(self.stretching_factors[m]) stretched_component_gra = derivative_func(self.stretching_factors[m]) stretched_component_hess = second_derivative_func(self.stretching_factors[m]) From ac2d944e170a9d4f81d54c07797c1a2560c1f200 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Tue, 6 Aug 2024 21:53:48 -0400 Subject: [PATCH 11/65] Clarify lambda func name --- diffpy/snmf/containers.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/diffpy/snmf/containers.py b/diffpy/snmf/containers.py index 04405154..1af960ad 100644 --- a/diffpy/snmf/containers.py +++ b/diffpy/snmf/containers.py @@ -40,13 +40,13 @@ def apply_stretch(self, m): stretching operation, and one vector is the second derivative of the stretching operation. """ normalized_grid = np.arange(len(self.grid)) - func = lambda stretching_factor: np.interp( # noqa: E731 + interpolate_intensity = lambda stretching_factor: np.interp( # noqa: E731 normalized_grid / stretching_factor, normalized_grid, self.iq, left=0, right=0 ) - derivative_func = numdifftools.Derivative(func) + derivative_func = numdifftools.Derivative(interpolate_intensity) second_derivative_func = numdifftools.Derivative(derivative_func) - stretched_component = func(self.stretching_factors[m]) + stretched_component = interpolate_intensity(self.stretching_factors[m]) stretched_component_gra = derivative_func(self.stretching_factors[m]) stretched_component_hess = second_derivative_func(self.stretching_factors[m]) From fab47285553b79e93abdba09b29fb5e21664583b Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Tue, 6 Aug 2024 22:50:54 -0400 Subject: [PATCH 12/65] Track License, code of conduct, readme --- AUTHORS.rst | 7 ++- CHANGELOG.rst | 6 +- CODE_OF_CONDUCT.rst | 133 ++++++++++++++++++++++++++++++++++++++++++++ LICENSE.rst | 30 ++++++++++ README.rst | 121 ++++++++++++++++++++++++++++++++++++++++ 5 files changed, 291 insertions(+), 6 deletions(-) create mode 100644 CODE_OF_CONDUCT.rst create mode 100644 LICENSE.rst create mode 100644 README.rst diff --git a/AUTHORS.rst b/AUTHORS.rst index 0223b1aa..6736f65f 100644 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -1,9 +1,10 @@ Authors -==================== +======= -Ran Gu, Adeolu Ajayi, Qiang Du, Simon J.L. Billinge +Billinge Group and community contibutors. Contributors ------------ -For a full list of contributors, visit + +For a list of contributors, visit https://github.com/diffpy/diffpy.snmf/graphs/contributors diff --git a/CHANGELOG.rst b/CHANGELOG.rst index f01f1336..26694512 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,5 +1,5 @@ -=========== - Change Log -=========== +============= +Release Notes +============= .. current developments diff --git a/CODE_OF_CONDUCT.rst b/CODE_OF_CONDUCT.rst new file mode 100644 index 00000000..ff9c3561 --- /dev/null +++ b/CODE_OF_CONDUCT.rst @@ -0,0 +1,133 @@ +===================================== + Contributor Covenant Code of Conduct +===================================== + +Our Pledge +---------- + +We as members, contributors, and leaders pledge to make participation in our +community a harassment-free experience for everyone, regardless of age, body +size, visible or invisible disability, ethnicity, sex characteristics, gender +identity and expression, level of experience, education, socio-economic status, +nationality, personal appearance, race, caste, color, religion, or sexual +identity and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. + +Our Standards +------------- + +Examples of behavior that contributes to a positive environment for our +community include: + +* Demonstrating empathy and kindness toward other people +* Being respectful of differing opinions, viewpoints, and experiences +* Giving and gracefully accepting constructive feedback +* Accepting responsibility and apologizing to those affected by our mistakes, + and learning from the experience +* Focusing on what is best not just for us as individuals, but for the overall + community + +Examples of unacceptable behavior include: + +* The use of sexualized language or imagery, and sexual attention or advances of + any kind +* Trolling, insulting or derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or email address, + without their explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +Enforcement Responsibilities +---------------------------- + +Community leaders are responsible for clarifying and enforcing our standards of +acceptable behavior and will take appropriate and fair corrective action in +response to any behavior that they deem inappropriate, threatening, offensive, +or harmful. + +Community leaders have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, and will communicate reasons for moderation +decisions when appropriate. + +Scope +----- + +This Code of Conduct applies within all community spaces, and also applies when +an individual is officially representing the community in public spaces. +Examples of representing our community include using an official email address, +posting via an official social media account, or acting as an appointed +representative at an online or offline event. + +Enforcement +----------- + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported to the community leaders responsible for enforcement at +sb2896@columbia.edu. All complaints will be reviewed and investigated promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the +reporter of any incident. + +Enforcement Guidelines +---------------------- + +Community leaders will follow these Community Impact Guidelines in determining +the consequences for any action they deem in violation of this Code of Conduct: + +1. Correction +**************** + +**Community Impact**: Use of inappropriate language or other behavior deemed +unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing +clarity around the nature of the violation and an explanation of why the +behavior was inappropriate. A public apology may be requested. + +2. Warning +************* + +**Community Impact**: A violation through a single incident or series of +actions. + +**Consequence**: A warning with consequences for continued behavior. No +interaction with the people involved, including unsolicited interaction with +those enforcing the Code of Conduct, for a specified period of time. This +includes avoiding interactions in community spaces as well as external channels +like social media. Violating these terms may lead to a temporary or permanent +ban. + +3. Temporary Ban +****************** + +**Community Impact**: A serious violation of community standards, including +sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public +communication with the community for a specified period of time. No public or +private interaction with the people involved, including unsolicited interaction +with those enforcing the Code of Conduct, is allowed during this period. +Violating these terms may lead to a permanent ban. + +4. Permanent Ban +****************** + +**Community Impact**: Demonstrating a pattern of violation of community +standards, including sustained inappropriate behavior, harassment of an +individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within the +community. + +Attribution +----------- + +This Code of Conduct is adapted from the `Contributor Covenant `_. + +Community Impact Guidelines were inspired by `Mozilla's code of conduct enforcement ladder `_. + +For answers to common questions about this code of conduct, see the `FAQ `_. `Translations are available `_ diff --git a/LICENSE.rst b/LICENSE.rst new file mode 100644 index 00000000..95a04ac9 --- /dev/null +++ b/LICENSE.rst @@ -0,0 +1,30 @@ +BSD 3-Clause License + +Copyright (c) 2024, The Trustees of Columbia University +in the City of New York. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/README.rst b/README.rst new file mode 100644 index 00000000..fddcf3f4 --- /dev/null +++ b/README.rst @@ -0,0 +1,121 @@ +|Icon| |title|_ +=============== + +.. |title| replace:: diffpy.snmf +.. _title: https://diffpy.github.io/diffpy.snmf + +.. |Icon| image:: https://avatars.githubusercontent.com/diffpy + :target: https://diffpy.github.io/diffpy.snmf + :height: 100px + +|PyPi| |Forge| |PythonVersion| |PR| + +|CI| |Codecov| |Black| |Tracking| + +.. |Black| image:: https://img.shields.io/badge/code_style-black-black + :target: https://github.com/psf/black + +.. |CI| image:: https://github.com/diffpy/diffpy.snmf/actions/workflows/main.yml/badge.svg + :target: https://github.com/diffpy/diffpy.snmf/actions/workflows/main.yml + +.. |Codecov| image:: https://codecov.io/gh/diffpy/diffpy.snmf/branch/main/graph/badge.svg + :target: https://codecov.io/gh/diffpy/diffpy.snmf + +.. |Forge| image:: https://img.shields.io/conda/vn/conda-forge/diffpy.snmf + :target: https://anaconda.org/conda-forge/diffpy.snmf + +.. |PR| image:: https://img.shields.io/badge/PR-Welcome-29ab47ff + +.. |PyPi| image:: https://img.shields.io/pypi/v/diffpy.snmf + :target: https://pypi.org/project/diffpy.snmf/ + +.. |PythonVersion| image:: https://img.shields.io/pypi/pyversions/diffpy.snmf + :target: https://pypi.org/project/diffpy.snmf/ + +.. |Tracking| image:: https://img.shields.io/badge/issue_tracking-github-blue + :target: https://github.com/diffpy/diffpy.snmf/issues + +A python package implementing the stretched NMF algorithm. + +* LONGER DESCRIPTION HERE + +For more information about the diffpy.snmf library, please consult our `online documentation `_. + +Citation +-------- + +If you use diffpy.snmf in a scientific publication, we would like you to cite this package as + + diffpy.snmf Package, https://github.com/diffpy/diffpy.snmf + +Installation +------------ + +The preferred method is to use `Miniconda Python +`_ +and install from the "conda-forge" channel of Conda packages. + +To add "conda-forge" to the conda channels, run the following in a terminal. :: + + conda config --add channels conda-forge + +We want to install our packages in a suitable conda environment. +The following creates and activates a new environment named ``diffpy.snmf_env`` :: + + conda create -n diffpy.snmf_env python=3 + conda activate diffpy.snmf_env + +Then, to fully install ``diffpy.snmf`` in our active environment, run :: + + conda install diffpy.snmf + +Another option is to use ``pip`` to download and install the latest release from +`Python Package Index `_. +To install using ``pip`` into your ``diffpy.snmf_env`` environment, we will also have to install dependencies :: + + pip install -r https://raw.githubusercontent.com/diffpy/diffpy.snmf/main/requirements/run.txt + +and then install the package :: + + pip install diffpy.snmf + +If you prefer to install from sources, after installing the dependencies, obtain the source archive from +`GitHub `_. Once installed, ``cd`` into your ``diffpy.snmf`` directory +and run the following :: + + pip install . + +Support and Contribute +---------------------- + +`Diffpy user group `_ is the discussion forum for general questions and discussions about the use of diffpy.snmf. Please join the diffpy.snmf users community by joining the Google group. The diffpy.snmf project welcomes your expertise and enthusiasm! + +If you see a bug or want to request a feature, please `report it as an issue `_ and/or `submit a fix as a PR `_. You can also post it to the `Diffpy user group `_. + +Feel free to fork the project and contribute. To install diffpy.snmf +in a development mode, with its sources being directly used by Python +rather than copied to a package directory, use the following in the root +directory :: + + pip install -e . + +To ensure code quality and to prevent accidental commits into the default branch, please set up the use of our pre-commit +hooks. + +1. Install pre-commit in your working environment by running ``conda install pre-commit``. + +2. Initialize pre-commit (one time only) ``pre-commit install``. + +Thereafter your code will be linted by black and isort and checked against flake8 before you can commit. +If it fails by black or isort, just rerun and it should pass (black and isort will modify the files so should +pass after they are modified). If the flake8 test fails please see the error messages and fix them manually before +trying to commit again. + +Improvements and fixes are always appreciated. + +Before contribuing, please read our `Code of Conduct `_. + +Contact +------- + +For more information on diffpy.snmf please visit the project `web-page `_ or email Prof. Simon Billinge at sb2896@columbia.edu. From 5d3b42eae2a86c6316faca66caa2b2d34e20633a Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Tue, 6 Aug 2024 22:51:07 -0400 Subject: [PATCH 13/65] Track src --- src/diffpy/__init__.py | 24 + src/diffpy/snmf/__init__.py | 24 + src/diffpy/snmf/containers.py | 77 ++++ src/diffpy/snmf/factorizers.py | 31 ++ src/diffpy/snmf/io.py | 120 +++++ src/diffpy/snmf/optimizers.py | 55 +++ src/diffpy/snmf/polynomials.py | 35 ++ src/diffpy/snmf/stretchednmfapp.py | 60 +++ src/diffpy/snmf/subroutines.py | 494 ++++++++++++++++++++ src/diffpy/snmf/tests/__init__.py | 0 src/diffpy/snmf/tests/conftest.py | 19 + src/diffpy/snmf/tests/debug.py | 35 ++ src/diffpy/snmf/tests/run.py | 34 ++ src/diffpy/snmf/tests/test_containers.py | 110 +++++ src/diffpy/snmf/tests/test_factorizers.py | 21 + src/diffpy/snmf/tests/test_optimizers.py | 20 + src/diffpy/snmf/tests/test_polynomials.py | 27 ++ src/diffpy/snmf/tests/test_subroutines.py | 539 ++++++++++++++++++++++ src/diffpy/snmf/version.py | 26 ++ 19 files changed, 1751 insertions(+) create mode 100644 src/diffpy/__init__.py create mode 100644 src/diffpy/snmf/__init__.py create mode 100644 src/diffpy/snmf/containers.py create mode 100644 src/diffpy/snmf/factorizers.py create mode 100644 src/diffpy/snmf/io.py create mode 100644 src/diffpy/snmf/optimizers.py create mode 100644 src/diffpy/snmf/polynomials.py create mode 100644 src/diffpy/snmf/stretchednmfapp.py create mode 100644 src/diffpy/snmf/subroutines.py create mode 100644 src/diffpy/snmf/tests/__init__.py create mode 100644 src/diffpy/snmf/tests/conftest.py create mode 100644 src/diffpy/snmf/tests/debug.py create mode 100644 src/diffpy/snmf/tests/run.py create mode 100644 src/diffpy/snmf/tests/test_containers.py create mode 100644 src/diffpy/snmf/tests/test_factorizers.py create mode 100644 src/diffpy/snmf/tests/test_optimizers.py create mode 100644 src/diffpy/snmf/tests/test_polynomials.py create mode 100644 src/diffpy/snmf/tests/test_subroutines.py create mode 100644 src/diffpy/snmf/version.py diff --git a/src/diffpy/__init__.py b/src/diffpy/__init__.py new file mode 100644 index 00000000..8fdf7aff --- /dev/null +++ b/src/diffpy/__init__.py @@ -0,0 +1,24 @@ +#!/usr/bin/env python +############################################################################## +# +# (c) 2024 The Trustees of Columbia University in the City of New York. +# All rights reserved. +# +# File coded by: Billinge Group members and community contributors. +# +# See GitHub contributions for a more detailed list of contributors. +# https://github.com/diffpy/diffpy.snmf/graphs/contributors +# +# See LICENSE.rst for license information. +# +############################################################################## + +"""Blank namespace package for module diffpy.""" + + +from pkgutil import extend_path + +__path__ = extend_path(__path__, __name__) + +# End of file + diff --git a/src/diffpy/snmf/__init__.py b/src/diffpy/snmf/__init__.py new file mode 100644 index 00000000..9c7220bd --- /dev/null +++ b/src/diffpy/snmf/__init__.py @@ -0,0 +1,24 @@ +#!/usr/bin/env python +############################################################################## +# +# (c) 2024 The Trustees of Columbia University in the City of New York. +# All rights reserved. +# +# File coded by: Billinge Group members and community contributors. +# +# See GitHub contributions for a more detailed list of contributors. +# https://github.com/diffpy/diffpy.snmf/graphs/contributors +# +# See LICENSE.rst for license information. +# +############################################################################## + +"""A python package implementing the stretched NMF algorithm.""" + +# package version +from diffpy.snmf.version import __version__ + +# silence the pyflakes syntax checker +assert __version__ or True + +# End of file diff --git a/src/diffpy/snmf/containers.py b/src/diffpy/snmf/containers.py new file mode 100644 index 00000000..1af960ad --- /dev/null +++ b/src/diffpy/snmf/containers.py @@ -0,0 +1,77 @@ +import numdifftools +import numpy as np + + +class ComponentSignal: + """ + Attributes + ---------- + grid: 1d array of floats + The vector containing the grid points of the component. + iq: 1d array of floats + The intensity/g(r) values of the component. + weights: 1d array of floats + The vector containing the weight of the component signal for each signal. + stretching_factors: 1d array of floats + The vector containing the stretching factor for the component signal for each signal. + id: int + The number identifying the component. + """ + + def __init__(self, grid, number_of_signals, id_number, perturbation=1e-3): + self.grid = np.asarray(grid) + self.iq = np.random.rand(len(grid)) + self.weights = np.random.rand(number_of_signals) + self.stretching_factors = np.ones(number_of_signals) + np.random.randn(number_of_signals) * perturbation + self.id = int(id_number) + + def apply_stretch(self, m): + """Applies a stretching factor to a component + + Parameters + ---------- + m: int + The index specifying which stretching factor to apply + + Returns + ------- + tuple of 1d arrays + The tuple of vectors where one vector is the stretched component, one vector is the 1st derivative of the + stretching operation, and one vector is the second derivative of the stretching operation. + """ + normalized_grid = np.arange(len(self.grid)) + interpolate_intensity = lambda stretching_factor: np.interp( # noqa: E731 + normalized_grid / stretching_factor, normalized_grid, self.iq, left=0, right=0 + ) + derivative_func = numdifftools.Derivative(interpolate_intensity) + second_derivative_func = numdifftools.Derivative(derivative_func) + + stretched_component = interpolate_intensity(self.stretching_factors[m]) + stretched_component_gra = derivative_func(self.stretching_factors[m]) + stretched_component_hess = second_derivative_func(self.stretching_factors[m]) + + return ( + np.asarray(stretched_component), + np.asarray(stretched_component_gra), + np.asarray(stretched_component_hess), + ) + + def apply_weight(self, m, stretched_component=None): + """Applies as weight factor to a component signal. + + Parameters + ---------- + m: int + The index specifying with weight to apply + stretched_component: 1d array + The 1d array containing a stretched component. + + Returns + ------- + 1d array + The vector containing a component signal or stretched component signal with a weight factor applied. + """ + if stretched_component is None: + return self.iq * self.weights[m] + else: + return stretched_component * self.weights[m] diff --git a/src/diffpy/snmf/factorizers.py b/src/diffpy/snmf/factorizers.py new file mode 100644 index 00000000..b4620fd4 --- /dev/null +++ b/src/diffpy/snmf/factorizers.py @@ -0,0 +1,31 @@ +import numpy as np +import scipy.optimize + + +def lsqnonneg(stretched_component_matrix, target_signal): + """Finds the weights of stretched component signals under one-sided constraint. + + Solves ``argmin_x || Ax - b ||_2`` for ``x>=0`` where A is the stretched_component_matrix and b is the + target_signal vector. Finds the weights of component signals given undecomposed signal data and stretched + components under a one-sided constraint on the weights. + + Parameters + ---------- + stretched_component_matrix: 2d array like + The component matrix where each column contains a stretched component signal. Has dimensions R x C where R is + the length of the signal and C is the number of components. Does not need to be nonnegative. Corresponds with + 'A' from the objective function. + + target_signal: 1d array like + The signal that is used as reference against which weight factors will be determined. Any column from the + matrix of the entire, unfactorized input data could be used. Has length R. Does not need to be nonnegative. + Corresponds with 'b' from the objective function. + + Returns + ------- + 1d array like + The vector containing component signal weights at a moment. Has length C. + """ + stretched_component_matrix = np.asarray(stretched_component_matrix) + target_signal = np.asarray(target_signal) + return scipy.optimize.nnls(stretched_component_matrix, target_signal)[0] diff --git a/src/diffpy/snmf/io.py b/src/diffpy/snmf/io.py new file mode 100644 index 00000000..12eb1b23 --- /dev/null +++ b/src/diffpy/snmf/io.py @@ -0,0 +1,120 @@ +from pathlib import Path + +import numpy as np +import scipy.sparse + +from diffpy.utils.parsers.loaddata import loadData + + +def initialize_variables(data_input, number_of_components, data_type, sparsity=1, smoothness=1e18): + """Determines the variables and initial values used in the SNMF algorithm. + + Parameters + ---------- + data_input: 2d array like + The observed or simulated PDF or XRD data provided by the user. Has dimensions R x N where R is the signa + length and N is the number of PDF/XRD signals. + + number_of_components: int + The number of component signals the user would like to decompose 'data_input' into. + + data_type: str + The type of data the user has passed into the program. Can assume the value of 'PDF' or 'XRD.' + + sparsity: float, optional + The regularization parameter that behaves as the coefficient of a "sparseness" regularization term that + enhances the ability to decompose signals in the case of sparse data e.g. X-ray Diffraction data. + A non-zero value indicates sparsity in the data; greater magnitudes indicate greater amounts of sparsity. + + smoothness: float, optional + The regularization parameter that behaves as the coefficient of a "smoothness" term that ensures that + component signal weightings change smoothly with time. Assumes a default value of 1e18. + + Returns + ------- + dictionary + The collection of the names and values of the constants used in the algorithm. Contains the number of + observed PDF/XRD patterns, the length of each pattern, the type of the data, the number of components + the user would like to decompose the data into, an initial guess for the component matrix, and initial + guess for the weight factor matrix, an initial guess for the stretching factor matrix, a parameter + controlling smoothness of the solution, a parameter controlling sparseness of the solution, the matrix + representing the smoothness term, and a matrix used to construct a hessian matrix. + + """ + signal_length = data_input.shape[0] + number_of_signals = data_input.shape[1] + + diagonals = [ + np.ones(number_of_signals - 2), + -2 * np.ones(number_of_signals - 2), + np.ones(number_of_signals - 2), + ] + smoothness_term = 0.25 * scipy.sparse.diags( + diagonals, [0, 1, 2], shape=(number_of_signals - 2, number_of_signals) + ) + + hessian_helper_matrix = scipy.sparse.block_diag([smoothness_term.T @ smoothness_term] * number_of_components) + sequence = ( + np.arange(number_of_signals * number_of_components) + .reshape(number_of_components, number_of_signals) + .T.flatten() + ) + hessian_helper_matrix = hessian_helper_matrix[sequence, :][:, sequence] + + return { + "signal_length": signal_length, + "number_of_signals": number_of_signals, + "number_of_components": number_of_components, + "data_type": data_type, + "smoothness": smoothness, + "sparsity": sparsity, + "smoothness_term": smoothness_term, + "hessian_helper_matrix": hessian_helper_matrix, + } + + +def load_input_signals(file_path=None): + """Processes a directory of a series of PDF/XRD patterns into a usable format. + + Constructs a 2d array out of a directory of PDF/XRD patterns containing each files dependent variable + column in a new column. Constructs a 1d array containing the grid values. + + Parameters + ---------- + file_path: str or Path object, optional + The path to the directory containing the input XRD/PDF data. If no path is specified, defaults to the + current working directory. Accepts a string or a pathlib.Path object. Input data not on the same grid + as the first file read will be ignored. + + Returns + ------- + tuple + The tuple whose first element is an R x M 2d array made of PDF/XRD patterns as each column; R is the + length of the signal and M is the number of patterns. The tuple contains a 1d array containing the values + of the grid points as its second element; Has length R. + + """ + + if file_path is None: + directory_path = Path.cwd() + else: + directory_path = Path(file_path) + + values_list = [] + grid_list = [] + current_grid = [] + for item in directory_path.iterdir(): + if item.is_file(): + data = loadData(item.resolve()) + if current_grid and current_grid != data[:, 0]: + print(f"{item.name} was ignored as it is not on a compatible grid.") + continue + else: + grid_list.append(data[:, 0]) + current_grid = grid_list[-1] + values_list.append(data[:, 1]) + + grid_array = np.column_stack(grid_list) + grid_vector = np.unique(grid_array, axis=1) + values_array = np.column_stack(values_list) + return grid_vector, values_array diff --git a/src/diffpy/snmf/optimizers.py b/src/diffpy/snmf/optimizers.py new file mode 100644 index 00000000..4198907d --- /dev/null +++ b/src/diffpy/snmf/optimizers.py @@ -0,0 +1,55 @@ +import cvxpy +import numpy as np + + +def get_weights(stretched_component_gram_matrix, linear_coefficient, lower_bound, upper_bound): + """Finds the weights of stretched component signals under a two-sided constraint + + Solves min J(y) = (linear_coefficient)' * y + (1/2) * y' * (quadratic coefficient) * y where + lower_bound <= y <= upper_bound and stretched_component_gram_matrix is symmetric positive definite. + Finds the weightings of stretched component signals under a two-sided constraint. + + Parameters + ---------- + stretched_component_gram_matrix: 2d array like + The Gram matrix constructed from the stretched component matrix. It is a square positive definite matrix. + It has dimensions C x C where C is the number of component signals. Must be symmetric positive definite. + + linear_coefficient: 1d array like + The vector containing the product of the stretched component matrix and the transpose of the observed + data matrix. Has length C. + + lower_bound: 1d array like + The lower bound on the values of the output weights. Has the same dimensions of the function output. Each + element in 'lower_bound' determines the minimum value the corresponding element in the function output may + take. + + upper_bound: 1d array like + The upper bound on the values of the output weights. Has the same dimensions of the function output. Each + element in 'upper_bound' determines the maximum value the corresponding element in the function output may + take. + + Returns + ------- + 1d array like + The vector containing the weightings of the components needed to reconstruct a given input signal from the + input set. Has length C + + """ + stretched_component_gram_matrix = np.asarray(stretched_component_gram_matrix) + linear_coefficient = np.asarray(linear_coefficient) + upper_bound = np.asarray(upper_bound) + lower_bound = np.asarray(lower_bound) + + problem_size = max(linear_coefficient.shape) + solution_variable = cvxpy.Variable(problem_size) + + objective = cvxpy.Minimize( + linear_coefficient.T @ solution_variable + + 0.5 * cvxpy.quad_form(solution_variable, stretched_component_gram_matrix) + ) + constraints = [lower_bound <= solution_variable, solution_variable <= upper_bound] + + cvxpy.Problem(objective, constraints).solve() + + return solution_variable.value diff --git a/src/diffpy/snmf/polynomials.py b/src/diffpy/snmf/polynomials.py new file mode 100644 index 00000000..265be050 --- /dev/null +++ b/src/diffpy/snmf/polynomials.py @@ -0,0 +1,35 @@ +import numpy as np + + +def rooth(linear_coefficient, constant_term): + """ + Returns the largest real root of x^3+(linear_coefficient) * x + constant_term. If there are no real roots + return 0. + + Parameters + ---------- + linear_coefficient: nd array like of floats + The matrix coefficient of the linear term + constant_term: 0d array like, 1d array like of floats or scalar + The constant scalar term of the problem + + Returns + ------- + ndarray of floats + The largest real root of x^3+(linear_coefficient) * x + constant_term if roots are real, else + return 0 array + + + """ + linear_coefficient = np.asarray(linear_coefficient) + constant_term = np.asarray(constant_term) + solution = np.empty_like(linear_coefficient, dtype=np.float64) + + for index, value in np.ndenumerate(linear_coefficient): + inputs = [1, 0, value, constant_term] + roots = np.roots(inputs) + if ((constant_term / 2) ** 2 + (value / 3) ** 3) < 0: # Discriminant of depressed cubic equation + solution[index] = max(np.real(roots)) + else: + solution[index] = 0 + return solution diff --git a/src/diffpy/snmf/stretchednmfapp.py b/src/diffpy/snmf/stretchednmfapp.py new file mode 100644 index 00000000..7080e0c4 --- /dev/null +++ b/src/diffpy/snmf/stretchednmfapp.py @@ -0,0 +1,60 @@ +import argparse +from pathlib import Path + +from diffpy.snmf.io import initialize_variables, load_input_signals +from diffpy.snmf.subroutines import initialize_components, lift_data + +ALLOWED_DATA_TYPES = ["powder_diffraction", "pd", "pair_distribution_function", "pdf"] + + +def create_parser(): + parser = argparse.ArgumentParser( + prog="stretched_nmf", description="Stretched Nonnegative Matrix Factorization" + ) + parser.add_argument( + "-i", + "--input-directory", + type=str, + default=None, + help="Directory containing experimental data. Defaults to current working directory.", + ) + parser.add_argument( + "-o", + "--output-directory", + type=str, + help="The directory where the results will be written. Defaults to '/snmf_results'.", + ) + parser.add_argument( + "t", + "--data-type", + type=str, + default=None, + choices=ALLOWED_DATA_TYPES, + help="The type of the experimental data.", + ) + parser.add_argument( + "-l", + "--lift-factor", + type=float, + default=1, + help="The lifting factor. Data will be lifted by lifted_data = data + abs(min(data) * lift). Default 1.", + ) + parser.add_argument( + "number-of-components", + type=int, + help="The number of component signals for the NMF decomposition. Must be an integer greater than 0", + ) + parser.add_argument("-v", "--version", action="version", help="Print the software version number") + args = parser.parse_args() + return args + + +def main(): + args = create_parser() + if args.input_directory is None: + args.input_directory = Path.cwd() + grid, input_data = load_input_signals(args.input_directory) + lifted_input_data = lift_data(input_data, args.lift_factor) + variables = initialize_variables(lifted_input_data, args.number_of_components, args.data_type) + components = initialize_components(variables["number_of_components"], variables["number_of_signals"], grid) + return components diff --git a/src/diffpy/snmf/subroutines.py b/src/diffpy/snmf/subroutines.py new file mode 100644 index 00000000..e4f6a5f7 --- /dev/null +++ b/src/diffpy/snmf/subroutines.py @@ -0,0 +1,494 @@ +import numdifftools +import numpy as np + +from diffpy.snmf.containers import ComponentSignal +from diffpy.snmf.factorizers import lsqnonneg +from diffpy.snmf.optimizers import get_weights + + +def initialize_components(number_of_components, number_of_signals, grid_vector): + """Initializes ComponentSignals for each of the components in the decomposition. + + Parameters + ---------- + number_of_components: int + The number of component signals in the NMF decomposition + number_of_signals: int + grid_vector: 1d array + The grid of the user provided signals. + + Returns + ------- + tuple of ComponentSignal objects + The tuple containing `number_of_components` of initialized ComponentSignal objects. + """ + if number_of_components <= 0: + raise ValueError(f"Number of components = {number_of_components}. Number_of_components must be >= 1.") + components = list() + for component in range(number_of_components): + component = ComponentSignal(grid_vector, number_of_signals, component) + components.append(component) + return tuple(components) + + +def lift_data(data_input, lift=1): + """Lifts values of data_input. + + Adds 'lift' * the minimum value in data_input to data_input element-wise. + + Parameters + ---------- + data_input: 2d array like + The matrix containing a series of signals to be decomposed. Has dimensions N x M where N is the length + of each signal and M is the number of signals. + + lift: float + The factor representing how much to lift 'data_input'. + + Returns + ------- + 2d array like + The matrix that contains data_input - (min(data_input) * lift). + """ + data_input = np.asarray(data_input) + return data_input + np.abs(np.min(data_input) * lift) + + +def construct_stretching_matrix(components, number_of_components, number_of_signals): + """Constructs the stretching factor matrix. + + Parameters + ---------- + components: tuple of ComponentSignal objects + The tuple containing the component signals in ComponentSignal objects. + number_of_signals: int + The number of signals in the data provided by the user. + + Returns + ------- + 2d array + The matrix containing the stretching factors for the component signals for each of the signals in the + raw data. Has dimensions `component_signal` x `number_of_signals` + """ + if (len(components)) == 0: + raise ValueError(f"Number of components = {number_of_components}. Number_of_components must be >= 1.") + number_of_components = len(components) + + if number_of_signals <= 0: + raise ValueError(f"Number of signals = {number_of_signals}. Number_of_signals must be >= 1.") + + stretching_factor_matrix = np.zeros((number_of_components, number_of_signals)) + for i, component in enumerate(components): + stretching_factor_matrix[i] = component.stretching_factors + return stretching_factor_matrix + + +def construct_component_matrix(components): + """Constructs the component matrix. + + Parameters + ---------- + components: tuple of ComponentSignal objects + The tuple containing the component signals in ComponentSignal objects. + + Returns + ------- + 2d array + The matrix containing the component signal values. Has dimensions `signal_length` x `number_of_components`. + """ + signal_length = len(components[0].iq) + number_of_components = len(components) + if signal_length == 0: + raise ValueError(f"Signal length = {signal_length}. Signal length must be >= 1") + if number_of_components == 0: + raise ValueError(f"Number of components = {number_of_components}. Number_of_components must be >= 1") + + component_matrix = np.zeros((number_of_components, signal_length)) + for i, component in enumerate(components): + component_matrix[i] = component.iq + return component_matrix + + +def construct_weight_matrix(components): + """Constructs the weights matrix. + + Constructs a Ķ x M matrix where K is the number of components and M is the + number of signals. Each element is the stretching factor for a specific + weights for a specific signal from the data input. + + Parameters + ---------- + components: tuple of ComponentSignal objects + The tuple containing the component signals. + + Returns + ------- + 2d array like + The 2d array containing the weightings for each component for each signal. + """ + number_of_components = len(components) + number_of_signals = len(components[0].weights) + if number_of_components == 0: + raise ValueError(f"Number of components = {number_of_components}. Number of components must be >= 1") + if number_of_signals == 0: + raise ValueError(f"Number of signals = {number_of_signals}. Number_of_signals must be >= 1.") + weights_matrix = np.zeros((number_of_components, number_of_signals)) + for i, component in enumerate(components): + weights_matrix[i] = component.weights + return weights_matrix + + +def update_weights(components, data_input, method=None): + """Updates the weights matrix. + + Updates the weights matrix and the weights vector for each ComponentSignal object. + + Parameters + ---------- + components: tuple of ComponentSignal objects + The tuple containing the component signals. + method: str + The string specifying which method should be used to find a new weight matrix: non-negative least squares + or a quadratic program. + data_input: 2d array + The 2d array containing the user-provided signals. + + Returns + ------- + 2d array + The 2d array containing the weight factors for each component for each signal from `data_input`. + Has dimensions K x M where K is the number of components and M is the number of signals in `data_input.` + """ + data_input = np.asarray(data_input) + weight_matrix = construct_weight_matrix(components) + number_of_signals = len(components[0].weights) + number_of_components = len(components) + signal_length = len(components[0].grid) + for signal in range(number_of_signals): + stretched_components = np.zeros((signal_length, number_of_components)) + for i, component in enumerate(components): + stretched_components[:, i] = component.apply_stretch(signal)[0] + if method == "align": + weights = lsqnonneg(stretched_components, data_input[:, signal]) + else: + weights = get_weights( + stretched_components.T @ stretched_components, + -stretched_components.T @ data_input[:, signal], + 0, + 1, + ) + weight_matrix[:, signal] = weights + return weight_matrix + + +def reconstruct_signal(components, signal_idx): + """Reconstructs a specific signal from its weighted and stretched components. + + Calculates the linear combination of stretched components where each term is the stretched component multiplied + by its weight factor. + + Parameters + ---------- + components: tuple of ComponentSignal objects + The tuple containing the ComponentSignal objects + signal_idx: int + The index of the specific signal in the input data to be reconstructed + + Returns + ------- + 1d array like + The reconstruction of a signal from calculated weights, stretching factors, and iq values. + """ + signal_length = len(components[0].grid) + reconstruction = np.zeros(signal_length) + for component in components: + stretched = component.apply_stretch(signal_idx)[0] + stretched_and_weighted = component.apply_weight(signal_idx, stretched) + reconstruction += stretched_and_weighted + return reconstruction + + +def initialize_arrays(number_of_components, number_of_moments, signal_length): + """Generates the initial guesses for the weight, stretching, and component matrices. + + Calculates the initial guesses for the component matrix, stretching factor matrix, and weight matrix. The + initial guess for the component matrix is a random (signal_length) x (number_of_components) matrix where + each element is between 0 and 1. The initial stretching factor matrix is a random + (number_of_components) x (number_of_moments) matrix where each element is number slightly perturbed from 1. + The initial weight matrix guess is a random (number_of_components) x (number_of_moments) matrix where + each element is between 0 and 1. + + Parameters + ---------- + number_of_components: int + The number of component signals to obtain from the stretched nmf decomposition. + + number_of_moments: int + The number of signals in the user provided dataset where each signal is at a different moment. + + signal_length: int + The length of each signal in the user provided dataset. + + Returns + ------- + tuple of 2d arrays of floats + The tuple containing three elements: the initial component matrix guess, the initial stretching factor matrix + guess, and the initial weight factor matrix guess in that order. + """ + component_matrix_guess = np.random.rand(signal_length, number_of_components) + weight_matrix_guess = np.random.rand(number_of_components, number_of_moments) + stretching_matrix_guess = ( + np.ones(number_of_components, number_of_moments) + + np.random.randn(number_of_components, number_of_moments) * 1e-3 + ) + return component_matrix_guess, weight_matrix_guess, stretching_matrix_guess + + +def objective_function( + residual_matrix, stretching_factor_matrix, smoothness, smoothness_term, component_matrix, sparsity +): + """Defines the objective function of the algorithm and returns its value. + + Calculates the value of '(||residual_matrix||_F) ** 2 + smoothness * (||smoothness_term * + stretching_factor_matrix.T||)**2 + sparsity * sum(component_matrix ** .5)' and returns its value. + + Parameters + ---------- + residual_matrix: 2d array like + The matrix where each column is the difference between an experimental PDF/XRD pattern and a calculated + PDF/XRD pattern at each grid point. Has dimensions R x M where R is the length of each pattern and M is + the amount of patterns. + + stretching_factor_matrix: 2d array like + The matrix containing the stretching factors of the calculated component signal. Has dimensions K x M where + K is the amount of components and M is the number of experimental PDF/XRD patterns. + + smoothness: float + The coefficient of the smoothness term which determines the intensity of the smoothness term and its + behavior. It is not very sensitive and is usually adjusted by multiplying it by ten. + + smoothness_term: 2d array like + The regularization term that ensures that smooth changes in the component stretching signals are favored. + Has dimensions (M-2) x M where M is the amount of experimentally obtained PDF/XRD patterns, the moment + amount. + + component_matrix: 2d array like + The matrix containing the calculated component signals of the experimental PDF/XRD patterns. Has dimensions + R x K where R is the signal length and K is the number of component signals. + + sparsity: float + The parameter determining the intensity of the sparsity regularization term which enables the algorithm to + exploit the sparse nature of XRD data. It is usually adjusted by doubling. + + Returns + ------- + float + The value of the objective function. + """ + residual_matrix = np.asarray(residual_matrix) + stretching_factor_matrix = np.asarray(stretching_factor_matrix) + component_matrix = np.asarray(component_matrix) + return ( + 0.5 * np.linalg.norm(residual_matrix, "fro") ** 2 + + 0.5 * smoothness * np.linalg.norm(smoothness_term @ stretching_factor_matrix.T, "fro") ** 2 + + sparsity * np.sum(np.sqrt(component_matrix)) + ) + + +def get_stretched_component(stretching_factor, component, signal_length): + """Applies a stretching factor to a component signal. + + Computes a stretched signal and reinterpolates it onto the original grid of points. Uses a normalized grid + of evenly spaced integers counting from 0 to signal_length (exclusive) to approximate values in between grid + nodes. Once this grid is stretched, values at grid nodes past the unstretched signal's domain are set to zero. + Returns the approximate values of x(r/a) from x(r) where x is a component signal. + + Parameters + ---------- + stretching_factor: float + The stretching factor of a component signal at a particular moment. + component: 1d array like + The calculated component signal without stretching or weighting. Has length N, the length of the signal. + signal_length: int + The length of the component signal. + + Returns + ------- + tuple of 1d array of floats + The calculated component signal with stretching factors applied. Has length N, the length of the unstretched + component signal. Also returns the gradient and hessian of the stretching transformation. + """ + component = np.asarray(component) + normalized_grid = np.arange(signal_length) + + def stretched_component_func(stretching_factor): + return np.interp(normalized_grid / stretching_factor, normalized_grid, component, left=0, right=0) + + derivative_func = numdifftools.Derivative(stretched_component_func) + second_derivative_func = numdifftools.Derivative(derivative_func) + + stretched_component = stretched_component_func(stretching_factor) + stretched_component_gra = derivative_func(stretching_factor) + stretched_component_hess = second_derivative_func(stretching_factor) + + return ( + np.asarray(stretched_component), + np.asarray(stretched_component_gra), + np.asarray(stretched_component_hess), + ) + + +def update_weights_matrix( + component_amount, + signal_length, + stretching_factor_matrix, + component_matrix, + data_input, + moment_amount, + weights_matrix, + method, +): + """Update the weight factors matrix. + + Parameters + ---------- + component_amount: int + The number of component signals the user would like to determine from the experimental data. + + signal_length: int + The length of the experimental signal patterns + + stretching_factor_matrix: 2d array like + The matrx containing the stretching factors of the calculated component signals. Has dimensions K x M + where K is the number of component signals and M is the number of XRD/PDF patterns. + + component_matrix: 2d array lik + The matrix containing the unstretched calculated component signals. Has dimensions N x K where N is the + length of the signals and K is the number of component signals. + + data_input: 2d array like + The experimental series of PDF/XRD patterns. Has dimensions N x M where N is the length of the PDF/XRD + signals and M is the number of PDF/XRD patterns. + + moment_amount: int + The number of PDF/XRD patterns from the experimental data. + + weights_matrix: 2d array like + The matrix containing the weights of the stretched component signals. Has dimensions K x M where K is + the number of component signals and M is the number of XRD/PDF patterns. + + method: str + The string specifying the method for obtaining individual weights. + + Returns + ------- + 2d array like + The matrix containing the new weight factors of the stretched component signals. + """ + stretching_factor_matrix = np.asarray(stretching_factor_matrix) + component_matrix = np.asarray(component_matrix) + data_input = np.asarray(data_input) + weights_matrix = np.asarray(weights_matrix) + weight = np.zeros(component_amount) + for i in range(moment_amount): + stretched_components = np.zeros((signal_length, component_amount)) + for n in range(component_amount): + stretched_components[:, n] = get_stretched_component( + stretching_factor_matrix[n, i], component_matrix[:, n], signal_length + )[0] + if method == "align": + weight = lsqnonneg(stretched_components[0:signal_length, :], data_input[0:signal_length, i]) + else: + weight = get_weights( + stretched_components[0:signal_length, :].T @ stretched_components[0:signal_length, :], + -1 * stretched_components[0:signal_length, :].T @ data_input[0:signal_length, i], + 0, + 1, + ) + weights_matrix[:, i] = weight + return weights_matrix + + +def get_residual_matrix( + component_matrix, weights_matrix, stretching_matrix, data_input, moment_amount, component_amount, signal_length +): + """Obtains the residual matrix between the experimental data and calculated data. + + Calculates the difference between the experimental data and the reconstructed experimental data created from + the calculated components, weights, and stretching factors. For each experimental pattern, the stretched and + weighted components making up that pattern are subtracted. + + Parameters + ---------- + component_matrix: 2d array like + The matrix containing the calculated component signals. Has dimensions N x K where N is the length of the + signal and K is the number of calculated component signals. + + weights_matrix: 2d array like + The matrix containing the calculated weights of the stretched component signals. Has dimensions K x M where + K is the number of components and M is the number of moments or experimental PDF/XRD patterns. + + stretching_matrix: 2d array like + The matrix containing the calculated stretching factors of the calculated component signals. Has dimensions + K x M where K is the number of components and M is the number of moments or experimental PDF/XRD patterns. + + data_input: 2d array like + The matrix containing the experimental PDF/XRD data. Has dimensions N x M where N is the length of the + signals and M is the number of signal patterns. + + moment_amount: int + The number of patterns in the experimental data. Represents the number of moments in time in the data series + + component_amount: int + The number of component signals the user would like to obtain from the experimental data. + + signal_length: int + The length of the signals in the experimental data. + + + Returns + ------- + 2d array like + The matrix containing the residual between the experimental data and reconstructed data from calculated + values. Has dimensions N x M where N is the signal length and M is the number of moments. Each column + contains the difference between an experimental signal and a reconstruction of that signal from the + calculated weights, components, and stretching factors. + """ + component_matrix = np.asarray(component_matrix) + weights_matrix = np.asarray(weights_matrix) + stretching_matrix = np.asarray(stretching_matrix) + data_input = np.asarray(data_input) + residual_matrx = -1 * data_input + for m in range(moment_amount): + residual = residual_matrx[:, m] + for k in range(component_amount): + residual = ( + residual + + weights_matrix[k, m] + * get_stretched_component(stretching_matrix[k, m], component_matrix[:, k], signal_length)[0] + ) + residual_matrx[:, m] = residual + return residual_matrx + + +def reconstruct_data(components): + """Reconstructs the `input_data` matrix. + + Reconstructs the `input_data` matrix from calculated component signals, weights, and stretching factors. + + Parameters + ---------- + components: tuple of ComponentSignal objects + The tuple containing the component signals. + + Returns + ------- + 2d array + The 2d array containing the reconstruction of input_data. + """ + signal_length = len(components[0].iq) + number_of_signals = len(components[0].weights) + data_reconstruction = np.zeros((signal_length, number_of_signals)) + for signal in range(number_of_signals): + data_reconstruction[:, signal] = reconstruct_signal(components, signal) + return data_reconstruction diff --git a/src/diffpy/snmf/tests/__init__.py b/src/diffpy/snmf/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/diffpy/snmf/tests/conftest.py b/src/diffpy/snmf/tests/conftest.py new file mode 100644 index 00000000..e3b63139 --- /dev/null +++ b/src/diffpy/snmf/tests/conftest.py @@ -0,0 +1,19 @@ +import json +from pathlib import Path + +import pytest + + +@pytest.fixture +def user_filesystem(tmp_path): + base_dir = Path(tmp_path) + home_dir = base_dir / "home_dir" + home_dir.mkdir(parents=True, exist_ok=True) + cwd_dir = base_dir / "cwd_dir" + cwd_dir.mkdir(parents=True, exist_ok=True) + + home_config_data = {"username": "home_username", "email": "home@email.com"} + with open(home_dir / "diffpyconfig.json", "w") as f: + json.dump(home_config_data, f) + + yield tmp_path diff --git a/src/diffpy/snmf/tests/debug.py b/src/diffpy/snmf/tests/debug.py new file mode 100644 index 00000000..2b585b27 --- /dev/null +++ b/src/diffpy/snmf/tests/debug.py @@ -0,0 +1,35 @@ +#!/usr/bin/env python +############################################################################## +# +# (c) 2024 The Trustees of Columbia University in the City of New York. +# All rights reserved. +# +# File coded by: Billinge Group members and community contributors. +# +# See GitHub contributions for a more detailed list of contributors. +# https://github.com/diffpy/diffpy.snmf/graphs/contributors +# +# See LICENSE.rst for license information. +# +############################################################################## + +""" +Convenience module for debugging the unit tests using + +python -m diffpy.snmf.tests.debug + +Exceptions raised by failed tests or other errors are not caught. +""" + + +if __name__ == "__main__": + import sys + + from diffpy.snmf.tests import testsuite + + pattern = sys.argv[1] if len(sys.argv) > 1 else "" + suite = testsuite(pattern) + suite.debug() + + +# End of file diff --git a/src/diffpy/snmf/tests/run.py b/src/diffpy/snmf/tests/run.py new file mode 100644 index 00000000..02b30c9d --- /dev/null +++ b/src/diffpy/snmf/tests/run.py @@ -0,0 +1,34 @@ +#!/usr/bin/env python +############################################################################## +# +# (c) 2024 The Trustees of Columbia University in the City of New York. +# All rights reserved. +# +# File coded by: Billinge Group members and community contributors. +# +# See GitHub contributions for a more detailed list of contributors. +# https://github.com/diffpy/diffpy.snmf/graphs/contributors +# +# See LICENSE.rst for license information. +# +############################################################################## +"""Convenience module for executing all unit tests with +python -m diffpy.snmf.tests.run +""" + +import sys + +import pytest + +if __name__ == "__main__": + # show output results from every test function + args = ["-v"] + # show the message output for skipped and expected failure tests + if len(sys.argv) > 1: + args.extend(sys.argv[1:]) + print("pytest arguments: {}".format(args)) + # call pytest and exit with the return code from pytest + exit_res = pytest.main(args) + sys.exit(exit_res) + +# End of file diff --git a/src/diffpy/snmf/tests/test_containers.py b/src/diffpy/snmf/tests/test_containers.py new file mode 100644 index 00000000..1c78be02 --- /dev/null +++ b/src/diffpy/snmf/tests/test_containers.py @@ -0,0 +1,110 @@ +import numpy as np +import pytest + +from diffpy.snmf.containers import ComponentSignal + +tas = [ + ( + [np.arange(10), 3, 0, [6.55, 0.357, 8.49, 9.33, 6.78, 7.57, 7.43, 3.92, 6.55, 1.71], 0.25], + [ + [6.55, 6.78, 6.55, 0, 0, 0, 0, 0, 0, 0], + [0, 14.07893122, 35.36478086, 0, 0, 0, 0, 0, 0, 0], + [0, -19.92049156, 11.6931482, 0, 0, 0, 0, 0, 0, 0], + ], + ), + ( + [np.arange(5), 10, 0, [-11.47, -10.688, -8.095, -29.44, 14.38], 1.25], + [ + [-11.47, -10.8444, -9.1322, -16.633, -20.6760], + [0, -0.50048, -3.31904, 40.9824, -112.1792], + [0, 0.800768, 5.310464, -65.57184, 179.48672], + ], + ), + ( + [np.arange(5), 2, 0, [-11.47, -10.688, -8.095, -29.44, 14.38], 0.88], + [ + [-11.47, -10.3344, -13.9164, -11.5136, 0], + [0, -3.3484, 55.1265, -169.7572, 0], + [0, 7.609997, -125.2876, 385.81189, 0], + ], + ), + ( + [np.arange(10), 1, 2, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], 0.88], + [ + [1, 2.1364, 3.2727, 4.4091, 5.5455, 6.6818, 7.8182, 8.9545, 0, 0], + [0, -1.29, -2.58, -3.87, -5.165, -6.45, -7.74, -9.039, 0, 0], + [0, 2.93, 5.869, 8.084, 11.739, 14.674, 17.608, 20.5437, 0, 0], + ], + ), + ( + [ + np.arange(14), + 100, + 3, + [ + -2.9384, + -1.4623, + -2.0913, + 4.6304, + -1.2127, + 1.4737, + -0.3791, + 1.7506, + -1.5068, + -2.7625, + 0.9617, + -0.3494, + -0.3862, + 2.7960, + ], + 0.55, + ], + [ + [-2.9384, -1.9769, 0.9121, 0.6314, 0.8622, -2.4239, -0.2302, 1.9281, 0, 0, 0, 0, 0, 0], + [0, 2.07933, 38.632, 18.3748, 43.07305, -61.557, 26.005, -73.637, 0, 0, 0, 0, 0, 0], + [0, -7.56, -140.480, -66.81, -156.6293, 223.84, -94.564, 267.7734, 0, 0, 0, 0, 0, 0], + ], + ), + ( + [np.arange(11), 20, 4, [0, 0.25, 0.5, 0.75, 1, 1.25, 1.5, 1.75, 2, 2.25, 2.5], 0.987], + [ + [0, 0.2533, 0.5066, 0.7599, 1.0132, 1.2665, 1.5198, 1.7730, 2.0263, 2.2796, 0], + [0, -0.2566, -0.5132, -0.7699, -1.0265, -1.2831, -1.5398, -1.7964, -2.0530, -2.3097, 0], + [0, 0.5200, 1.0400, 1.56005, 2.08007, 2.6000, 3.1201, 3.6401, 4.1601, 4.6801, 0], + ], + ), + ( + [np.arange(9), 15, 3, [-1, -2, -3, -4, -5, -6, -7, -8, -9], -0.4], + [[-1, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0]], + ), +] + + +@pytest.mark.parametrize("tas", tas) +def test_apply_stretch(tas): + component = ComponentSignal(tas[0][0], tas[0][1], tas[0][2]) + component.iq = tas[0][3] + component.stretching_factors[0] = tas[0][4] + actual = component.apply_stretch(0) + expected = tas[1] + np.testing.assert_allclose(actual, expected, rtol=1e-01) + + +taw = [ + ([np.arange(5), 2, 0, [0, 1, 2, 3, 4], 0.5], [0, 0.5, 1, 1.5, 2]), + ([np.arange(5), 20, 2, [0, -1, -2, -3, -4], 0.25], [0, -0.25, -0.5, -0.75, -1]), + ([np.arange(40), 200, 4, np.arange(0, 10, 0.25), 0.3], np.arange(0, 10, 0.25) * 0.3), + ([np.arange(1), 10, 2, [10.5, 11.5, -10.5], 0], [0, 0, 0]), + ([[-12, -10, -15], 5, 2, [-0.5, -1, -1.2], 0.9], [-0.45, -0.9, -1.08]), + ([[-12, -10, -15], 5, 2, [0, 0, 0], 0.9], [0, 0, 0]), +] + + +@pytest.mark.parametrize("taw", taw) +def test_apply_weight(taw): + component = ComponentSignal(taw[0][0], taw[0][1], taw[0][2]) + component.iq = np.array(taw[0][3]) + component.weights[0] = taw[0][4] + actual = component.apply_weight(0) + expected = taw[1] + np.testing.assert_allclose(actual, expected, rtol=1e-01) diff --git a/src/diffpy/snmf/tests/test_factorizers.py b/src/diffpy/snmf/tests/test_factorizers.py new file mode 100644 index 00000000..c20d1926 --- /dev/null +++ b/src/diffpy/snmf/tests/test_factorizers.py @@ -0,0 +1,21 @@ +import numpy as np +import pytest + +from diffpy.snmf.factorizers import lsqnonneg + +tl = [ + ([[[1, 0], [1, 0], [0, 1]], [2, 1, 1]], [1.5, 1.0]), + ([[[2, 3], [1, 2], [0, 0]], [7, 7, 2]], [0, 2.6923]), + ([[[3, 2, 4, 1]], [3.2]], [0, 0, 0.8, 0]), + ([[[-0.4, 0], [0, 0], [-9, -18]], [-2, -3, -4.9]], [0.5532, 0]), + ([[[-0.1, -0.2], [-0.8, -0.9]], [0, 0]], [0, 0]), + ([[[0, 0], [0, 0]], [10, 10]], [0, 0]), + ([[[2], [1], [-4], [-0.3]], [6, 4, 0.33, -5]], 0.767188240872451), +] + + +@pytest.mark.parametrize("tl", tl) +def test_lsqnonneg(tl): + actual = lsqnonneg(tl[0][0], tl[0][1]) + expected = tl[1] + np.testing.assert_array_almost_equal(actual, expected, decimal=4) diff --git a/src/diffpy/snmf/tests/test_optimizers.py b/src/diffpy/snmf/tests/test_optimizers.py new file mode 100644 index 00000000..ab7a71f4 --- /dev/null +++ b/src/diffpy/snmf/tests/test_optimizers.py @@ -0,0 +1,20 @@ +import pytest + +from diffpy.snmf.optimizers import get_weights + +tm = [ + ([[[1, 0], [0, 1]], [1, 1], [0, 0], [1, 1]], [0, 0]), + ([[[1, 0], [0, 1]], [1, 1], -1, 1], [-1, -1]), + ([[[1.75, 0], [0, 1.5]], [1, 1.2], -1, 1], [-0.571428571428571, -0.8]), + ([[[0.75, 0.2], [0.2, 0.75]], [-0.1, -0.2], -1, 1], [0.066985645933014, 0.248803827751196]), + ([[[2, -1, 0], [-1, 2, -1], [0, -1, 2]], [1, 1, 1], -10, 12], [-1.5, -2, -1.5]), + ([[[2, -1, 0], [-1, 2, -1], [0, -1, 2]], [1, -1, -1], -10, 12], [0, 1, 1]), + ([[[4, 0, 0, 0], [0, 3, 0, 0], [0, 0, 2, 0], [0, 0, 0, 1]], [-2, -3, -4, -1], 0, 1000], [0.5, 1, 2, 1]), +] + + +@pytest.mark.parametrize("tm", tm) +def test_get_weights(tm): + expected = tm[1] + actual = get_weights(tm[0][0], tm[0][1], tm[0][2], tm[0][3]) + assert actual == pytest.approx(expected, rel=1e-4, abs=1e-6) diff --git a/src/diffpy/snmf/tests/test_polynomials.py b/src/diffpy/snmf/tests/test_polynomials.py new file mode 100644 index 00000000..d9358341 --- /dev/null +++ b/src/diffpy/snmf/tests/test_polynomials.py @@ -0,0 +1,27 @@ +import numpy as np +import pytest + +from diffpy.snmf.polynomials import rooth + +tr = [ + ([0, 0], 0), + ([-99.99, 12.50], 9.936397678254531), + ([-4, 0], 2), + ([10, 0], 0), + ([-7, -7], 3.04891734), + ([100, 72], 0), + ([1, 3], 0), + ([0, -7], 0), + ([-9, 0], 3), + ([-9, 3], 2.8169), + ([[2, 2], 2], [0, 0]), + ([[[2, 2], [2, 2]], 2], [[0, 0], [0, 0]]), + ([[[[3, 2], [-2, -2], [100, 0]]], 2], [[[0, 0], [0, 0], [0, 0]]]), +] + + +@pytest.mark.parametrize("tr", tr) +def test_rooth(tr): + actual = rooth(tr[0][0], tr[0][1]) + expected = tr[1] + np.testing.assert_allclose(actual, expected, rtol=1e-4) diff --git a/src/diffpy/snmf/tests/test_subroutines.py b/src/diffpy/snmf/tests/test_subroutines.py new file mode 100644 index 00000000..66029d2e --- /dev/null +++ b/src/diffpy/snmf/tests/test_subroutines.py @@ -0,0 +1,539 @@ +import numpy as np +import pytest + +from diffpy.snmf.containers import ComponentSignal +from diffpy.snmf.subroutines import ( + construct_component_matrix, + construct_stretching_matrix, + construct_weight_matrix, + get_residual_matrix, + get_stretched_component, + initialize_components, + lift_data, + objective_function, + reconstruct_data, + reconstruct_signal, + update_weights, + update_weights_matrix, +) + +to = [ + ([[[1, 2], [3, 4]], [[5, 6], [7, 8]], 1e11, [[1, 2], [3, 4]], [[1, 2], [3, 4]], 1], 2.574e14), + ([[[11, 2], [31, 4]], [[5, 63], [7, 18]], 0.001, [[21, 2], [3, 4]], [[11, 22], [3, 40]], 1], 650.4576), + ([[[1, 2], [3, 4]], [[5, 6], [7, 8]], 1e11, [[1, 2], [3, 4]], [[1, 2], [3, 4]], 0], 2.574e14), +] + + +@pytest.mark.parametrize("to", to) +def test_objective_function(to): + actual = objective_function(to[0][0], to[0][1], to[0][2], to[0][3], to[0][4], to[0][5]) + expected = to[1] + assert actual == pytest.approx(expected) + + +tgso = [ + ( + [0.25, [6.55, 0.357, 8.49, 9.33, 6.78, 7.57, 7.43, 3.92, 6.55, 1.71], 10], + ( + [6.55, 6.78, 6.55, 0, 0, 0, 0, 0, 0, 0], + [0, 14.07893122, 35.36478086, 0, 0, 0, 0, 0, 0, 0], + [0, -19.92049156, 11.6931482, 0, 0, 0, 0, 0, 0, 0], + ), + ), + ( + [1.25, [-11.47, -10.688, -8.095, -29.44, 14.38], 5], + ( + [-11.47, -10.8444, -9.1322, -16.633, -20.6760], + [0, -0.50048, -3.31904, 40.9824, -112.1792], + [0, 0.800768, 5.310464, -65.57184, 179.48672], + ), + ), + ( + [0.88, [-11.47, -10.688, -8.095, -29.44, 14.38], 5], + ( + [-11.47, -10.3344, -13.9164, -11.5136, 0], + [0, -3.3484, 55.1265, -169.7572, 0], + [0, 7.609997, -125.2876, 385.81189, 0], + ), + ), + ( + [0.88, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], 10], + ( + [1, 2.1364, 3.2727, 4.4091, 5.5455, 6.6818, 7.8182, 8.9545, 0, 0], + [0, -1.29, -2.58, -3.87, -5.165, -6.45, -7.74, -9.039, 0, 0], + [0, 2.93, 5.869, 8.084, 11.739, 14.674, 17.608, 20.5437, 0, 0], + ), + ), + ( + [ + 0.55, + [ + -2.9384, + -1.4623, + -2.0913, + 4.6304, + -1.2127, + 1.4737, + -0.3791, + 1.7506, + -1.5068, + -2.7625, + 0.9617, + -0.3494, + -0.3862, + 2.7960, + ], + 14, + ], + ( + [-2.9384, -1.9769, 0.9121, 0.6314, 0.8622, -2.4239, -0.2302, 1.9281, 0, 0, 0, 0, 0, 0], + [0, 2.07933, 38.632, 18.3748, 43.07305, -61.557, 26.005, -73.637, 0, 0, 0, 0, 0, 0], + [0, -7.56, -140.480, -66.81, -156.6293, 223.84, -94.564, 267.7734, 0, 0, 0, 0, 0, 0], + ), + ), + ( + [0.987, [0, 0.25, 0.5, 0.75, 1, 1.25, 1.5, 1.75, 2, 2.25, 2.5], 11], + ( + [0, 0.2533, 0.5066, 0.7599, 1.0132, 1.2665, 1.5198, 1.7730, 2.0263, 2.2796, 0], + [0, -0.2566, -0.5132, -0.7699, -1.0265, -1.2831, -1.5398, -1.7964, -2.0530, -2.3097, 0], + [0, 0.5200, 1.0400, 1.56005, 2.08007, 2.6000, 3.1201, 3.6401, 4.1601, 4.6801, 0], + ), + ), + ( + [-0.4, [-1, -2, -3, -4, -5, -6, -7, -8, -9], 9], + ([-1, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0]), + ), +] + + +@pytest.mark.parametrize("tgso", tgso) +def test_get_stretched_component(tgso): + actual = get_stretched_component(tgso[0][0], tgso[0][1], tgso[0][2]) + expected = tgso[1] + np.testing.assert_allclose(actual, expected, rtol=1e-01) + + +tuwm = [ + ( + [ + 2, + 2, + [[0.5, 0.6], [0.7, 0.8]], + [[1, 2], [4, 8]], + [[1.6, 2.8], [5, 8.8]], + 2, + [[0.78, 0.12], [0.5, 0.5]], + None, + ], + [[0, 1], [1, 1]], + ), + ( + [2, 3, [[0.5], [0.5]], [[1, 2.5], [1.5, 3], [2, 3.5]], [[1, 2], [3, 4], [5, 6]], 1, [[0.5], [0.5]], None], + [[1], [0.1892]], + ), + ( + [ + 2, + 3, + [[0.5, 0.6, 0.7], [0.5, 0.6, 0.7]], + [[1, 2.5], [1.5, 3], [2, 3.5]], + [[1, 2, 3], [3, 4, 5], [5, 6, 7]], + 3, + [[0.5, 0.45, 0.4], [0.5, 0.45, 0.4]], + None, + ], + [[1, 1, 1], [0.1892, 0.5600, 0.938]], + ), + ( + [ + 3, + 3, + [[0.7, 0.8, 0.9], [0.71, 0.72, 0.73], [0.8, 0.85, 0.9]], + [[-1, -2.7, -3], [-11, -6, -5.1], [0, -1, -0.5]], + [[-2, -3, -4], [-9, -5, -5], [0, -2, -1]], + 3, + [[0.9, 0.4, 0.5], [1, 0, 0.4], [0, 0, 0.98]], + None, + ], + [[1, 0.0651, 0], [0.5848, 0.0381, 0.1857], [0, 1, 1]], + ), + ([2, 2, [[0.5], [0.5]], [[0, 0], [0, 0]], [[0, 0], [0, 0]], 1, [[0.6], [0.4]], "align"], [[0], [0]]), + ([1, 3, [[0.5, 0.3]], [[1], [1.1], [1.3]], [[1, 2], [2, 3], [3, 2]], 2, [[0.6, 0.4]], None], [[1, 1]]), + ( + [ + 2, + 2, + [[0.5, 0.6], [0.7, 0.8]], + [[1, 2], [4, 8]], + [[1.6, 2.8], [5, 8.8]], + 2, + [[0.78, 0.12], [0.5, 0.5]], + "align", + ], + [[0, 0], [1.0466, 1.46]], + ), + ( + [ + 2, + 3, + [[0.5], [0.5]], + [[1, 2.5], [1.5, 3], [2, 3.5]], + [[1, 2], [3, 4], [5, 6]], + 1, + [[0.5], [0.5]], + "align", + ], + [[1.4], [0]], + ), + ( + [ + 3, + 3, + [[0.7, 0.8, 0.9], [0.71, 0.72, 0.73], [0.8, 0.85, 0.9]], + [[-1, -2.7, -3], [-11, -6, -5.1], [0, -1, -0.5]], + [[-2, -3, -4], [-9, -5, -5], [0, -2, -1]], + 3, + [[0.9, 0.4, 0.5], [1, 0, 0.4], [0, 0, 0.98]], + "align", + ], + [[1.2605, 0.0552, 0], [0.2723, 0, 0], [0, 1.0538, 1.1696]], + ), + ([2, 2, [[0.5], [0.5]], [[0, 0], [0, 0]], [[0, 0], [0, 0]], 1, [[0.6], [0.4]], "align"], [[0], [0]]), + ([1, 3, [[0.5, 0.3]], [[1], [1.1], [1.3]], [[1, 2], [2, 3], [3, 2]], 2, [[0.6, 0.4]], "align"], [[1.3383, 2]]), +] + + +@pytest.mark.parametrize("tuwm", tuwm) +def test_update_weights_matrix(tuwm): + actual = update_weights_matrix( + tuwm[0][0], tuwm[0][1], tuwm[0][2], tuwm[0][3], tuwm[0][4], tuwm[0][5], tuwm[0][6], tuwm[0][7] + ) + expected = tuwm[1] + np.testing.assert_allclose(actual, expected, rtol=1e-03, atol=0.5) + + +tgrm = [ + ([[[1, 2], [3, 4]], [[0.25], [0.75]], [[0.9], [0.7]], [[11, 22], [33, 44]], 1, 2, 2], [[-9, -22], [-33, -44]]), + ([[[1, 2], [3, 4]], [[1], [1]], [[1], [1]], [[11, 22], [33, 44]], 1, 2, 2], [[-8, -22], [-26, -44]]), + ( + [ + [[1.1, 4.4], [1.2, 4.5], [14, 7.8]], + [[0.4, 0.6], [0.75, 0.25]], + [[0.9, 0.89], [0.98, 0.88]], + [[10, 20], [-10.5, -20.6], [0.6, 0.9]], + 2, + 2, + 3, + ], + [[-6.26, -18.24], [14.9744, 23.5067], [-0.6, -0.9]], + ), + # positive float + ( + [ + [[-1.1, -4.4], [-1.2, -4.5], [-14, -7.8]], + [[0.4, 0.6], [0.75, 0.25]], + [[0.9, 0.89], [0.98, 0.88]], + [[10, 20], [-10.5, -20.6], [0.6, 0.9]], + 2, + 2, + 3, + ], + [[-13.74, -21.76], [6.0256, 17.6933], [-0.6, -0.9]], + ), + # negative floats + ( + [ + [[0, 0, 0, 0], [0, 0, 0, 0]], + [[0.4], [0.2], [0.3], [0.3]], + [[0.9], [0.9], [0.9], [0.9]], + [[0, 0, 0, 0], [0, 0, 0, 0]], + 1, + 4, + 2, + ], + [[0, 0, 0, 0], [0, 0, 0, 0]], + ), +] + + +@pytest.mark.parametrize("tgrm", tgrm) +def test_get_residual_matrix(tgrm): + actual = get_residual_matrix( + tgrm[0][0], tgrm[0][1], tgrm[0][2], tgrm[0][3], tgrm[0][4], tgrm[0][5], tgrm[0][6] + ) + expected = tgrm[1] + np.testing.assert_allclose(actual, expected, rtol=1e-04) + + +trd = [ + ( + [ + ComponentSignal([0, 0.25, 0.5, 0.75, 1], 2, 0), + ComponentSignal([0, 0.25, 0.5, 0.75, 1], 2, 1), + ComponentSignal([0, 0.25, 0.5, 0.75, 1], 2, 2), + ] + ), + ([ComponentSignal([0, 0.25, 0.5, 0.75, 1], 2, 0)]), + ( + [ + ComponentSignal([0, 0.25, 0.5, 0.75, 1], 2, 0), + ComponentSignal([0, 0.25, 0.5, 0.75, 1], 2, 1), + ComponentSignal([0, 0.25, 0.5, 0.75, 1], 2, 2), + ComponentSignal([0, 0.25, 0.5, 0.75, 1], 2, 3), + ComponentSignal([0, 0.25, 0.5, 0.75, 1], 2, 4), + ] + ), + # ([]) # Exception expected +] + + +@pytest.mark.parametrize("trd", trd) +def test_reconstruct_data(trd): + actual = reconstruct_data(trd) + assert actual.shape == (len(trd[0].iq), len(trd[0].weights)) + print(actual) + + +tld = [ + (([[[1, -1, 1], [0, 0, 0], [2, 10, -3]], 1]), ([[4, 2, 4], [3, 3, 3], [5, 13, 0]])), + (([[[1, -1, 1], [0, 0, 0], [2, 10, -3]], 0]), ([[1, -1, 1], [0, 0, 0], [2, 10, -3]])), + (([[[1, -1, 1], [0, 0, 0], [2, 10, -3]], 0.5]), ([[2.5, 0.5, 2.5], [1.5, 1.5, 1.5], [3.5, 11.5, -1.5]])), + (([[[1, -1, 1], [0, 0, 0], [2, 10, -3]], -1]), ([[4, 2, 4], [3, 3, 3], [5, 13, 0]])), + (([[[0, 0, 0], [0, 0, 0], [0, 0, 0]], 100]), ([[0, 0, 0], [0, 0, 0], [0, 0, 0]])), + (([[[1.5, 2], [10.5, 1], [0.5, 2]], 1]), ([[2, 2.5], [11, 1.5], [1, 2.5]])), + (([[[-10, -10.5], [-12.2, -12.2], [0, 0]], 1]), ([[2.2, 1.7], [0, 0], [12.2, 12.2]])), +] + + +@pytest.mark.parametrize("tld", tld) +def test_lift_data(tld): + actual = lift_data(tld[0][0], tld[0][1]) + expected = tld[1] + np.testing.assert_allclose(actual, expected) + + +tcc = [ + (2, 3, [0, 0.5, 1, 1.5]), # Regular usage + # (0, 3,[0, .5, 1, 1.5]), # Zero components raise an exception. Not tested +] + + +@pytest.mark.parametrize("tcc", tcc) +def test_initialize_components(tcc): + actual = initialize_components(tcc[0], tcc[1], tcc[2]) + assert len(actual) == tcc[0] + assert len(actual[0].weights) == tcc[1] + assert (actual[0].grid == np.array(tcc[2])).all() + + +tcso = [ + ([ComponentSignal([0, 0.5, 1, 1.5], 20, 0)], 1, 20), + ([ComponentSignal([0, 0.5, 1, 1.5], 20, 0)], 4, 20), + # ([ComponentSignal([0,.5,1,1.5],20,0)],0,20), # Raises an exception + # ([ComponentSignal([0,.5,1,1.5],20,0)],-2,20), # Raises an exception + # ([ComponentSignal([0,.5,1,1.5],20,0)],1,0), # Raises an Exception + # ([ComponentSignal([0,.5,1,1.5],20,0)],1,-3), # Raises an exception + ([ComponentSignal([0, 0.5, 1, 1.5], 20, 0), ComponentSignal([0, 0.5, 1, 1.5], 20, 1)], 2, 20), + ([ComponentSignal([0, 0.5, 1, 1.5], 20, 0), ComponentSignal([0, 0.5, 1, 21.5], 20, 1)], 2, 20), + ([ComponentSignal([0, 1, 1.5], 20, 0), ComponentSignal([0, 0.5, 1, 21.5], 20, 1)], 2, 20), + # ([ComponentSignal([0,.5,1,1.5],20,0),ComponentSignal([0,.5,1,1.5],20,1)],1,-3), + # Negative signal length. Raises an exception + # ([],1,20), # Empty components. Raises an Exception + # ([],-1,20), # Empty components with negative number of components. Raises an exception + # ([],0,20), # Empty components with zero number of components. Raises an exception + # ([],1,0), # Empty components with zero signal length. Raises an exception. + # ([],-1,-2), # Empty components with negative number of components and signal length Raises an exception. +] + + +@pytest.mark.parametrize("tcso", tcso) +def test_construct_stretching_matrix(tcso): + actual = construct_stretching_matrix(tcso[0], tcso[1], tcso[2]) + for component in tcso[0]: + np.testing.assert_allclose(actual[component.id, :], component.stretching_factors) + # assert actual[component.id, :] == component.stretching_factors + + +tccm = [ + ([ComponentSignal([0, 0.25, 0.5, 0.75, 1], 20, 0)]), + ([ComponentSignal([0, 0.25, 0.5, 0.75, 1], 0, 0)]), + ( + [ + ComponentSignal([0, 0.25, 0.5, 0.75, 1], 20, 0), + ComponentSignal([0, 0.25, 0.5, 0.75, 1], 20, 1), + ComponentSignal([0, 0.25, 0.5, 0.75, 1], 20, 2), + ] + ), + ( + [ + ComponentSignal([0, 0.25, 0.5, 0.75, 1], 20, 0), + ComponentSignal([0, 0.25, 0.5, 0.75, 1], 20, 1), + ComponentSignal([0, 0.25, 0.5, 0.75, 1], 20, 2), + ] + ), + ( + [ + ComponentSignal([0, 0.25, 0.5, 0.75, 1], 20, 0), + ComponentSignal([0, 0.25, 0.5, 2.75, 1], 20, 1), + ComponentSignal([0, 0.25, 0.5, 0.75, 1], 20, 2), + ] + ), + ([ComponentSignal([0.25], 20, 0), ComponentSignal([0.25], 20, 1), ComponentSignal([0.25], 20, 2)]), + ([ComponentSignal([0, 0.25, 0.5, 0.75, 1], 20, 0), ComponentSignal([0, 0.25, 0.5, 0.75, 1], 20, 1)]), + # ([ComponentSignal([[0, .25, .5, .75, 1],[0, .25, .5, .75, 1]], 20, 0), + # ComponentSignal([[0, .25, .5, .75, 1],[0, .25, .5, .75, 1]], 20, 1)]), + # iq is multidimensional. Expected to fail + # (ComponentSignal([], 20, 0)), # Expected to fail + # ([]), #Expected to fail +] + + +@pytest.mark.parametrize("tccm", tccm) +def test_construct_component_matrix(tccm): + actual = construct_component_matrix(tccm) + for component in tccm: + np.testing.assert_allclose(actual[component.id], component.iq) + + +tcwm = [ + ([ComponentSignal([0, 0.25, 0.5, 0.75, 1], 20, 0)]), + # ([ComponentSignal([0,.25,.5,.75,1],0,0)]), # 0 signal length. Failure expected + ( + [ + ComponentSignal([0, 0.25, 0.5, 0.75, 1], 20, 0), + ComponentSignal([0, 0.25, 0.5, 0.75, 1], 20, 1), + ComponentSignal([0, 0.25, 0.5, 0.75, 1], 20, 2), + ] + ), + ( + [ + ComponentSignal([0, 0.25, 0.5, 0.75, 1], 20, 0), + ComponentSignal([0, 0.25, 0.5, 0.75, 1], 20, 1), + ComponentSignal([0, 0.25, 0.5, 0.75, 1], 20, 2), + ] + ), + ( + [ + ComponentSignal([0, 0.25, 0.5, 0.75, 1], 20, 0), + ComponentSignal([0, 0.25, 0.5, 2.75, 1], 20, 1), + ComponentSignal([0, 0.25, 0.5, 0.75, 1], 20, 2), + ] + ), + ([ComponentSignal([0.25], 20, 0), ComponentSignal([0.25], 20, 1), ComponentSignal([0.25], 20, 2)]), + ([ComponentSignal([0, 0.25, 0.5, 0.75, 1], 20, 0), ComponentSignal([0, 0.25, 0.5, 0.75, 1], 20, 1)]), + # (ComponentSignal([], 20, 0)), # Expected to fail + # ([]), #Expected to fail +] + + +@pytest.mark.parametrize("tcwm", tcwm) +def test_construct_weight_matrix(tcwm): + actual = construct_weight_matrix(tcwm) + for component in tcwm: + np.testing.assert_allclose(actual[component.id], component.weights) + + +tuw = [ + ( + [ + ComponentSignal([0, 0.25, 0.5, 0.75, 1], 2, 0), + ComponentSignal([0, 0.25, 0.5, 0.75, 1], 2, 1), + ComponentSignal([0, 0.25, 0.5, 0.75, 1], 2, 2), + ], + [[1, 1], [1.2, 1.3], [1.3, 1.4], [1.4, 1.5], [2, 2.1]], + None, + ), + ( + [ + ComponentSignal([0, 0.25, 0.5, 0.75, 1], 2, 0), + ComponentSignal([0, 0.25, 0.5, 0.75, 1], 2, 1), + ComponentSignal([0, 0.25, 0.5, 0.75, 1], 2, 2), + ], + [[1, 1], [1.2, 1.3], [1.3, 1.4], [1.4, 1.5], [2, 2.1]], + "align", + ), + ( + [ + ComponentSignal([0, 0.25, 0.5, 0.75, 1], 2, 0), + ComponentSignal([0, 0.25, 0.5, 0.75, 1], 2, 1), + ComponentSignal([0, 0.25, 0.5, 0.75, 1], 2, 2), + ], + [[0, 0], [0, 0], [0, 0], [0, 0], [0, 0]], + None, + ), + ( + [ + ComponentSignal([0, 0.25, 0.5, 0.75, 1], 2, 0), + ComponentSignal([0, 0.25, 0.5, 0.75, 1], 2, 1), + ComponentSignal([0, 0.25, 0.5, 0.75, 1], 2, 2), + ], + [[0, 0], [0, 0], [0, 0], [0, 0], [0, 0]], + "align", + ), + ( + [ + ComponentSignal([0, 0.25, 0.5, 0.75, 1], 2, 0), + ComponentSignal([0, 0.25, 0.5, 0.75, 1], 2, 1), + ComponentSignal([0, 0.25, 0.5, 0.75, 1], 2, 2), + ], + [[-0.5, 1], [1.2, -1.3], [1.1, -1], [0, -1.5], [0, 0.1]], + None, + ), + ( + [ + ComponentSignal([0, 0.25, 0.5, 0.75, 1], 2, 0), + ComponentSignal([0, 0.25, 0.5, 0.75, 1], 2, 1), + ComponentSignal([0, 0.25, 0.5, 0.75, 1], 2, 2), + ], + [[-0.5, 1], [1.2, -1.3], [1.1, -1], [0, -1.5], [0, 0.1]], + "align", + ), + # ([ComponentSignal([0, .25, .5, .75, 1], 0, 0), ComponentSignal([0, .25, .5, .75, 1], 0, 1), + # ComponentSignal([0, .25, .5, .75, 1], 0, 2)], [[1, 1], [1.2, 1.3], [1.3, 1.4], [1.4, 1.5], [2, 2.1]], None), + # ([ComponentSignal([0, .25, .5, .75, 1], 0, 0), ComponentSignal([0, .25, .5, .75, 1], 0, 1), + # ComponentSignal([0, .25, .5, .75, 1], 0, 2)], [], None), + # ([ComponentSignal([0, .25, .5, .75, 1], 2, 0), ComponentSignal([0, .25, .5, .75, 1], 2, 1), + # ComponentSignal([0, .25, .5, .75, 1], 2, 2)], [], 170), +] + + +@pytest.mark.parametrize("tuw", tuw) +def test_update_weights(tuw): + actual = update_weights(tuw[0], tuw[1], tuw[2]) + assert np.shape(actual) == (len(tuw[0]), len(tuw[0][0].weights)) + + +trs = [ + ( + [ + ComponentSignal([0, 0.25, 0.5, 0.75, 1], 2, 0), + ComponentSignal([0, 0.25, 0.5, 0.75, 1], 2, 1), + ComponentSignal([0, 0.25, 0.5, 0.75, 1], 2, 2), + ], + 1, + ), + ( + [ + ComponentSignal([0, 0.25, 0.5, 0.75, 1], 2, 0), + ComponentSignal([0, 0.25, 0.5, 0.75, 1], 2, 1), + ComponentSignal([0, 0.25, 0.5, 0.75, 1], 2, 2), + ], + 0, + ), + ( + [ + ComponentSignal([0, 0.25, 0.5, 0.75, 1], 3, 0), + ComponentSignal([0, 0.25, 0.5, 0.75, 1], 3, 1), + ComponentSignal([0, 0.25, 0.5, 0.75, 1], 3, 2), + ], + 2, + ), + # ([ComponentSignal([0, .25, .5, .75, 1], 2, 0), ComponentSignal([0, .25, .5, .75, 1], 2, 1), + # ComponentSignal([0, .25, .5, .75, 1], 2, 2)], -1), +] + + +@pytest.mark.parametrize("trs", trs) +def test_reconstruct_signal(trs): + actual = reconstruct_signal(trs[0], trs[1]) + assert len(actual) == len(trs[0][0].grid) diff --git a/src/diffpy/snmf/version.py b/src/diffpy/snmf/version.py new file mode 100644 index 00000000..202bd54e --- /dev/null +++ b/src/diffpy/snmf/version.py @@ -0,0 +1,26 @@ +#!/usr/bin/env python +############################################################################## +# +# (c) 2024 The Trustees of Columbia University in the City of New York. +# All rights reserved. +# +# File coded by: Billinge Group members and community contributors. +# +# See GitHub contributions for a more detailed list of contributors. +# https://github.com/diffpy/diffpy.snmf/graphs/contributors +# +# See LICENSE.rst for license information. +# +############################################################################## + +"""Definition of __version__.""" + +# We do not use the other three variables, but can be added back if needed. +# __all__ = ["__date__", "__git_commit__", "__timestamp__", "__version__"] + +# obtain version information +from importlib.metadata import version + +__version__ = version("diffpy.snmf") + +# End of file From f4b83b25a716333d98dd81dbfb10c27e837da2cb Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Tue, 6 Aug 2024 22:51:34 -0400 Subject: [PATCH 14/65] Track requirement files --- requirements/README.txt | 11 +++++++++++ requirements/build.txt | 2 ++ requirements/docs.txt | 3 ++- requirements/pip.txt | 0 requirements/run.txt | 5 ----- 5 files changed, 15 insertions(+), 6 deletions(-) create mode 100644 requirements/README.txt create mode 100644 requirements/build.txt create mode 100644 requirements/pip.txt diff --git a/requirements/README.txt b/requirements/README.txt new file mode 100644 index 00000000..dc34909d --- /dev/null +++ b/requirements/README.txt @@ -0,0 +1,11 @@ +# YOU MAY DELETE THIS FILE AFTER SETTING UP DEPENDENCIES! +# +# This directory is where you should place your project dependencies. +# "pip.txt" should contain all required packages not available on conda. +# All other files should contain only packages available to download from conda. +# build.txt should contain all packages required to build (not run) the project. +# run.txt should contain all packages (including optional packages) required for a user to run the program. +# test.txt should contain all packages required for the testing suite and to ensure all tests pass. +# docs.txt should contain all packages required for building the package documentation page. +# +# YOU MAY DELETE THIS FILE AFTER SETTING UP DEPENDENCIES! diff --git a/requirements/build.txt b/requirements/build.txt new file mode 100644 index 00000000..f72d870d --- /dev/null +++ b/requirements/build.txt @@ -0,0 +1,2 @@ +python +setuptools diff --git a/requirements/docs.txt b/requirements/docs.txt index 8b087850..ab17b1c8 100644 --- a/requirements/docs.txt +++ b/requirements/docs.txt @@ -1,3 +1,4 @@ -make +sphinx sphinx_rtd_theme +doctr m2r diff --git a/requirements/pip.txt b/requirements/pip.txt new file mode 100644 index 00000000..e69de29b diff --git a/requirements/run.txt b/requirements/run.txt index 411cf1be..e69de29b 100644 --- a/requirements/run.txt +++ b/requirements/run.txt @@ -1,5 +0,0 @@ -numpy -scipy -cvxpy -diffpy.utils -numdifftools From 37b854738f8da3319732345496ea2bc46507c6aa Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Tue, 6 Aug 2024 22:52:01 -0400 Subject: [PATCH 15/65] Track doc source api and static, news --- doc/source/_static/.placeholder | 0 .../api/diffpy.snmf.example_package.rst | 31 +++++++++++++++++++ doc/source/api/diffpy.snmf.rst | 30 ++++++++++++++++++ news/TEMPLATE.rst | 23 ++++++++++++++ 4 files changed, 84 insertions(+) create mode 100644 doc/source/_static/.placeholder create mode 100644 doc/source/api/diffpy.snmf.example_package.rst create mode 100644 doc/source/api/diffpy.snmf.rst create mode 100644 news/TEMPLATE.rst diff --git a/doc/source/_static/.placeholder b/doc/source/_static/.placeholder new file mode 100644 index 00000000..e69de29b diff --git a/doc/source/api/diffpy.snmf.example_package.rst b/doc/source/api/diffpy.snmf.example_package.rst new file mode 100644 index 00000000..c815461a --- /dev/null +++ b/doc/source/api/diffpy.snmf.example_package.rst @@ -0,0 +1,31 @@ +.. _example_package documentation: + +|title| +======= + +.. |title| replace:: diffpy.snmf.example_package package + +.. automodule:: diffpy.snmf.example_package + :members: + :undoc-members: + :show-inheritance: + +|foo| +----- + +.. |foo| replace:: diffpy.snmf.example_package.foo module + +.. automodule:: diffpy.snmf.example_package.foo + :members: + :undoc-members: + :show-inheritance: + +|bar| +----- + +.. |bar| replace:: diffpy.snmf.example_package.bar module + +.. automodule:: diffpy.snmf.example_package.foo + :members: + :undoc-members: + :show-inheritance: diff --git a/doc/source/api/diffpy.snmf.rst b/doc/source/api/diffpy.snmf.rst new file mode 100644 index 00000000..28f90a6d --- /dev/null +++ b/doc/source/api/diffpy.snmf.rst @@ -0,0 +1,30 @@ +:tocdepth: -1 + +|title| +======= + +.. |title| replace:: diffpy.snmf package + +.. automodule:: diffpy.snmf + :members: + :undoc-members: + :show-inheritance: + +Subpackages +----------- + +.. toctree:: + diffpy.snmf.example_package + +Submodules +---------- + +|module| +-------- + +.. |module| replace:: diffpy.snmf.example_submodule module + +.. automodule:: diffpy.snmf.example_submodule + :members: + :undoc-members: + :show-inheritance: diff --git a/news/TEMPLATE.rst b/news/TEMPLATE.rst new file mode 100644 index 00000000..790d30b1 --- /dev/null +++ b/news/TEMPLATE.rst @@ -0,0 +1,23 @@ +**Added:** + +* + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* From fbc2163b6a0e1bc5794c3203bbd306fb1a77e9b6 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Tue, 6 Aug 2024 22:52:26 -0400 Subject: [PATCH 16/65] Track GH files --- .gitattributes | 1 + .github/workflows/docs.yml | 43 ++++++++++++++++++++++++++++++++ .github/workflows/pre-commit.yml | 19 ++++++++++++++ 3 files changed, 63 insertions(+) create mode 100644 .gitattributes create mode 100644 .github/workflows/docs.yml create mode 100644 .github/workflows/pre-commit.yml diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..42fce5f9 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +diffpy.snmf/_version.py export-subst diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 00000000..2909d67f --- /dev/null +++ b/.github/workflows/docs.yml @@ -0,0 +1,43 @@ +name: Build Documentation + +on: + push: + branches: + - main + release: + +jobs: + test: + runs-on: ubuntu-latest + defaults: + run: + shell: bash -l {0} + steps: + - uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - uses: conda-incubator/setup-miniconda@v2 + with: + activate-environment: build + auto-update-conda: true + + - name: install requirements + run: >- + conda install -n build -c conda-forge + --file requirements/build.txt + --file requirements/run.txt + --file requirements/docs.txt + --quiet --yes + + - name: install the package + run: python -m pip install . --no-deps + + - name: build documents + run: make -C doc html + + - name: Deploy + uses: peaceiris/actions-gh-pages@v4 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + publish_dir: ./doc/build/html diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml new file mode 100644 index 00000000..f2ff7e42 --- /dev/null +++ b/.github/workflows/pre-commit.yml @@ -0,0 +1,19 @@ +name: pre-commit + +on: + pull_request: + push: + workflow_dispatch: + +jobs: + pre-commit: + # pull requests are a duplicate of a branch push if within the same repo. + if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.repository + + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-python@v4 + - uses: pre-commit/action@v3.0.0 + with: + extra_args: --all-files From 54ad0c0cc1f660d3f280d79bfe1810a46e97b628 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Tue, 6 Aug 2024 22:52:47 -0400 Subject: [PATCH 17/65] Track cfg and MANIFEST.in files --- .isort.cfg | 4 ++++ MANIFEST.in | 14 ++++++++++++++ 2 files changed, 18 insertions(+) create mode 100644 .isort.cfg create mode 100644 MANIFEST.in diff --git a/.isort.cfg b/.isort.cfg new file mode 100644 index 00000000..e0926f42 --- /dev/null +++ b/.isort.cfg @@ -0,0 +1,4 @@ +[settings] +line_length = 115 +multi_line_output = 3 +include_trailing_comma = True diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 00000000..73710630 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,14 @@ +include AUTHORS.rst +include LICENSE +include README.rst +include requirements.txt + +recursive-exclude * __pycache__ +recursive-exclude * *.py[co] + +recursive-include docs *.rst conf.py Makefile make.bat + +include diffpy.snmf/version.py + +# If including data files in the package, add them like: +# include path/to/data_file From 548915e97f32ee1226f4d3acab6483e2f647166d Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 7 Aug 2024 02:59:19 +0000 Subject: [PATCH 18/65] [pre-commit.ci] auto fixes from pre-commit hooks --- src/diffpy/__init__.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/diffpy/__init__.py b/src/diffpy/__init__.py index 8fdf7aff..2baa1eaa 100644 --- a/src/diffpy/__init__.py +++ b/src/diffpy/__init__.py @@ -21,4 +21,3 @@ __path__ = extend_path(__path__, __name__) # End of file - From dbc8a650552f953dbad105dcbad2eb8931fb697a Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Wed, 7 Aug 2024 10:34:05 -0400 Subject: [PATCH 19/65] Add requirements to run.txt --- requirements/run.txt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/requirements/run.txt b/requirements/run.txt index e69de29b..411cf1be 100644 --- a/requirements/run.txt +++ b/requirements/run.txt @@ -0,0 +1,5 @@ +numpy +scipy +cvxpy +diffpy.utils +numdifftools From e2cb35df905343c1c46d8c02a2fb36529eacef46 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Wed, 7 Aug 2024 10:40:11 -0400 Subject: [PATCH 20/65] Remove requirements/README.txt --- requirements/README.txt | 11 ----------- 1 file changed, 11 deletions(-) delete mode 100644 requirements/README.txt diff --git a/requirements/README.txt b/requirements/README.txt deleted file mode 100644 index dc34909d..00000000 --- a/requirements/README.txt +++ /dev/null @@ -1,11 +0,0 @@ -# YOU MAY DELETE THIS FILE AFTER SETTING UP DEPENDENCIES! -# -# This directory is where you should place your project dependencies. -# "pip.txt" should contain all required packages not available on conda. -# All other files should contain only packages available to download from conda. -# build.txt should contain all packages required to build (not run) the project. -# run.txt should contain all packages (including optional packages) required for a user to run the program. -# test.txt should contain all packages required for the testing suite and to ensure all tests pass. -# docs.txt should contain all packages required for building the package documentation page. -# -# YOU MAY DELETE THIS FILE AFTER SETTING UP DEPENDENCIES! From 48e99dbd1172ebdfb67159d51e6d7898115cc5ea Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Wed, 7 Aug 2024 16:17:55 -0400 Subject: [PATCH 21/65] Remove one of diffpy dirs --- diffpy/__init__.py | 26 -- diffpy/snmf/__init__.py | 25 -- diffpy/snmf/containers.py | 77 ---- diffpy/snmf/factorizers.py | 31 -- diffpy/snmf/io.py | 120 ------ diffpy/snmf/optimizers.py | 55 --- diffpy/snmf/polynomials.py | 35 -- diffpy/snmf/stretchednmfapp.py | 60 --- diffpy/snmf/subroutines.py | 494 ----------------------- diffpy/snmf/tests/test_containers.py | 110 ------ diffpy/snmf/tests/test_factorizers.py | 21 - diffpy/snmf/tests/test_optimizers.py | 20 - diffpy/snmf/tests/test_polynomials.py | 27 -- diffpy/snmf/tests/test_subroutines.py | 539 -------------------------- diffpy/snmf/version.py | 28 -- 15 files changed, 1668 deletions(-) delete mode 100644 diffpy/__init__.py delete mode 100644 diffpy/snmf/__init__.py delete mode 100644 diffpy/snmf/containers.py delete mode 100644 diffpy/snmf/factorizers.py delete mode 100644 diffpy/snmf/io.py delete mode 100644 diffpy/snmf/optimizers.py delete mode 100644 diffpy/snmf/polynomials.py delete mode 100644 diffpy/snmf/stretchednmfapp.py delete mode 100644 diffpy/snmf/subroutines.py delete mode 100644 diffpy/snmf/tests/test_containers.py delete mode 100644 diffpy/snmf/tests/test_factorizers.py delete mode 100644 diffpy/snmf/tests/test_optimizers.py delete mode 100644 diffpy/snmf/tests/test_polynomials.py delete mode 100644 diffpy/snmf/tests/test_subroutines.py delete mode 100644 diffpy/snmf/version.py diff --git a/diffpy/__init__.py b/diffpy/__init__.py deleted file mode 100644 index 8fbb313c..00000000 --- a/diffpy/__init__.py +++ /dev/null @@ -1,26 +0,0 @@ -#!/usr/bin/env python -############################################################################## -# -# diffpy by DANSE Diffraction group -# Simon J. L. Billinge -# (c) 2008 Trustees of the Columbia University -# in the City of New York. All rights reserved. -# -# File coded by: Pavol Juhas -# -# See AUTHORS.txt for a list of people who contributed. -# See LICENSE.txt for license information. -# -############################################################################## - -"""snmf - implements the stretchednmf algorithm - -""" - - -from pkgutil import extend_path - -__path__ = extend_path(__path__, __name__) - - -# End of file diff --git a/diffpy/snmf/__init__.py b/diffpy/snmf/__init__.py deleted file mode 100644 index 9e9f1b60..00000000 --- a/diffpy/snmf/__init__.py +++ /dev/null @@ -1,25 +0,0 @@ -#!/usr/bin/env python -############################################################################## -# -# pdfmorph by DANSE Diffraction group -# Simon J. L. Billinge -# (c) 2006 trustees of the Michigan State University. -# All rights reserved. -# -# File coded by: Chris Farrow -# -# See AUTHORS.txt for a list of people who contributed. -# See LICENSE.txt for license information. -# -############################################################################## - -"""Tools for manipulating and comparing PDFs. -""" - -# obtain version information -__version__ = "0.0.1" - -# top-level import -# from diffpy.snmf.pdfmorph_api import pdfmorph, morph_default_config, plot_morph - -# End of file diff --git a/diffpy/snmf/containers.py b/diffpy/snmf/containers.py deleted file mode 100644 index 1af960ad..00000000 --- a/diffpy/snmf/containers.py +++ /dev/null @@ -1,77 +0,0 @@ -import numdifftools -import numpy as np - - -class ComponentSignal: - """ - Attributes - ---------- - grid: 1d array of floats - The vector containing the grid points of the component. - iq: 1d array of floats - The intensity/g(r) values of the component. - weights: 1d array of floats - The vector containing the weight of the component signal for each signal. - stretching_factors: 1d array of floats - The vector containing the stretching factor for the component signal for each signal. - id: int - The number identifying the component. - """ - - def __init__(self, grid, number_of_signals, id_number, perturbation=1e-3): - self.grid = np.asarray(grid) - self.iq = np.random.rand(len(grid)) - self.weights = np.random.rand(number_of_signals) - self.stretching_factors = np.ones(number_of_signals) + np.random.randn(number_of_signals) * perturbation - self.id = int(id_number) - - def apply_stretch(self, m): - """Applies a stretching factor to a component - - Parameters - ---------- - m: int - The index specifying which stretching factor to apply - - Returns - ------- - tuple of 1d arrays - The tuple of vectors where one vector is the stretched component, one vector is the 1st derivative of the - stretching operation, and one vector is the second derivative of the stretching operation. - """ - normalized_grid = np.arange(len(self.grid)) - interpolate_intensity = lambda stretching_factor: np.interp( # noqa: E731 - normalized_grid / stretching_factor, normalized_grid, self.iq, left=0, right=0 - ) - derivative_func = numdifftools.Derivative(interpolate_intensity) - second_derivative_func = numdifftools.Derivative(derivative_func) - - stretched_component = interpolate_intensity(self.stretching_factors[m]) - stretched_component_gra = derivative_func(self.stretching_factors[m]) - stretched_component_hess = second_derivative_func(self.stretching_factors[m]) - - return ( - np.asarray(stretched_component), - np.asarray(stretched_component_gra), - np.asarray(stretched_component_hess), - ) - - def apply_weight(self, m, stretched_component=None): - """Applies as weight factor to a component signal. - - Parameters - ---------- - m: int - The index specifying with weight to apply - stretched_component: 1d array - The 1d array containing a stretched component. - - Returns - ------- - 1d array - The vector containing a component signal or stretched component signal with a weight factor applied. - """ - if stretched_component is None: - return self.iq * self.weights[m] - else: - return stretched_component * self.weights[m] diff --git a/diffpy/snmf/factorizers.py b/diffpy/snmf/factorizers.py deleted file mode 100644 index b4620fd4..00000000 --- a/diffpy/snmf/factorizers.py +++ /dev/null @@ -1,31 +0,0 @@ -import numpy as np -import scipy.optimize - - -def lsqnonneg(stretched_component_matrix, target_signal): - """Finds the weights of stretched component signals under one-sided constraint. - - Solves ``argmin_x || Ax - b ||_2`` for ``x>=0`` where A is the stretched_component_matrix and b is the - target_signal vector. Finds the weights of component signals given undecomposed signal data and stretched - components under a one-sided constraint on the weights. - - Parameters - ---------- - stretched_component_matrix: 2d array like - The component matrix where each column contains a stretched component signal. Has dimensions R x C where R is - the length of the signal and C is the number of components. Does not need to be nonnegative. Corresponds with - 'A' from the objective function. - - target_signal: 1d array like - The signal that is used as reference against which weight factors will be determined. Any column from the - matrix of the entire, unfactorized input data could be used. Has length R. Does not need to be nonnegative. - Corresponds with 'b' from the objective function. - - Returns - ------- - 1d array like - The vector containing component signal weights at a moment. Has length C. - """ - stretched_component_matrix = np.asarray(stretched_component_matrix) - target_signal = np.asarray(target_signal) - return scipy.optimize.nnls(stretched_component_matrix, target_signal)[0] diff --git a/diffpy/snmf/io.py b/diffpy/snmf/io.py deleted file mode 100644 index 12eb1b23..00000000 --- a/diffpy/snmf/io.py +++ /dev/null @@ -1,120 +0,0 @@ -from pathlib import Path - -import numpy as np -import scipy.sparse - -from diffpy.utils.parsers.loaddata import loadData - - -def initialize_variables(data_input, number_of_components, data_type, sparsity=1, smoothness=1e18): - """Determines the variables and initial values used in the SNMF algorithm. - - Parameters - ---------- - data_input: 2d array like - The observed or simulated PDF or XRD data provided by the user. Has dimensions R x N where R is the signa - length and N is the number of PDF/XRD signals. - - number_of_components: int - The number of component signals the user would like to decompose 'data_input' into. - - data_type: str - The type of data the user has passed into the program. Can assume the value of 'PDF' or 'XRD.' - - sparsity: float, optional - The regularization parameter that behaves as the coefficient of a "sparseness" regularization term that - enhances the ability to decompose signals in the case of sparse data e.g. X-ray Diffraction data. - A non-zero value indicates sparsity in the data; greater magnitudes indicate greater amounts of sparsity. - - smoothness: float, optional - The regularization parameter that behaves as the coefficient of a "smoothness" term that ensures that - component signal weightings change smoothly with time. Assumes a default value of 1e18. - - Returns - ------- - dictionary - The collection of the names and values of the constants used in the algorithm. Contains the number of - observed PDF/XRD patterns, the length of each pattern, the type of the data, the number of components - the user would like to decompose the data into, an initial guess for the component matrix, and initial - guess for the weight factor matrix, an initial guess for the stretching factor matrix, a parameter - controlling smoothness of the solution, a parameter controlling sparseness of the solution, the matrix - representing the smoothness term, and a matrix used to construct a hessian matrix. - - """ - signal_length = data_input.shape[0] - number_of_signals = data_input.shape[1] - - diagonals = [ - np.ones(number_of_signals - 2), - -2 * np.ones(number_of_signals - 2), - np.ones(number_of_signals - 2), - ] - smoothness_term = 0.25 * scipy.sparse.diags( - diagonals, [0, 1, 2], shape=(number_of_signals - 2, number_of_signals) - ) - - hessian_helper_matrix = scipy.sparse.block_diag([smoothness_term.T @ smoothness_term] * number_of_components) - sequence = ( - np.arange(number_of_signals * number_of_components) - .reshape(number_of_components, number_of_signals) - .T.flatten() - ) - hessian_helper_matrix = hessian_helper_matrix[sequence, :][:, sequence] - - return { - "signal_length": signal_length, - "number_of_signals": number_of_signals, - "number_of_components": number_of_components, - "data_type": data_type, - "smoothness": smoothness, - "sparsity": sparsity, - "smoothness_term": smoothness_term, - "hessian_helper_matrix": hessian_helper_matrix, - } - - -def load_input_signals(file_path=None): - """Processes a directory of a series of PDF/XRD patterns into a usable format. - - Constructs a 2d array out of a directory of PDF/XRD patterns containing each files dependent variable - column in a new column. Constructs a 1d array containing the grid values. - - Parameters - ---------- - file_path: str or Path object, optional - The path to the directory containing the input XRD/PDF data. If no path is specified, defaults to the - current working directory. Accepts a string or a pathlib.Path object. Input data not on the same grid - as the first file read will be ignored. - - Returns - ------- - tuple - The tuple whose first element is an R x M 2d array made of PDF/XRD patterns as each column; R is the - length of the signal and M is the number of patterns. The tuple contains a 1d array containing the values - of the grid points as its second element; Has length R. - - """ - - if file_path is None: - directory_path = Path.cwd() - else: - directory_path = Path(file_path) - - values_list = [] - grid_list = [] - current_grid = [] - for item in directory_path.iterdir(): - if item.is_file(): - data = loadData(item.resolve()) - if current_grid and current_grid != data[:, 0]: - print(f"{item.name} was ignored as it is not on a compatible grid.") - continue - else: - grid_list.append(data[:, 0]) - current_grid = grid_list[-1] - values_list.append(data[:, 1]) - - grid_array = np.column_stack(grid_list) - grid_vector = np.unique(grid_array, axis=1) - values_array = np.column_stack(values_list) - return grid_vector, values_array diff --git a/diffpy/snmf/optimizers.py b/diffpy/snmf/optimizers.py deleted file mode 100644 index 4198907d..00000000 --- a/diffpy/snmf/optimizers.py +++ /dev/null @@ -1,55 +0,0 @@ -import cvxpy -import numpy as np - - -def get_weights(stretched_component_gram_matrix, linear_coefficient, lower_bound, upper_bound): - """Finds the weights of stretched component signals under a two-sided constraint - - Solves min J(y) = (linear_coefficient)' * y + (1/2) * y' * (quadratic coefficient) * y where - lower_bound <= y <= upper_bound and stretched_component_gram_matrix is symmetric positive definite. - Finds the weightings of stretched component signals under a two-sided constraint. - - Parameters - ---------- - stretched_component_gram_matrix: 2d array like - The Gram matrix constructed from the stretched component matrix. It is a square positive definite matrix. - It has dimensions C x C where C is the number of component signals. Must be symmetric positive definite. - - linear_coefficient: 1d array like - The vector containing the product of the stretched component matrix and the transpose of the observed - data matrix. Has length C. - - lower_bound: 1d array like - The lower bound on the values of the output weights. Has the same dimensions of the function output. Each - element in 'lower_bound' determines the minimum value the corresponding element in the function output may - take. - - upper_bound: 1d array like - The upper bound on the values of the output weights. Has the same dimensions of the function output. Each - element in 'upper_bound' determines the maximum value the corresponding element in the function output may - take. - - Returns - ------- - 1d array like - The vector containing the weightings of the components needed to reconstruct a given input signal from the - input set. Has length C - - """ - stretched_component_gram_matrix = np.asarray(stretched_component_gram_matrix) - linear_coefficient = np.asarray(linear_coefficient) - upper_bound = np.asarray(upper_bound) - lower_bound = np.asarray(lower_bound) - - problem_size = max(linear_coefficient.shape) - solution_variable = cvxpy.Variable(problem_size) - - objective = cvxpy.Minimize( - linear_coefficient.T @ solution_variable - + 0.5 * cvxpy.quad_form(solution_variable, stretched_component_gram_matrix) - ) - constraints = [lower_bound <= solution_variable, solution_variable <= upper_bound] - - cvxpy.Problem(objective, constraints).solve() - - return solution_variable.value diff --git a/diffpy/snmf/polynomials.py b/diffpy/snmf/polynomials.py deleted file mode 100644 index 265be050..00000000 --- a/diffpy/snmf/polynomials.py +++ /dev/null @@ -1,35 +0,0 @@ -import numpy as np - - -def rooth(linear_coefficient, constant_term): - """ - Returns the largest real root of x^3+(linear_coefficient) * x + constant_term. If there are no real roots - return 0. - - Parameters - ---------- - linear_coefficient: nd array like of floats - The matrix coefficient of the linear term - constant_term: 0d array like, 1d array like of floats or scalar - The constant scalar term of the problem - - Returns - ------- - ndarray of floats - The largest real root of x^3+(linear_coefficient) * x + constant_term if roots are real, else - return 0 array - - - """ - linear_coefficient = np.asarray(linear_coefficient) - constant_term = np.asarray(constant_term) - solution = np.empty_like(linear_coefficient, dtype=np.float64) - - for index, value in np.ndenumerate(linear_coefficient): - inputs = [1, 0, value, constant_term] - roots = np.roots(inputs) - if ((constant_term / 2) ** 2 + (value / 3) ** 3) < 0: # Discriminant of depressed cubic equation - solution[index] = max(np.real(roots)) - else: - solution[index] = 0 - return solution diff --git a/diffpy/snmf/stretchednmfapp.py b/diffpy/snmf/stretchednmfapp.py deleted file mode 100644 index 7080e0c4..00000000 --- a/diffpy/snmf/stretchednmfapp.py +++ /dev/null @@ -1,60 +0,0 @@ -import argparse -from pathlib import Path - -from diffpy.snmf.io import initialize_variables, load_input_signals -from diffpy.snmf.subroutines import initialize_components, lift_data - -ALLOWED_DATA_TYPES = ["powder_diffraction", "pd", "pair_distribution_function", "pdf"] - - -def create_parser(): - parser = argparse.ArgumentParser( - prog="stretched_nmf", description="Stretched Nonnegative Matrix Factorization" - ) - parser.add_argument( - "-i", - "--input-directory", - type=str, - default=None, - help="Directory containing experimental data. Defaults to current working directory.", - ) - parser.add_argument( - "-o", - "--output-directory", - type=str, - help="The directory where the results will be written. Defaults to '/snmf_results'.", - ) - parser.add_argument( - "t", - "--data-type", - type=str, - default=None, - choices=ALLOWED_DATA_TYPES, - help="The type of the experimental data.", - ) - parser.add_argument( - "-l", - "--lift-factor", - type=float, - default=1, - help="The lifting factor. Data will be lifted by lifted_data = data + abs(min(data) * lift). Default 1.", - ) - parser.add_argument( - "number-of-components", - type=int, - help="The number of component signals for the NMF decomposition. Must be an integer greater than 0", - ) - parser.add_argument("-v", "--version", action="version", help="Print the software version number") - args = parser.parse_args() - return args - - -def main(): - args = create_parser() - if args.input_directory is None: - args.input_directory = Path.cwd() - grid, input_data = load_input_signals(args.input_directory) - lifted_input_data = lift_data(input_data, args.lift_factor) - variables = initialize_variables(lifted_input_data, args.number_of_components, args.data_type) - components = initialize_components(variables["number_of_components"], variables["number_of_signals"], grid) - return components diff --git a/diffpy/snmf/subroutines.py b/diffpy/snmf/subroutines.py deleted file mode 100644 index e4f6a5f7..00000000 --- a/diffpy/snmf/subroutines.py +++ /dev/null @@ -1,494 +0,0 @@ -import numdifftools -import numpy as np - -from diffpy.snmf.containers import ComponentSignal -from diffpy.snmf.factorizers import lsqnonneg -from diffpy.snmf.optimizers import get_weights - - -def initialize_components(number_of_components, number_of_signals, grid_vector): - """Initializes ComponentSignals for each of the components in the decomposition. - - Parameters - ---------- - number_of_components: int - The number of component signals in the NMF decomposition - number_of_signals: int - grid_vector: 1d array - The grid of the user provided signals. - - Returns - ------- - tuple of ComponentSignal objects - The tuple containing `number_of_components` of initialized ComponentSignal objects. - """ - if number_of_components <= 0: - raise ValueError(f"Number of components = {number_of_components}. Number_of_components must be >= 1.") - components = list() - for component in range(number_of_components): - component = ComponentSignal(grid_vector, number_of_signals, component) - components.append(component) - return tuple(components) - - -def lift_data(data_input, lift=1): - """Lifts values of data_input. - - Adds 'lift' * the minimum value in data_input to data_input element-wise. - - Parameters - ---------- - data_input: 2d array like - The matrix containing a series of signals to be decomposed. Has dimensions N x M where N is the length - of each signal and M is the number of signals. - - lift: float - The factor representing how much to lift 'data_input'. - - Returns - ------- - 2d array like - The matrix that contains data_input - (min(data_input) * lift). - """ - data_input = np.asarray(data_input) - return data_input + np.abs(np.min(data_input) * lift) - - -def construct_stretching_matrix(components, number_of_components, number_of_signals): - """Constructs the stretching factor matrix. - - Parameters - ---------- - components: tuple of ComponentSignal objects - The tuple containing the component signals in ComponentSignal objects. - number_of_signals: int - The number of signals in the data provided by the user. - - Returns - ------- - 2d array - The matrix containing the stretching factors for the component signals for each of the signals in the - raw data. Has dimensions `component_signal` x `number_of_signals` - """ - if (len(components)) == 0: - raise ValueError(f"Number of components = {number_of_components}. Number_of_components must be >= 1.") - number_of_components = len(components) - - if number_of_signals <= 0: - raise ValueError(f"Number of signals = {number_of_signals}. Number_of_signals must be >= 1.") - - stretching_factor_matrix = np.zeros((number_of_components, number_of_signals)) - for i, component in enumerate(components): - stretching_factor_matrix[i] = component.stretching_factors - return stretching_factor_matrix - - -def construct_component_matrix(components): - """Constructs the component matrix. - - Parameters - ---------- - components: tuple of ComponentSignal objects - The tuple containing the component signals in ComponentSignal objects. - - Returns - ------- - 2d array - The matrix containing the component signal values. Has dimensions `signal_length` x `number_of_components`. - """ - signal_length = len(components[0].iq) - number_of_components = len(components) - if signal_length == 0: - raise ValueError(f"Signal length = {signal_length}. Signal length must be >= 1") - if number_of_components == 0: - raise ValueError(f"Number of components = {number_of_components}. Number_of_components must be >= 1") - - component_matrix = np.zeros((number_of_components, signal_length)) - for i, component in enumerate(components): - component_matrix[i] = component.iq - return component_matrix - - -def construct_weight_matrix(components): - """Constructs the weights matrix. - - Constructs a Ķ x M matrix where K is the number of components and M is the - number of signals. Each element is the stretching factor for a specific - weights for a specific signal from the data input. - - Parameters - ---------- - components: tuple of ComponentSignal objects - The tuple containing the component signals. - - Returns - ------- - 2d array like - The 2d array containing the weightings for each component for each signal. - """ - number_of_components = len(components) - number_of_signals = len(components[0].weights) - if number_of_components == 0: - raise ValueError(f"Number of components = {number_of_components}. Number of components must be >= 1") - if number_of_signals == 0: - raise ValueError(f"Number of signals = {number_of_signals}. Number_of_signals must be >= 1.") - weights_matrix = np.zeros((number_of_components, number_of_signals)) - for i, component in enumerate(components): - weights_matrix[i] = component.weights - return weights_matrix - - -def update_weights(components, data_input, method=None): - """Updates the weights matrix. - - Updates the weights matrix and the weights vector for each ComponentSignal object. - - Parameters - ---------- - components: tuple of ComponentSignal objects - The tuple containing the component signals. - method: str - The string specifying which method should be used to find a new weight matrix: non-negative least squares - or a quadratic program. - data_input: 2d array - The 2d array containing the user-provided signals. - - Returns - ------- - 2d array - The 2d array containing the weight factors for each component for each signal from `data_input`. - Has dimensions K x M where K is the number of components and M is the number of signals in `data_input.` - """ - data_input = np.asarray(data_input) - weight_matrix = construct_weight_matrix(components) - number_of_signals = len(components[0].weights) - number_of_components = len(components) - signal_length = len(components[0].grid) - for signal in range(number_of_signals): - stretched_components = np.zeros((signal_length, number_of_components)) - for i, component in enumerate(components): - stretched_components[:, i] = component.apply_stretch(signal)[0] - if method == "align": - weights = lsqnonneg(stretched_components, data_input[:, signal]) - else: - weights = get_weights( - stretched_components.T @ stretched_components, - -stretched_components.T @ data_input[:, signal], - 0, - 1, - ) - weight_matrix[:, signal] = weights - return weight_matrix - - -def reconstruct_signal(components, signal_idx): - """Reconstructs a specific signal from its weighted and stretched components. - - Calculates the linear combination of stretched components where each term is the stretched component multiplied - by its weight factor. - - Parameters - ---------- - components: tuple of ComponentSignal objects - The tuple containing the ComponentSignal objects - signal_idx: int - The index of the specific signal in the input data to be reconstructed - - Returns - ------- - 1d array like - The reconstruction of a signal from calculated weights, stretching factors, and iq values. - """ - signal_length = len(components[0].grid) - reconstruction = np.zeros(signal_length) - for component in components: - stretched = component.apply_stretch(signal_idx)[0] - stretched_and_weighted = component.apply_weight(signal_idx, stretched) - reconstruction += stretched_and_weighted - return reconstruction - - -def initialize_arrays(number_of_components, number_of_moments, signal_length): - """Generates the initial guesses for the weight, stretching, and component matrices. - - Calculates the initial guesses for the component matrix, stretching factor matrix, and weight matrix. The - initial guess for the component matrix is a random (signal_length) x (number_of_components) matrix where - each element is between 0 and 1. The initial stretching factor matrix is a random - (number_of_components) x (number_of_moments) matrix where each element is number slightly perturbed from 1. - The initial weight matrix guess is a random (number_of_components) x (number_of_moments) matrix where - each element is between 0 and 1. - - Parameters - ---------- - number_of_components: int - The number of component signals to obtain from the stretched nmf decomposition. - - number_of_moments: int - The number of signals in the user provided dataset where each signal is at a different moment. - - signal_length: int - The length of each signal in the user provided dataset. - - Returns - ------- - tuple of 2d arrays of floats - The tuple containing three elements: the initial component matrix guess, the initial stretching factor matrix - guess, and the initial weight factor matrix guess in that order. - """ - component_matrix_guess = np.random.rand(signal_length, number_of_components) - weight_matrix_guess = np.random.rand(number_of_components, number_of_moments) - stretching_matrix_guess = ( - np.ones(number_of_components, number_of_moments) - + np.random.randn(number_of_components, number_of_moments) * 1e-3 - ) - return component_matrix_guess, weight_matrix_guess, stretching_matrix_guess - - -def objective_function( - residual_matrix, stretching_factor_matrix, smoothness, smoothness_term, component_matrix, sparsity -): - """Defines the objective function of the algorithm and returns its value. - - Calculates the value of '(||residual_matrix||_F) ** 2 + smoothness * (||smoothness_term * - stretching_factor_matrix.T||)**2 + sparsity * sum(component_matrix ** .5)' and returns its value. - - Parameters - ---------- - residual_matrix: 2d array like - The matrix where each column is the difference between an experimental PDF/XRD pattern and a calculated - PDF/XRD pattern at each grid point. Has dimensions R x M where R is the length of each pattern and M is - the amount of patterns. - - stretching_factor_matrix: 2d array like - The matrix containing the stretching factors of the calculated component signal. Has dimensions K x M where - K is the amount of components and M is the number of experimental PDF/XRD patterns. - - smoothness: float - The coefficient of the smoothness term which determines the intensity of the smoothness term and its - behavior. It is not very sensitive and is usually adjusted by multiplying it by ten. - - smoothness_term: 2d array like - The regularization term that ensures that smooth changes in the component stretching signals are favored. - Has dimensions (M-2) x M where M is the amount of experimentally obtained PDF/XRD patterns, the moment - amount. - - component_matrix: 2d array like - The matrix containing the calculated component signals of the experimental PDF/XRD patterns. Has dimensions - R x K where R is the signal length and K is the number of component signals. - - sparsity: float - The parameter determining the intensity of the sparsity regularization term which enables the algorithm to - exploit the sparse nature of XRD data. It is usually adjusted by doubling. - - Returns - ------- - float - The value of the objective function. - """ - residual_matrix = np.asarray(residual_matrix) - stretching_factor_matrix = np.asarray(stretching_factor_matrix) - component_matrix = np.asarray(component_matrix) - return ( - 0.5 * np.linalg.norm(residual_matrix, "fro") ** 2 - + 0.5 * smoothness * np.linalg.norm(smoothness_term @ stretching_factor_matrix.T, "fro") ** 2 - + sparsity * np.sum(np.sqrt(component_matrix)) - ) - - -def get_stretched_component(stretching_factor, component, signal_length): - """Applies a stretching factor to a component signal. - - Computes a stretched signal and reinterpolates it onto the original grid of points. Uses a normalized grid - of evenly spaced integers counting from 0 to signal_length (exclusive) to approximate values in between grid - nodes. Once this grid is stretched, values at grid nodes past the unstretched signal's domain are set to zero. - Returns the approximate values of x(r/a) from x(r) where x is a component signal. - - Parameters - ---------- - stretching_factor: float - The stretching factor of a component signal at a particular moment. - component: 1d array like - The calculated component signal without stretching or weighting. Has length N, the length of the signal. - signal_length: int - The length of the component signal. - - Returns - ------- - tuple of 1d array of floats - The calculated component signal with stretching factors applied. Has length N, the length of the unstretched - component signal. Also returns the gradient and hessian of the stretching transformation. - """ - component = np.asarray(component) - normalized_grid = np.arange(signal_length) - - def stretched_component_func(stretching_factor): - return np.interp(normalized_grid / stretching_factor, normalized_grid, component, left=0, right=0) - - derivative_func = numdifftools.Derivative(stretched_component_func) - second_derivative_func = numdifftools.Derivative(derivative_func) - - stretched_component = stretched_component_func(stretching_factor) - stretched_component_gra = derivative_func(stretching_factor) - stretched_component_hess = second_derivative_func(stretching_factor) - - return ( - np.asarray(stretched_component), - np.asarray(stretched_component_gra), - np.asarray(stretched_component_hess), - ) - - -def update_weights_matrix( - component_amount, - signal_length, - stretching_factor_matrix, - component_matrix, - data_input, - moment_amount, - weights_matrix, - method, -): - """Update the weight factors matrix. - - Parameters - ---------- - component_amount: int - The number of component signals the user would like to determine from the experimental data. - - signal_length: int - The length of the experimental signal patterns - - stretching_factor_matrix: 2d array like - The matrx containing the stretching factors of the calculated component signals. Has dimensions K x M - where K is the number of component signals and M is the number of XRD/PDF patterns. - - component_matrix: 2d array lik - The matrix containing the unstretched calculated component signals. Has dimensions N x K where N is the - length of the signals and K is the number of component signals. - - data_input: 2d array like - The experimental series of PDF/XRD patterns. Has dimensions N x M where N is the length of the PDF/XRD - signals and M is the number of PDF/XRD patterns. - - moment_amount: int - The number of PDF/XRD patterns from the experimental data. - - weights_matrix: 2d array like - The matrix containing the weights of the stretched component signals. Has dimensions K x M where K is - the number of component signals and M is the number of XRD/PDF patterns. - - method: str - The string specifying the method for obtaining individual weights. - - Returns - ------- - 2d array like - The matrix containing the new weight factors of the stretched component signals. - """ - stretching_factor_matrix = np.asarray(stretching_factor_matrix) - component_matrix = np.asarray(component_matrix) - data_input = np.asarray(data_input) - weights_matrix = np.asarray(weights_matrix) - weight = np.zeros(component_amount) - for i in range(moment_amount): - stretched_components = np.zeros((signal_length, component_amount)) - for n in range(component_amount): - stretched_components[:, n] = get_stretched_component( - stretching_factor_matrix[n, i], component_matrix[:, n], signal_length - )[0] - if method == "align": - weight = lsqnonneg(stretched_components[0:signal_length, :], data_input[0:signal_length, i]) - else: - weight = get_weights( - stretched_components[0:signal_length, :].T @ stretched_components[0:signal_length, :], - -1 * stretched_components[0:signal_length, :].T @ data_input[0:signal_length, i], - 0, - 1, - ) - weights_matrix[:, i] = weight - return weights_matrix - - -def get_residual_matrix( - component_matrix, weights_matrix, stretching_matrix, data_input, moment_amount, component_amount, signal_length -): - """Obtains the residual matrix between the experimental data and calculated data. - - Calculates the difference between the experimental data and the reconstructed experimental data created from - the calculated components, weights, and stretching factors. For each experimental pattern, the stretched and - weighted components making up that pattern are subtracted. - - Parameters - ---------- - component_matrix: 2d array like - The matrix containing the calculated component signals. Has dimensions N x K where N is the length of the - signal and K is the number of calculated component signals. - - weights_matrix: 2d array like - The matrix containing the calculated weights of the stretched component signals. Has dimensions K x M where - K is the number of components and M is the number of moments or experimental PDF/XRD patterns. - - stretching_matrix: 2d array like - The matrix containing the calculated stretching factors of the calculated component signals. Has dimensions - K x M where K is the number of components and M is the number of moments or experimental PDF/XRD patterns. - - data_input: 2d array like - The matrix containing the experimental PDF/XRD data. Has dimensions N x M where N is the length of the - signals and M is the number of signal patterns. - - moment_amount: int - The number of patterns in the experimental data. Represents the number of moments in time in the data series - - component_amount: int - The number of component signals the user would like to obtain from the experimental data. - - signal_length: int - The length of the signals in the experimental data. - - - Returns - ------- - 2d array like - The matrix containing the residual between the experimental data and reconstructed data from calculated - values. Has dimensions N x M where N is the signal length and M is the number of moments. Each column - contains the difference between an experimental signal and a reconstruction of that signal from the - calculated weights, components, and stretching factors. - """ - component_matrix = np.asarray(component_matrix) - weights_matrix = np.asarray(weights_matrix) - stretching_matrix = np.asarray(stretching_matrix) - data_input = np.asarray(data_input) - residual_matrx = -1 * data_input - for m in range(moment_amount): - residual = residual_matrx[:, m] - for k in range(component_amount): - residual = ( - residual - + weights_matrix[k, m] - * get_stretched_component(stretching_matrix[k, m], component_matrix[:, k], signal_length)[0] - ) - residual_matrx[:, m] = residual - return residual_matrx - - -def reconstruct_data(components): - """Reconstructs the `input_data` matrix. - - Reconstructs the `input_data` matrix from calculated component signals, weights, and stretching factors. - - Parameters - ---------- - components: tuple of ComponentSignal objects - The tuple containing the component signals. - - Returns - ------- - 2d array - The 2d array containing the reconstruction of input_data. - """ - signal_length = len(components[0].iq) - number_of_signals = len(components[0].weights) - data_reconstruction = np.zeros((signal_length, number_of_signals)) - for signal in range(number_of_signals): - data_reconstruction[:, signal] = reconstruct_signal(components, signal) - return data_reconstruction diff --git a/diffpy/snmf/tests/test_containers.py b/diffpy/snmf/tests/test_containers.py deleted file mode 100644 index 1c78be02..00000000 --- a/diffpy/snmf/tests/test_containers.py +++ /dev/null @@ -1,110 +0,0 @@ -import numpy as np -import pytest - -from diffpy.snmf.containers import ComponentSignal - -tas = [ - ( - [np.arange(10), 3, 0, [6.55, 0.357, 8.49, 9.33, 6.78, 7.57, 7.43, 3.92, 6.55, 1.71], 0.25], - [ - [6.55, 6.78, 6.55, 0, 0, 0, 0, 0, 0, 0], - [0, 14.07893122, 35.36478086, 0, 0, 0, 0, 0, 0, 0], - [0, -19.92049156, 11.6931482, 0, 0, 0, 0, 0, 0, 0], - ], - ), - ( - [np.arange(5), 10, 0, [-11.47, -10.688, -8.095, -29.44, 14.38], 1.25], - [ - [-11.47, -10.8444, -9.1322, -16.633, -20.6760], - [0, -0.50048, -3.31904, 40.9824, -112.1792], - [0, 0.800768, 5.310464, -65.57184, 179.48672], - ], - ), - ( - [np.arange(5), 2, 0, [-11.47, -10.688, -8.095, -29.44, 14.38], 0.88], - [ - [-11.47, -10.3344, -13.9164, -11.5136, 0], - [0, -3.3484, 55.1265, -169.7572, 0], - [0, 7.609997, -125.2876, 385.81189, 0], - ], - ), - ( - [np.arange(10), 1, 2, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], 0.88], - [ - [1, 2.1364, 3.2727, 4.4091, 5.5455, 6.6818, 7.8182, 8.9545, 0, 0], - [0, -1.29, -2.58, -3.87, -5.165, -6.45, -7.74, -9.039, 0, 0], - [0, 2.93, 5.869, 8.084, 11.739, 14.674, 17.608, 20.5437, 0, 0], - ], - ), - ( - [ - np.arange(14), - 100, - 3, - [ - -2.9384, - -1.4623, - -2.0913, - 4.6304, - -1.2127, - 1.4737, - -0.3791, - 1.7506, - -1.5068, - -2.7625, - 0.9617, - -0.3494, - -0.3862, - 2.7960, - ], - 0.55, - ], - [ - [-2.9384, -1.9769, 0.9121, 0.6314, 0.8622, -2.4239, -0.2302, 1.9281, 0, 0, 0, 0, 0, 0], - [0, 2.07933, 38.632, 18.3748, 43.07305, -61.557, 26.005, -73.637, 0, 0, 0, 0, 0, 0], - [0, -7.56, -140.480, -66.81, -156.6293, 223.84, -94.564, 267.7734, 0, 0, 0, 0, 0, 0], - ], - ), - ( - [np.arange(11), 20, 4, [0, 0.25, 0.5, 0.75, 1, 1.25, 1.5, 1.75, 2, 2.25, 2.5], 0.987], - [ - [0, 0.2533, 0.5066, 0.7599, 1.0132, 1.2665, 1.5198, 1.7730, 2.0263, 2.2796, 0], - [0, -0.2566, -0.5132, -0.7699, -1.0265, -1.2831, -1.5398, -1.7964, -2.0530, -2.3097, 0], - [0, 0.5200, 1.0400, 1.56005, 2.08007, 2.6000, 3.1201, 3.6401, 4.1601, 4.6801, 0], - ], - ), - ( - [np.arange(9), 15, 3, [-1, -2, -3, -4, -5, -6, -7, -8, -9], -0.4], - [[-1, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0]], - ), -] - - -@pytest.mark.parametrize("tas", tas) -def test_apply_stretch(tas): - component = ComponentSignal(tas[0][0], tas[0][1], tas[0][2]) - component.iq = tas[0][3] - component.stretching_factors[0] = tas[0][4] - actual = component.apply_stretch(0) - expected = tas[1] - np.testing.assert_allclose(actual, expected, rtol=1e-01) - - -taw = [ - ([np.arange(5), 2, 0, [0, 1, 2, 3, 4], 0.5], [0, 0.5, 1, 1.5, 2]), - ([np.arange(5), 20, 2, [0, -1, -2, -3, -4], 0.25], [0, -0.25, -0.5, -0.75, -1]), - ([np.arange(40), 200, 4, np.arange(0, 10, 0.25), 0.3], np.arange(0, 10, 0.25) * 0.3), - ([np.arange(1), 10, 2, [10.5, 11.5, -10.5], 0], [0, 0, 0]), - ([[-12, -10, -15], 5, 2, [-0.5, -1, -1.2], 0.9], [-0.45, -0.9, -1.08]), - ([[-12, -10, -15], 5, 2, [0, 0, 0], 0.9], [0, 0, 0]), -] - - -@pytest.mark.parametrize("taw", taw) -def test_apply_weight(taw): - component = ComponentSignal(taw[0][0], taw[0][1], taw[0][2]) - component.iq = np.array(taw[0][3]) - component.weights[0] = taw[0][4] - actual = component.apply_weight(0) - expected = taw[1] - np.testing.assert_allclose(actual, expected, rtol=1e-01) diff --git a/diffpy/snmf/tests/test_factorizers.py b/diffpy/snmf/tests/test_factorizers.py deleted file mode 100644 index c20d1926..00000000 --- a/diffpy/snmf/tests/test_factorizers.py +++ /dev/null @@ -1,21 +0,0 @@ -import numpy as np -import pytest - -from diffpy.snmf.factorizers import lsqnonneg - -tl = [ - ([[[1, 0], [1, 0], [0, 1]], [2, 1, 1]], [1.5, 1.0]), - ([[[2, 3], [1, 2], [0, 0]], [7, 7, 2]], [0, 2.6923]), - ([[[3, 2, 4, 1]], [3.2]], [0, 0, 0.8, 0]), - ([[[-0.4, 0], [0, 0], [-9, -18]], [-2, -3, -4.9]], [0.5532, 0]), - ([[[-0.1, -0.2], [-0.8, -0.9]], [0, 0]], [0, 0]), - ([[[0, 0], [0, 0]], [10, 10]], [0, 0]), - ([[[2], [1], [-4], [-0.3]], [6, 4, 0.33, -5]], 0.767188240872451), -] - - -@pytest.mark.parametrize("tl", tl) -def test_lsqnonneg(tl): - actual = lsqnonneg(tl[0][0], tl[0][1]) - expected = tl[1] - np.testing.assert_array_almost_equal(actual, expected, decimal=4) diff --git a/diffpy/snmf/tests/test_optimizers.py b/diffpy/snmf/tests/test_optimizers.py deleted file mode 100644 index ab7a71f4..00000000 --- a/diffpy/snmf/tests/test_optimizers.py +++ /dev/null @@ -1,20 +0,0 @@ -import pytest - -from diffpy.snmf.optimizers import get_weights - -tm = [ - ([[[1, 0], [0, 1]], [1, 1], [0, 0], [1, 1]], [0, 0]), - ([[[1, 0], [0, 1]], [1, 1], -1, 1], [-1, -1]), - ([[[1.75, 0], [0, 1.5]], [1, 1.2], -1, 1], [-0.571428571428571, -0.8]), - ([[[0.75, 0.2], [0.2, 0.75]], [-0.1, -0.2], -1, 1], [0.066985645933014, 0.248803827751196]), - ([[[2, -1, 0], [-1, 2, -1], [0, -1, 2]], [1, 1, 1], -10, 12], [-1.5, -2, -1.5]), - ([[[2, -1, 0], [-1, 2, -1], [0, -1, 2]], [1, -1, -1], -10, 12], [0, 1, 1]), - ([[[4, 0, 0, 0], [0, 3, 0, 0], [0, 0, 2, 0], [0, 0, 0, 1]], [-2, -3, -4, -1], 0, 1000], [0.5, 1, 2, 1]), -] - - -@pytest.mark.parametrize("tm", tm) -def test_get_weights(tm): - expected = tm[1] - actual = get_weights(tm[0][0], tm[0][1], tm[0][2], tm[0][3]) - assert actual == pytest.approx(expected, rel=1e-4, abs=1e-6) diff --git a/diffpy/snmf/tests/test_polynomials.py b/diffpy/snmf/tests/test_polynomials.py deleted file mode 100644 index d9358341..00000000 --- a/diffpy/snmf/tests/test_polynomials.py +++ /dev/null @@ -1,27 +0,0 @@ -import numpy as np -import pytest - -from diffpy.snmf.polynomials import rooth - -tr = [ - ([0, 0], 0), - ([-99.99, 12.50], 9.936397678254531), - ([-4, 0], 2), - ([10, 0], 0), - ([-7, -7], 3.04891734), - ([100, 72], 0), - ([1, 3], 0), - ([0, -7], 0), - ([-9, 0], 3), - ([-9, 3], 2.8169), - ([[2, 2], 2], [0, 0]), - ([[[2, 2], [2, 2]], 2], [[0, 0], [0, 0]]), - ([[[[3, 2], [-2, -2], [100, 0]]], 2], [[[0, 0], [0, 0], [0, 0]]]), -] - - -@pytest.mark.parametrize("tr", tr) -def test_rooth(tr): - actual = rooth(tr[0][0], tr[0][1]) - expected = tr[1] - np.testing.assert_allclose(actual, expected, rtol=1e-4) diff --git a/diffpy/snmf/tests/test_subroutines.py b/diffpy/snmf/tests/test_subroutines.py deleted file mode 100644 index 66029d2e..00000000 --- a/diffpy/snmf/tests/test_subroutines.py +++ /dev/null @@ -1,539 +0,0 @@ -import numpy as np -import pytest - -from diffpy.snmf.containers import ComponentSignal -from diffpy.snmf.subroutines import ( - construct_component_matrix, - construct_stretching_matrix, - construct_weight_matrix, - get_residual_matrix, - get_stretched_component, - initialize_components, - lift_data, - objective_function, - reconstruct_data, - reconstruct_signal, - update_weights, - update_weights_matrix, -) - -to = [ - ([[[1, 2], [3, 4]], [[5, 6], [7, 8]], 1e11, [[1, 2], [3, 4]], [[1, 2], [3, 4]], 1], 2.574e14), - ([[[11, 2], [31, 4]], [[5, 63], [7, 18]], 0.001, [[21, 2], [3, 4]], [[11, 22], [3, 40]], 1], 650.4576), - ([[[1, 2], [3, 4]], [[5, 6], [7, 8]], 1e11, [[1, 2], [3, 4]], [[1, 2], [3, 4]], 0], 2.574e14), -] - - -@pytest.mark.parametrize("to", to) -def test_objective_function(to): - actual = objective_function(to[0][0], to[0][1], to[0][2], to[0][3], to[0][4], to[0][5]) - expected = to[1] - assert actual == pytest.approx(expected) - - -tgso = [ - ( - [0.25, [6.55, 0.357, 8.49, 9.33, 6.78, 7.57, 7.43, 3.92, 6.55, 1.71], 10], - ( - [6.55, 6.78, 6.55, 0, 0, 0, 0, 0, 0, 0], - [0, 14.07893122, 35.36478086, 0, 0, 0, 0, 0, 0, 0], - [0, -19.92049156, 11.6931482, 0, 0, 0, 0, 0, 0, 0], - ), - ), - ( - [1.25, [-11.47, -10.688, -8.095, -29.44, 14.38], 5], - ( - [-11.47, -10.8444, -9.1322, -16.633, -20.6760], - [0, -0.50048, -3.31904, 40.9824, -112.1792], - [0, 0.800768, 5.310464, -65.57184, 179.48672], - ), - ), - ( - [0.88, [-11.47, -10.688, -8.095, -29.44, 14.38], 5], - ( - [-11.47, -10.3344, -13.9164, -11.5136, 0], - [0, -3.3484, 55.1265, -169.7572, 0], - [0, 7.609997, -125.2876, 385.81189, 0], - ), - ), - ( - [0.88, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], 10], - ( - [1, 2.1364, 3.2727, 4.4091, 5.5455, 6.6818, 7.8182, 8.9545, 0, 0], - [0, -1.29, -2.58, -3.87, -5.165, -6.45, -7.74, -9.039, 0, 0], - [0, 2.93, 5.869, 8.084, 11.739, 14.674, 17.608, 20.5437, 0, 0], - ), - ), - ( - [ - 0.55, - [ - -2.9384, - -1.4623, - -2.0913, - 4.6304, - -1.2127, - 1.4737, - -0.3791, - 1.7506, - -1.5068, - -2.7625, - 0.9617, - -0.3494, - -0.3862, - 2.7960, - ], - 14, - ], - ( - [-2.9384, -1.9769, 0.9121, 0.6314, 0.8622, -2.4239, -0.2302, 1.9281, 0, 0, 0, 0, 0, 0], - [0, 2.07933, 38.632, 18.3748, 43.07305, -61.557, 26.005, -73.637, 0, 0, 0, 0, 0, 0], - [0, -7.56, -140.480, -66.81, -156.6293, 223.84, -94.564, 267.7734, 0, 0, 0, 0, 0, 0], - ), - ), - ( - [0.987, [0, 0.25, 0.5, 0.75, 1, 1.25, 1.5, 1.75, 2, 2.25, 2.5], 11], - ( - [0, 0.2533, 0.5066, 0.7599, 1.0132, 1.2665, 1.5198, 1.7730, 2.0263, 2.2796, 0], - [0, -0.2566, -0.5132, -0.7699, -1.0265, -1.2831, -1.5398, -1.7964, -2.0530, -2.3097, 0], - [0, 0.5200, 1.0400, 1.56005, 2.08007, 2.6000, 3.1201, 3.6401, 4.1601, 4.6801, 0], - ), - ), - ( - [-0.4, [-1, -2, -3, -4, -5, -6, -7, -8, -9], 9], - ([-1, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0]), - ), -] - - -@pytest.mark.parametrize("tgso", tgso) -def test_get_stretched_component(tgso): - actual = get_stretched_component(tgso[0][0], tgso[0][1], tgso[0][2]) - expected = tgso[1] - np.testing.assert_allclose(actual, expected, rtol=1e-01) - - -tuwm = [ - ( - [ - 2, - 2, - [[0.5, 0.6], [0.7, 0.8]], - [[1, 2], [4, 8]], - [[1.6, 2.8], [5, 8.8]], - 2, - [[0.78, 0.12], [0.5, 0.5]], - None, - ], - [[0, 1], [1, 1]], - ), - ( - [2, 3, [[0.5], [0.5]], [[1, 2.5], [1.5, 3], [2, 3.5]], [[1, 2], [3, 4], [5, 6]], 1, [[0.5], [0.5]], None], - [[1], [0.1892]], - ), - ( - [ - 2, - 3, - [[0.5, 0.6, 0.7], [0.5, 0.6, 0.7]], - [[1, 2.5], [1.5, 3], [2, 3.5]], - [[1, 2, 3], [3, 4, 5], [5, 6, 7]], - 3, - [[0.5, 0.45, 0.4], [0.5, 0.45, 0.4]], - None, - ], - [[1, 1, 1], [0.1892, 0.5600, 0.938]], - ), - ( - [ - 3, - 3, - [[0.7, 0.8, 0.9], [0.71, 0.72, 0.73], [0.8, 0.85, 0.9]], - [[-1, -2.7, -3], [-11, -6, -5.1], [0, -1, -0.5]], - [[-2, -3, -4], [-9, -5, -5], [0, -2, -1]], - 3, - [[0.9, 0.4, 0.5], [1, 0, 0.4], [0, 0, 0.98]], - None, - ], - [[1, 0.0651, 0], [0.5848, 0.0381, 0.1857], [0, 1, 1]], - ), - ([2, 2, [[0.5], [0.5]], [[0, 0], [0, 0]], [[0, 0], [0, 0]], 1, [[0.6], [0.4]], "align"], [[0], [0]]), - ([1, 3, [[0.5, 0.3]], [[1], [1.1], [1.3]], [[1, 2], [2, 3], [3, 2]], 2, [[0.6, 0.4]], None], [[1, 1]]), - ( - [ - 2, - 2, - [[0.5, 0.6], [0.7, 0.8]], - [[1, 2], [4, 8]], - [[1.6, 2.8], [5, 8.8]], - 2, - [[0.78, 0.12], [0.5, 0.5]], - "align", - ], - [[0, 0], [1.0466, 1.46]], - ), - ( - [ - 2, - 3, - [[0.5], [0.5]], - [[1, 2.5], [1.5, 3], [2, 3.5]], - [[1, 2], [3, 4], [5, 6]], - 1, - [[0.5], [0.5]], - "align", - ], - [[1.4], [0]], - ), - ( - [ - 3, - 3, - [[0.7, 0.8, 0.9], [0.71, 0.72, 0.73], [0.8, 0.85, 0.9]], - [[-1, -2.7, -3], [-11, -6, -5.1], [0, -1, -0.5]], - [[-2, -3, -4], [-9, -5, -5], [0, -2, -1]], - 3, - [[0.9, 0.4, 0.5], [1, 0, 0.4], [0, 0, 0.98]], - "align", - ], - [[1.2605, 0.0552, 0], [0.2723, 0, 0], [0, 1.0538, 1.1696]], - ), - ([2, 2, [[0.5], [0.5]], [[0, 0], [0, 0]], [[0, 0], [0, 0]], 1, [[0.6], [0.4]], "align"], [[0], [0]]), - ([1, 3, [[0.5, 0.3]], [[1], [1.1], [1.3]], [[1, 2], [2, 3], [3, 2]], 2, [[0.6, 0.4]], "align"], [[1.3383, 2]]), -] - - -@pytest.mark.parametrize("tuwm", tuwm) -def test_update_weights_matrix(tuwm): - actual = update_weights_matrix( - tuwm[0][0], tuwm[0][1], tuwm[0][2], tuwm[0][3], tuwm[0][4], tuwm[0][5], tuwm[0][6], tuwm[0][7] - ) - expected = tuwm[1] - np.testing.assert_allclose(actual, expected, rtol=1e-03, atol=0.5) - - -tgrm = [ - ([[[1, 2], [3, 4]], [[0.25], [0.75]], [[0.9], [0.7]], [[11, 22], [33, 44]], 1, 2, 2], [[-9, -22], [-33, -44]]), - ([[[1, 2], [3, 4]], [[1], [1]], [[1], [1]], [[11, 22], [33, 44]], 1, 2, 2], [[-8, -22], [-26, -44]]), - ( - [ - [[1.1, 4.4], [1.2, 4.5], [14, 7.8]], - [[0.4, 0.6], [0.75, 0.25]], - [[0.9, 0.89], [0.98, 0.88]], - [[10, 20], [-10.5, -20.6], [0.6, 0.9]], - 2, - 2, - 3, - ], - [[-6.26, -18.24], [14.9744, 23.5067], [-0.6, -0.9]], - ), - # positive float - ( - [ - [[-1.1, -4.4], [-1.2, -4.5], [-14, -7.8]], - [[0.4, 0.6], [0.75, 0.25]], - [[0.9, 0.89], [0.98, 0.88]], - [[10, 20], [-10.5, -20.6], [0.6, 0.9]], - 2, - 2, - 3, - ], - [[-13.74, -21.76], [6.0256, 17.6933], [-0.6, -0.9]], - ), - # negative floats - ( - [ - [[0, 0, 0, 0], [0, 0, 0, 0]], - [[0.4], [0.2], [0.3], [0.3]], - [[0.9], [0.9], [0.9], [0.9]], - [[0, 0, 0, 0], [0, 0, 0, 0]], - 1, - 4, - 2, - ], - [[0, 0, 0, 0], [0, 0, 0, 0]], - ), -] - - -@pytest.mark.parametrize("tgrm", tgrm) -def test_get_residual_matrix(tgrm): - actual = get_residual_matrix( - tgrm[0][0], tgrm[0][1], tgrm[0][2], tgrm[0][3], tgrm[0][4], tgrm[0][5], tgrm[0][6] - ) - expected = tgrm[1] - np.testing.assert_allclose(actual, expected, rtol=1e-04) - - -trd = [ - ( - [ - ComponentSignal([0, 0.25, 0.5, 0.75, 1], 2, 0), - ComponentSignal([0, 0.25, 0.5, 0.75, 1], 2, 1), - ComponentSignal([0, 0.25, 0.5, 0.75, 1], 2, 2), - ] - ), - ([ComponentSignal([0, 0.25, 0.5, 0.75, 1], 2, 0)]), - ( - [ - ComponentSignal([0, 0.25, 0.5, 0.75, 1], 2, 0), - ComponentSignal([0, 0.25, 0.5, 0.75, 1], 2, 1), - ComponentSignal([0, 0.25, 0.5, 0.75, 1], 2, 2), - ComponentSignal([0, 0.25, 0.5, 0.75, 1], 2, 3), - ComponentSignal([0, 0.25, 0.5, 0.75, 1], 2, 4), - ] - ), - # ([]) # Exception expected -] - - -@pytest.mark.parametrize("trd", trd) -def test_reconstruct_data(trd): - actual = reconstruct_data(trd) - assert actual.shape == (len(trd[0].iq), len(trd[0].weights)) - print(actual) - - -tld = [ - (([[[1, -1, 1], [0, 0, 0], [2, 10, -3]], 1]), ([[4, 2, 4], [3, 3, 3], [5, 13, 0]])), - (([[[1, -1, 1], [0, 0, 0], [2, 10, -3]], 0]), ([[1, -1, 1], [0, 0, 0], [2, 10, -3]])), - (([[[1, -1, 1], [0, 0, 0], [2, 10, -3]], 0.5]), ([[2.5, 0.5, 2.5], [1.5, 1.5, 1.5], [3.5, 11.5, -1.5]])), - (([[[1, -1, 1], [0, 0, 0], [2, 10, -3]], -1]), ([[4, 2, 4], [3, 3, 3], [5, 13, 0]])), - (([[[0, 0, 0], [0, 0, 0], [0, 0, 0]], 100]), ([[0, 0, 0], [0, 0, 0], [0, 0, 0]])), - (([[[1.5, 2], [10.5, 1], [0.5, 2]], 1]), ([[2, 2.5], [11, 1.5], [1, 2.5]])), - (([[[-10, -10.5], [-12.2, -12.2], [0, 0]], 1]), ([[2.2, 1.7], [0, 0], [12.2, 12.2]])), -] - - -@pytest.mark.parametrize("tld", tld) -def test_lift_data(tld): - actual = lift_data(tld[0][0], tld[0][1]) - expected = tld[1] - np.testing.assert_allclose(actual, expected) - - -tcc = [ - (2, 3, [0, 0.5, 1, 1.5]), # Regular usage - # (0, 3,[0, .5, 1, 1.5]), # Zero components raise an exception. Not tested -] - - -@pytest.mark.parametrize("tcc", tcc) -def test_initialize_components(tcc): - actual = initialize_components(tcc[0], tcc[1], tcc[2]) - assert len(actual) == tcc[0] - assert len(actual[0].weights) == tcc[1] - assert (actual[0].grid == np.array(tcc[2])).all() - - -tcso = [ - ([ComponentSignal([0, 0.5, 1, 1.5], 20, 0)], 1, 20), - ([ComponentSignal([0, 0.5, 1, 1.5], 20, 0)], 4, 20), - # ([ComponentSignal([0,.5,1,1.5],20,0)],0,20), # Raises an exception - # ([ComponentSignal([0,.5,1,1.5],20,0)],-2,20), # Raises an exception - # ([ComponentSignal([0,.5,1,1.5],20,0)],1,0), # Raises an Exception - # ([ComponentSignal([0,.5,1,1.5],20,0)],1,-3), # Raises an exception - ([ComponentSignal([0, 0.5, 1, 1.5], 20, 0), ComponentSignal([0, 0.5, 1, 1.5], 20, 1)], 2, 20), - ([ComponentSignal([0, 0.5, 1, 1.5], 20, 0), ComponentSignal([0, 0.5, 1, 21.5], 20, 1)], 2, 20), - ([ComponentSignal([0, 1, 1.5], 20, 0), ComponentSignal([0, 0.5, 1, 21.5], 20, 1)], 2, 20), - # ([ComponentSignal([0,.5,1,1.5],20,0),ComponentSignal([0,.5,1,1.5],20,1)],1,-3), - # Negative signal length. Raises an exception - # ([],1,20), # Empty components. Raises an Exception - # ([],-1,20), # Empty components with negative number of components. Raises an exception - # ([],0,20), # Empty components with zero number of components. Raises an exception - # ([],1,0), # Empty components with zero signal length. Raises an exception. - # ([],-1,-2), # Empty components with negative number of components and signal length Raises an exception. -] - - -@pytest.mark.parametrize("tcso", tcso) -def test_construct_stretching_matrix(tcso): - actual = construct_stretching_matrix(tcso[0], tcso[1], tcso[2]) - for component in tcso[0]: - np.testing.assert_allclose(actual[component.id, :], component.stretching_factors) - # assert actual[component.id, :] == component.stretching_factors - - -tccm = [ - ([ComponentSignal([0, 0.25, 0.5, 0.75, 1], 20, 0)]), - ([ComponentSignal([0, 0.25, 0.5, 0.75, 1], 0, 0)]), - ( - [ - ComponentSignal([0, 0.25, 0.5, 0.75, 1], 20, 0), - ComponentSignal([0, 0.25, 0.5, 0.75, 1], 20, 1), - ComponentSignal([0, 0.25, 0.5, 0.75, 1], 20, 2), - ] - ), - ( - [ - ComponentSignal([0, 0.25, 0.5, 0.75, 1], 20, 0), - ComponentSignal([0, 0.25, 0.5, 0.75, 1], 20, 1), - ComponentSignal([0, 0.25, 0.5, 0.75, 1], 20, 2), - ] - ), - ( - [ - ComponentSignal([0, 0.25, 0.5, 0.75, 1], 20, 0), - ComponentSignal([0, 0.25, 0.5, 2.75, 1], 20, 1), - ComponentSignal([0, 0.25, 0.5, 0.75, 1], 20, 2), - ] - ), - ([ComponentSignal([0.25], 20, 0), ComponentSignal([0.25], 20, 1), ComponentSignal([0.25], 20, 2)]), - ([ComponentSignal([0, 0.25, 0.5, 0.75, 1], 20, 0), ComponentSignal([0, 0.25, 0.5, 0.75, 1], 20, 1)]), - # ([ComponentSignal([[0, .25, .5, .75, 1],[0, .25, .5, .75, 1]], 20, 0), - # ComponentSignal([[0, .25, .5, .75, 1],[0, .25, .5, .75, 1]], 20, 1)]), - # iq is multidimensional. Expected to fail - # (ComponentSignal([], 20, 0)), # Expected to fail - # ([]), #Expected to fail -] - - -@pytest.mark.parametrize("tccm", tccm) -def test_construct_component_matrix(tccm): - actual = construct_component_matrix(tccm) - for component in tccm: - np.testing.assert_allclose(actual[component.id], component.iq) - - -tcwm = [ - ([ComponentSignal([0, 0.25, 0.5, 0.75, 1], 20, 0)]), - # ([ComponentSignal([0,.25,.5,.75,1],0,0)]), # 0 signal length. Failure expected - ( - [ - ComponentSignal([0, 0.25, 0.5, 0.75, 1], 20, 0), - ComponentSignal([0, 0.25, 0.5, 0.75, 1], 20, 1), - ComponentSignal([0, 0.25, 0.5, 0.75, 1], 20, 2), - ] - ), - ( - [ - ComponentSignal([0, 0.25, 0.5, 0.75, 1], 20, 0), - ComponentSignal([0, 0.25, 0.5, 0.75, 1], 20, 1), - ComponentSignal([0, 0.25, 0.5, 0.75, 1], 20, 2), - ] - ), - ( - [ - ComponentSignal([0, 0.25, 0.5, 0.75, 1], 20, 0), - ComponentSignal([0, 0.25, 0.5, 2.75, 1], 20, 1), - ComponentSignal([0, 0.25, 0.5, 0.75, 1], 20, 2), - ] - ), - ([ComponentSignal([0.25], 20, 0), ComponentSignal([0.25], 20, 1), ComponentSignal([0.25], 20, 2)]), - ([ComponentSignal([0, 0.25, 0.5, 0.75, 1], 20, 0), ComponentSignal([0, 0.25, 0.5, 0.75, 1], 20, 1)]), - # (ComponentSignal([], 20, 0)), # Expected to fail - # ([]), #Expected to fail -] - - -@pytest.mark.parametrize("tcwm", tcwm) -def test_construct_weight_matrix(tcwm): - actual = construct_weight_matrix(tcwm) - for component in tcwm: - np.testing.assert_allclose(actual[component.id], component.weights) - - -tuw = [ - ( - [ - ComponentSignal([0, 0.25, 0.5, 0.75, 1], 2, 0), - ComponentSignal([0, 0.25, 0.5, 0.75, 1], 2, 1), - ComponentSignal([0, 0.25, 0.5, 0.75, 1], 2, 2), - ], - [[1, 1], [1.2, 1.3], [1.3, 1.4], [1.4, 1.5], [2, 2.1]], - None, - ), - ( - [ - ComponentSignal([0, 0.25, 0.5, 0.75, 1], 2, 0), - ComponentSignal([0, 0.25, 0.5, 0.75, 1], 2, 1), - ComponentSignal([0, 0.25, 0.5, 0.75, 1], 2, 2), - ], - [[1, 1], [1.2, 1.3], [1.3, 1.4], [1.4, 1.5], [2, 2.1]], - "align", - ), - ( - [ - ComponentSignal([0, 0.25, 0.5, 0.75, 1], 2, 0), - ComponentSignal([0, 0.25, 0.5, 0.75, 1], 2, 1), - ComponentSignal([0, 0.25, 0.5, 0.75, 1], 2, 2), - ], - [[0, 0], [0, 0], [0, 0], [0, 0], [0, 0]], - None, - ), - ( - [ - ComponentSignal([0, 0.25, 0.5, 0.75, 1], 2, 0), - ComponentSignal([0, 0.25, 0.5, 0.75, 1], 2, 1), - ComponentSignal([0, 0.25, 0.5, 0.75, 1], 2, 2), - ], - [[0, 0], [0, 0], [0, 0], [0, 0], [0, 0]], - "align", - ), - ( - [ - ComponentSignal([0, 0.25, 0.5, 0.75, 1], 2, 0), - ComponentSignal([0, 0.25, 0.5, 0.75, 1], 2, 1), - ComponentSignal([0, 0.25, 0.5, 0.75, 1], 2, 2), - ], - [[-0.5, 1], [1.2, -1.3], [1.1, -1], [0, -1.5], [0, 0.1]], - None, - ), - ( - [ - ComponentSignal([0, 0.25, 0.5, 0.75, 1], 2, 0), - ComponentSignal([0, 0.25, 0.5, 0.75, 1], 2, 1), - ComponentSignal([0, 0.25, 0.5, 0.75, 1], 2, 2), - ], - [[-0.5, 1], [1.2, -1.3], [1.1, -1], [0, -1.5], [0, 0.1]], - "align", - ), - # ([ComponentSignal([0, .25, .5, .75, 1], 0, 0), ComponentSignal([0, .25, .5, .75, 1], 0, 1), - # ComponentSignal([0, .25, .5, .75, 1], 0, 2)], [[1, 1], [1.2, 1.3], [1.3, 1.4], [1.4, 1.5], [2, 2.1]], None), - # ([ComponentSignal([0, .25, .5, .75, 1], 0, 0), ComponentSignal([0, .25, .5, .75, 1], 0, 1), - # ComponentSignal([0, .25, .5, .75, 1], 0, 2)], [], None), - # ([ComponentSignal([0, .25, .5, .75, 1], 2, 0), ComponentSignal([0, .25, .5, .75, 1], 2, 1), - # ComponentSignal([0, .25, .5, .75, 1], 2, 2)], [], 170), -] - - -@pytest.mark.parametrize("tuw", tuw) -def test_update_weights(tuw): - actual = update_weights(tuw[0], tuw[1], tuw[2]) - assert np.shape(actual) == (len(tuw[0]), len(tuw[0][0].weights)) - - -trs = [ - ( - [ - ComponentSignal([0, 0.25, 0.5, 0.75, 1], 2, 0), - ComponentSignal([0, 0.25, 0.5, 0.75, 1], 2, 1), - ComponentSignal([0, 0.25, 0.5, 0.75, 1], 2, 2), - ], - 1, - ), - ( - [ - ComponentSignal([0, 0.25, 0.5, 0.75, 1], 2, 0), - ComponentSignal([0, 0.25, 0.5, 0.75, 1], 2, 1), - ComponentSignal([0, 0.25, 0.5, 0.75, 1], 2, 2), - ], - 0, - ), - ( - [ - ComponentSignal([0, 0.25, 0.5, 0.75, 1], 3, 0), - ComponentSignal([0, 0.25, 0.5, 0.75, 1], 3, 1), - ComponentSignal([0, 0.25, 0.5, 0.75, 1], 3, 2), - ], - 2, - ), - # ([ComponentSignal([0, .25, .5, .75, 1], 2, 0), ComponentSignal([0, .25, .5, .75, 1], 2, 1), - # ComponentSignal([0, .25, .5, .75, 1], 2, 2)], -1), -] - - -@pytest.mark.parametrize("trs", trs) -def test_reconstruct_signal(trs): - actual = reconstruct_signal(trs[0], trs[1]) - assert len(actual) == len(trs[0][0].grid) diff --git a/diffpy/snmf/version.py b/diffpy/snmf/version.py deleted file mode 100644 index d7a5c1cf..00000000 --- a/diffpy/snmf/version.py +++ /dev/null @@ -1,28 +0,0 @@ -#!/usr/bin/env python -############################################################################## -# -# Structure by DANSE Diffraction group -# Simon J. L. Billinge -# (c) 2008 trustees of the Michigan State University. -# All rights reserved. -# -# File coded by: Pavol Juhas -# -# See AUTHORS.txt for a list of people who contributed. -# See LICENSE.txt for license information. -# -############################################################################## - -"""Definition of __version__ and __date__ for diffpy.pdfmorph. -""" - - -# obtain version information -from pkg_resources import get_distribution - -__version__ = get_distribution("diffpy.snmf").version - -# we assume that tag_date was used and __version__ ends in YYYYMMDD -__date__ = __version__[-8:-4] + "-" + __version__[-4:-2] + "-" + __version__[-2:] - -# End of file From e10c264bda583dc7250edf0b6da83829bdc884c5 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Wed, 7 Aug 2024 16:54:39 -0400 Subject: [PATCH 22/65] Modify codecov src path --- .coveragerc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.coveragerc b/.coveragerc index 72a8c926..fa3fa528 100644 --- a/.coveragerc +++ b/.coveragerc @@ -1,6 +1,6 @@ [run] source = - diffpy/snmf/ + src [report] omit = */python?.?/* From f428d5db41c9a93575bdbcef231c519b10572690 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Thu, 8 Aug 2024 15:11:01 -0400 Subject: [PATCH 23/65] Modify doc absolute path --- doc/source/conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/conf.py b/doc/source/conf.py index c4508858..444a86ac 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -14,7 +14,7 @@ # import sys -sys.path.insert(0, os.path.abspath("../..")) +sys.path.insert(0, os.path.abspath("..")) # -- Project information ----------------------------------------------------- From a85403c23834742d1ff9a2406175d23c1035cf2f Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Thu, 8 Aug 2024 16:18:20 -0400 Subject: [PATCH 24/65] Remove empty files under doc --- doc/images/aspirin_smear.png | 0 doc/images/aspirin_stretch.png | 0 doc/images/morph_ex1.png | 0 doc/images/morph_ex2.png | 0 doc/images/nacl_example.agr | 0 doc/images/nacl_example.xcf | 0 doc/images/pdfmorph_example.agr | 0 doc/images/pdfmorph_example.png | 0 doc/images/pdfmorph_example.xcf | 0 doc/images/pdfmorph_smear.png | 0 doc/images/pdfmorph_smear2.png | 0 doc/images/pdfmorph_stretch.png | 0 12 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 doc/images/aspirin_smear.png delete mode 100644 doc/images/aspirin_stretch.png delete mode 100644 doc/images/morph_ex1.png delete mode 100644 doc/images/morph_ex2.png delete mode 100644 doc/images/nacl_example.agr delete mode 100644 doc/images/nacl_example.xcf delete mode 100644 doc/images/pdfmorph_example.agr delete mode 100644 doc/images/pdfmorph_example.png delete mode 100644 doc/images/pdfmorph_example.xcf delete mode 100644 doc/images/pdfmorph_smear.png delete mode 100644 doc/images/pdfmorph_smear2.png delete mode 100644 doc/images/pdfmorph_stretch.png diff --git a/doc/images/aspirin_smear.png b/doc/images/aspirin_smear.png deleted file mode 100644 index e69de29b..00000000 diff --git a/doc/images/aspirin_stretch.png b/doc/images/aspirin_stretch.png deleted file mode 100644 index e69de29b..00000000 diff --git a/doc/images/morph_ex1.png b/doc/images/morph_ex1.png deleted file mode 100644 index e69de29b..00000000 diff --git a/doc/images/morph_ex2.png b/doc/images/morph_ex2.png deleted file mode 100644 index e69de29b..00000000 diff --git a/doc/images/nacl_example.agr b/doc/images/nacl_example.agr deleted file mode 100644 index e69de29b..00000000 diff --git a/doc/images/nacl_example.xcf b/doc/images/nacl_example.xcf deleted file mode 100644 index e69de29b..00000000 diff --git a/doc/images/pdfmorph_example.agr b/doc/images/pdfmorph_example.agr deleted file mode 100644 index e69de29b..00000000 diff --git a/doc/images/pdfmorph_example.png b/doc/images/pdfmorph_example.png deleted file mode 100644 index e69de29b..00000000 diff --git a/doc/images/pdfmorph_example.xcf b/doc/images/pdfmorph_example.xcf deleted file mode 100644 index e69de29b..00000000 diff --git a/doc/images/pdfmorph_smear.png b/doc/images/pdfmorph_smear.png deleted file mode 100644 index e69de29b..00000000 diff --git a/doc/images/pdfmorph_smear2.png b/doc/images/pdfmorph_smear2.png deleted file mode 100644 index e69de29b..00000000 diff --git a/doc/images/pdfmorph_stretch.png b/doc/images/pdfmorph_stretch.png deleted file mode 100644 index e69de29b..00000000 From 237c75fa88469c831c50d0ea0827dc97ed8f8660 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Thu, 8 Aug 2024 17:40:08 -0400 Subject: [PATCH 25/65] Remove pdfmorph quickstart --- doc/source/quickstart.rst | 189 -------------------------------------- 1 file changed, 189 deletions(-) delete mode 100644 doc/source/quickstart.rst diff --git a/doc/source/quickstart.rst b/doc/source/quickstart.rst deleted file mode 100644 index 6bf4a728..00000000 --- a/doc/source/quickstart.rst +++ /dev/null @@ -1,189 +0,0 @@ -.. _quick_start: - -PDFmorph Tutorial -################# - -Welcome! This will be a quick tutorial to accquaint users with PDFmorph -and some of what it can do. - -As we described in the README and installation instructions, please make -sure that you are familiar with working with your command line terminal -before using this application. - -Before you've started this tutorial, please ensure that you've installed -all necessary software and dependencies. - -Basic PDFmorph Workflow ------------------------ - - 1. Open your Terminal or Command Prompt. - - 2. It it's not active already, activate your PDFmorph-equipped - conda environment by typing in :: - - source activate - - on Linux or ``activate `` on Windows. - - * If you need to list your available conda environments, - run the command ``conda info --envs`` or - ``conda env list`` - - * Run the ``pdfmorph --help`` command and read over the - info on that page for a brief overview of some of what we will - explore in this tutorial. - - 3. Using the ``mkdir`` command, create a directory where you'll - store the tutorial PDF files and use the ``cd`` command to change - into that directory. You can download the tutorial files - :download:`here <../../tests/testdata/tutorialData.zip>`. - Then, ``cd`` into the ``tutorialData`` directory. - - * The files in this dataset were collected by Soham Banerjee - at Brookhaven National Laboratory in Upton, New York. - - * The files are PDF data collected on Iridium Telluride with - 20% Rhodium Doping (IrRhTe2) with the first file (01) collected - at 10K and the last (44) at 300K. The samples increase in - temperature as their numbers increase. The "C" in their names - indicates that they have undergone cooling. - - * Note that these files have the ``.gr`` extension, which - indicates that they are measured PDFs. The ``.cgr`` file - extension indicates that a file is a calculated PDF, sich as - those generated by the `PDFgui `_ - program. - - 4. First, we will run the PDFmorph application without any morphing - and only using one PDF. Type the following command into your - Command Line :: - - pdfmorph darkSub_rh20_C_01.gr darkSub_rh20_C_01.gr - - This should produce two PDF curves which are congruent, resulting - in a flat green line underneath them. - - 5. Now, we will see PDFmorph run with two different PDFs and no - morphing. Type the following command into your command line :: - - pdfmorph darkSub_rh20_C_01.gr darkSub_rh20_C_44.gr - - Without morphing, the difference Rw = 0.407. This indicates that - the two PDFs vary drastically. - - * While running the ``pdfmorph`` command, it is important - to remember that the first PDF file argument you provide - (in this case, ``darkSub_rh20_C_01.gr``) is the PDF which - will get morphed, while the second PDF file argument you - provide (here, ``darkSub_rh20_C_44.gr``) is the PDF which - acts as the model and does not get morphed. Hereinafter, - we will refer to the first PDF argument as the "objective" - and the second as the "reference", as the PDFmorph display - does. - - 6. Now, we will start the morphing process, which requires us to - provide initial guesses for our scaling factor, Gaussian smear, - and stretch, separately. We will start with the scaling factor. - Begin by typing the command :: - - pdfmorph --scale=2 -a darkSub_rh20_C_01.gr darkSub_rh20_C_44.gr - - Now, the difference Rw = 1.457, a significant increase from our - value previously. We must modify our initial value for the - scaling factor and do so until we see a reduction in the - difference Rw from the unmorphed value. Type :: - - pdfmorph --scale=0.9 -a darkSub_rh20_C_01.gr darkSub_rh20_C_44.gr - - The difference Rw is now 0.351, lower than our unmorphed - example's value. To see PDFmorph optimize the scale factor, - simply drop ``-a`` from the command and type :: - - pdfmorph --scale=0.9 darkSub_rh20_C_01.gr darkSub_rh20_C_44.gr - - PDFmorph, given a reasonable initial guess, will use find the - optimal value for each morphing feature. Here, we see that - PDFmorph displays ``scale = 0.799025`` in the command prompt, - meaning that it has found this to be the most optimal value for - the scale factor. The difference Rw = 0.330, indicating a - better fit than our reasonable initial guess. - - * It is the choice of the user whether or not to run values - before removing ``-a`` when analyzing data with PDFmorph. - By including it, you allow the possibility to move towards - convergence before allowing the program to optimize by - removing it; when including it, you may reach a highly - optimized value on the first guess or diverge greatly. - In this tutorial, we will use it every time to check - for convergence. - - 7. Now, we will examine the Gaussian smearing factor. We provide an - initial guess by typing :: - - pdfmorph --scale=0.8 --smear=0.5 -a darkSub_rh20_C_01.gr darkSub_rh20_C_44.gr - - And viewing the results. We've tailored our scale factor to be - close to the value given by PDFmorph, but see that the difference - Rw has increased substantially due to our smear value. One - approach, as described above, is to remove the ``-a`` from the - above command and run it again. - - * Note: The warnings that the Terminal/Command Prompt - displays are largely numerical in nature and do not - indicate a physically irrelevant guess. These are somewhat - superficial and in most cases can be ignored. - - We see that this has had hardly any effect on our PDF. To see - an effect, we restrict the ``rmin`` and ``rmax`` values to - reflect relevant data range by typing :: - - pdfmorph --scale=0.8 --smear=0.5 --rmin=1.5 --rmax=30 darkSub_rh20_C_01.gr darkSub_rh20_C_44.gr - - Now, we see that the difference Rw = 0.204 and that the optimized - ``smear=-0.084138``. - - * We restricted the r values because some of the Gaussian - smear effects are only visible in a fixed r range. We - chose this r range by noting where most of our relevant - data was that was not exponentially decayed by - instrumental shortcomings. - - We are getting closer to an acceptably close fit to our data! - - 8. Finally, we will examine the stretch factor. Provide an intial - guess by typing :: - - pdfmorph --scale=0.8 --smear=-0.08 --stretch=0.5 --rmin=1.5 --rmax=30 -a darkSub_rh20_C_01.gr darkSub_rh20_C_44.gr - - And noting that the difference has increased. Before continuing, - see if you can see which direction (higher or lower) our initial - estimate for the stretch factor needs to go and then removing - the ``-a`` to check optimized value! - - If you cannot, type :: - - pdfmorph --scale=0.8 --smear=-0.08 --stretch=0.005 --rmin=1.5 --rmax=30 -a darkSub_rh20_C_01.gr darkSub_rh20_C_44.gr - - to observe decreased difference and then remove ``-a`` to see - the optimized ``--stretch=0.001762``. We have now reached - the optimal fit for our PDF! - - 9. Now, try it on your own! If you have personally collected or - otherwise readily available PDF data, try this process to see if - you can morph your PDFs to one another. Many of the parameters - provided in this tutorial are unique to it, so be cautious about - your choices and made sure that they remain physically relevant. - -Enjoy the software! - -.. Additional PDFmorph Functionality/Exploration -.. --------------------------------------------- - -.. TODO include undoped PDF example, phase changed PDFs, and nano/non-nano PDFs - - -Bug Reports ------------ - -Please enjoy using our software! If you come accross any bugs in the -application, please report them to diffpy-dev@googlegroups.com From ef31dd1273482b1b89568079e1206b03ce4230f2 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Thu, 8 Aug 2024 22:35:21 -0400 Subject: [PATCH 26/65] Add section headers to quickstart.rst --- doc/source/quickstart.rst | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 doc/source/quickstart.rst diff --git a/doc/source/quickstart.rst b/doc/source/quickstart.rst new file mode 100644 index 00000000..3fbea737 --- /dev/null +++ b/doc/source/quickstart.rst @@ -0,0 +1,22 @@ +.. _quick_start: + +Tutorial +################# + +Welcome! This will be a quick tutorial to accquaint users with `snmf`. + +Basic Workflow +======================= + + 1. Add a step-by-step guide to the basic workflow of the software. + +Extra Tutorials +=============== + +Add extra tutorials here + +Bug Reports +=========== + +Please enjoy using our software! If you come accross any bugs in the +application, please report them to diffpy-users@googlegroups.com. \ No newline at end of file From ed0823135b08159a847810d74f2dfea7d15fa09e Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 9 Aug 2024 02:35:35 +0000 Subject: [PATCH 27/65] [pre-commit.ci] auto fixes from pre-commit hooks --- doc/source/quickstart.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/quickstart.rst b/doc/source/quickstart.rst index 3fbea737..6751c104 100644 --- a/doc/source/quickstart.rst +++ b/doc/source/quickstart.rst @@ -19,4 +19,4 @@ Bug Reports =========== Please enjoy using our software! If you come accross any bugs in the -application, please report them to diffpy-users@googlegroups.com. \ No newline at end of file +application, please report them to diffpy-users@googlegroups.com. From 12b09f9739ecf87e521fe9573a7c2068489df765 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Fri, 9 Aug 2024 13:13:27 -0400 Subject: [PATCH 28/65] Remove older LICENSE and REAMDE.md --- LICENSE | 28 ---------------------------- README.md | 4 ---- setup.py | 2 +- 3 files changed, 1 insertion(+), 33 deletions(-) delete mode 100644 LICENSE delete mode 100644 README.md diff --git a/LICENSE b/LICENSE deleted file mode 100644 index da868965..00000000 --- a/LICENSE +++ /dev/null @@ -1,28 +0,0 @@ -BSD 3-Clause License - -Copyright (c) 2023, Simon Billinge - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - -2. Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -3. Neither the name of the copyright holder nor the names of its - contributors may be used to endorse or promote products derived from - this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/README.md b/README.md deleted file mode 100644 index 86614d50..00000000 --- a/README.md +++ /dev/null @@ -1,4 +0,0 @@ -# diffpy.snmf - -[![test](https://github.com/diffpy/diffpy.snmf/actions/workflows/main.yml/badge.svg)](https://github.com/diffpy/diffpy.snmf/actions/workflows/main.yml) -[![codecov](https://codecov.io/gh/diffpy/diffpy.snmf/branch/main/graph/badge.svg)](https://codecov.io/gh/diffpy/diffpy.snmf) diff --git a/setup.py b/setup.py index 7a021885..791bd6d2 100644 --- a/setup.py +++ b/setup.py @@ -16,7 +16,7 @@ # with open(os.path.join(MYDIR, 'requirements/run.txt')) as fp: # requirements = [line.strip() for line in fp] -with open(os.path.join(MYDIR, "README.md")) as fp: +with open(os.path.join(MYDIR, "README.rst")) as fp: long_description = fp.read() From a9312a0ffd1bf20e58da2e4865c5e79ffcb1897f Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Fri, 9 Aug 2024 14:08:31 -0400 Subject: [PATCH 29/65] API doc success --- .../api/diffpy.snmf.example_package.rst | 31 --------- doc/source/api/diffpy.snmf.rst | 66 +++++++++++++++---- doc/source/index.rst | 1 + 3 files changed, 53 insertions(+), 45 deletions(-) delete mode 100644 doc/source/api/diffpy.snmf.example_package.rst diff --git a/doc/source/api/diffpy.snmf.example_package.rst b/doc/source/api/diffpy.snmf.example_package.rst deleted file mode 100644 index c815461a..00000000 --- a/doc/source/api/diffpy.snmf.example_package.rst +++ /dev/null @@ -1,31 +0,0 @@ -.. _example_package documentation: - -|title| -======= - -.. |title| replace:: diffpy.snmf.example_package package - -.. automodule:: diffpy.snmf.example_package - :members: - :undoc-members: - :show-inheritance: - -|foo| ------ - -.. |foo| replace:: diffpy.snmf.example_package.foo module - -.. automodule:: diffpy.snmf.example_package.foo - :members: - :undoc-members: - :show-inheritance: - -|bar| ------ - -.. |bar| replace:: diffpy.snmf.example_package.bar module - -.. automodule:: diffpy.snmf.example_package.foo - :members: - :undoc-members: - :show-inheritance: diff --git a/doc/source/api/diffpy.snmf.rst b/doc/source/api/diffpy.snmf.rst index 28f90a6d..9a808f6d 100644 --- a/doc/source/api/diffpy.snmf.rst +++ b/doc/source/api/diffpy.snmf.rst @@ -1,30 +1,68 @@ :tocdepth: -1 -|title| -======= - -.. |title| replace:: diffpy.snmf package +diffpy.snmf package +=================== .. automodule:: diffpy.snmf :members: :undoc-members: :show-inheritance: -Subpackages ------------ - -.. toctree:: - diffpy.snmf.example_package - Submodules ---------- -|module| --------- +diffpy.snmf.subroutines module +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. automodule:: diffpy.snmf.subroutines + :members: + :undoc-members: + :show-inheritance: + +diffpy.snmf.containers module +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. automodule:: diffpy.snmf.containers + :members: + :undoc-members: + :show-inheritance: + +diffpy.snmf.io module +^^^^^^^^^^^^^^^^^^^^^ + +.. automodule:: diffpy.snmf.io + :members: + :undoc-members: + :show-inheritance: + +diffpy.snmf.polynomials module +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. automodule:: diffpy.snmf.polynomials + :members: + :undoc-members: + :show-inheritance: + +diffpy.snmf.optimizers module +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. automodule:: diffpy.snmf.optimizers + :members: + :undoc-members: + :show-inheritance: + +diffpy.snmf.factorizers module +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. automodule:: diffpy.snmf.factorizers + :members: + :undoc-members: + :show-inheritance: -.. |module| replace:: diffpy.snmf.example_submodule module +diffpy.snmf.stretchednmfapp module +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -.. automodule:: diffpy.snmf.example_submodule +.. automodule:: diffpy.snmf.stretchednmfapp :members: :undoc-members: :show-inheritance: diff --git a/doc/source/index.rst b/doc/source/index.rst index 25e675dd..74061dde 100644 --- a/doc/source/index.rst +++ b/doc/source/index.rst @@ -66,6 +66,7 @@ To get started, please go to :ref:`quick_start` license release + Package API .. include:: ../../CHANGELOG.rst From 0af373de236857bfd20daf742ce6ef864d32a9c9 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Fri, 9 Aug 2024 15:51:17 -0400 Subject: [PATCH 30/65] Remove rever.xsh and setup.py --- rever.xsh | 18 --------------- setup.py | 65 ------------------------------------------------------- 2 files changed, 83 deletions(-) delete mode 100644 rever.xsh delete mode 100644 setup.py diff --git a/rever.xsh b/rever.xsh deleted file mode 100644 index 5932990a..00000000 --- a/rever.xsh +++ /dev/null @@ -1,18 +0,0 @@ -$ACTIVITIES = [ - 'version_bump', # Changes the version number in various source files (setup.py, __init__.py, etc) - 'changelog', # Uses files in the news folder to create a changelog for release - 'tag', # Creates a tag for the new version number - #'pypi', # Sends the package to pypi - #'conda_forge', # Creates a PR into your package's feedstock - 'ghrelease' # Creates a Github release entry for the new tag - ] -$VERSION_BUMP_PATTERNS = [ # These note where/how to find the version numbers - ('snmf/__init__.py', '__version__\s*=.*', "__version__ = '$VERSION'"), - ('setup.py', 'version\s*=.*,', "version='$VERSION',") - ] -$CHANGELOG_FILENAME = 'CHANGELOG.rst' # Filename for the changelog -$CHANGELOG_TEMPLATE = 'TEMPLATE.rst' # Filename for the news template -$TAG_REMOTE = 'https://github.com/diffpy/snmf.git' # Repo to push tags to - -$GITHUB_ORG = 'diffpy' # Github org for Github releases and conda-forge -$GITHUB_REPO = 'diffpy.snmf' # Github repo for Github releases and conda-forge diff --git a/setup.py b/setup.py deleted file mode 100644 index 791bd6d2..00000000 --- a/setup.py +++ /dev/null @@ -1,65 +0,0 @@ -#!/usr/bin/env python - -# Installation script for diffpy.snmf - -"""diffpy.snmf - a package implementing the stretched NMF algorithm. - -Packages: diffpy.snmf -""" - -import os - -from setuptools import find_packages, setup - -MYDIR = os.path.dirname(os.path.abspath(__file__)) - -# with open(os.path.join(MYDIR, 'requirements/run.txt')) as fp: -# requirements = [line.strip() for line in fp] - -with open(os.path.join(MYDIR, "README.rst")) as fp: - long_description = fp.read() - - -# define distribution -setup( - name="diffpy.snmf", - version="0.0.1", - packages=find_packages(exclude=["tests", "applications"]), - entry_points={ - # define console_scripts here, see setuptools docs for details. - "console_scripts": [ - "snmf = diffpy.snmf.stretchednmfapp:main", - ], - }, - test_suite="tests", - # install_requires=requirements, - author="Ran Gu, Simon J.L. Billinge", - author_email="sb2896@columbia.edu", - maintainer="Simon J.L. Billinge", - maintainer_email="sb2896@columbia.edu", - url="https://github.com/diffpy/diffpy.snmf", - description="A python package implementing the stretched NMF algorithm.", - long_description=long_description, - long_description_content_type="text/x-rst", - license="BSD", - keywords="diffpy PDF", - classifiers=[ - # List of possible values at - # http://pypi.python.org/pypi?:action=list_classifiers - "Development Status :: 4 - Beta", - "Environment :: Console", - "Intended Audience :: Science/Research", - "License :: OSI Approved :: BSD License", - "Operating System :: MacOS :: MacOS X", - "Operating System :: Microsoft :: Windows", - "Operating System :: POSIX", - "Operating System :: Unix", - "Programming Language :: Python :: 3.9", - "Programming Language :: Python :: 3.10", - "Programming Language :: Python :: 3.11", - "Topic :: Scientific/Engineering :: Chemistry", - "Topic :: Scientific/Engineering :: Physics", - ], -) - -# End of file From 5e57da1cd24739ac24502d8afb3dd0a0aa922fcf Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Fri, 9 Aug 2024 16:16:10 -0400 Subject: [PATCH 31/65] Modify pyproject.toml --- pyproject.toml | 52 +++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 51 insertions(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index e537e24a..efccab14 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,3 +1,53 @@ +[build-system] +requires = ["setuptools>=62.0", "setuptools-git-versioning<2"] +build-backend = "setuptools.build_meta" + +[project] +name = "diffpy.snmf" +dynamic=['version'] +authors = [ + { name="Simon J.L. Billinge group", email="simon.billinge@gmail.com" }, +] +maintainers = [ + { name="Simon J.L. Billinge group", email="simon.billinge@gmail.com" }, +] +description = "A python package implementing the stretched NMF algorithm." +keywords = ['diffpy', 'PDF'] +readme = "README.rst" +requires-python = ">=3.10" +classifiers = [ + 'Development Status :: 5 - Production/Stable', + 'Environment :: Console', + 'Intended Audience :: Developers', + 'Intended Audience :: Science/Research', + 'License :: OSI Approved :: BSD License', + 'Operating System :: MacOS :: MacOS X', + 'Operating System :: Microsoft :: Windows', + 'Operating System :: POSIX', + 'Operating System :: Unix', + 'Programming Language :: Python :: 3.10', + 'Programming Language :: Python :: 3.11', + 'Programming Language :: Python :: 3.12', + 'Topic :: Scientific/Engineering :: Physics', + 'Topic :: Scientific/Engineering :: Chemistry', +] + +[project.urls] +Homepage = "https://github.com/diffpy/diffpy.snmf/" +Issues = "https://github.com/diffpy/diffpy.snmf/issues/" + +[tool.setuptools-git-versioning] +enabled = true +template = "{tag}" +dev_template = "{tag}" +dirty_template = "{tag}" + +[tool.setuptools.packages.find] +where = ["src"] # list of folders that contain the packages (["."] by default) +include = ["*"] # package names should match these glob patterns (["*"] by default) +exclude = ["diffpy.snmf.tests*"] # exclude packages matching these glob patterns (empty by default) +namespaces = false # to disable scanning PEP 420 namespaces (true by default) + [tool.black] line-length = 115 include = '\.pyi?$' @@ -19,4 +69,4 @@ exclude = ''' | blib2to3 | tests/data )/ -''' +''' \ No newline at end of file From 45c99e17498de11c662ee195905503a269797627 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 9 Aug 2024 20:17:00 +0000 Subject: [PATCH 32/65] [pre-commit.ci] auto fixes from pre-commit hooks --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index efccab14..01f1f5b2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -69,4 +69,4 @@ exclude = ''' | blib2to3 | tests/data )/ -''' \ No newline at end of file +''' From a23b95cde4c67eb97f13d92836924d735738e8af Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Fri, 9 Aug 2024 19:43:06 -0400 Subject: [PATCH 33/65] Add news under cookie.rst --- news/cookie.rst | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 news/cookie.rst diff --git a/news/cookie.rst b/news/cookie.rst new file mode 100644 index 00000000..d15f9d43 --- /dev/null +++ b/news/cookie.rst @@ -0,0 +1,23 @@ +**Added:** + +* + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* Repo structure modified to the new diffpy standard + +**Security:** + +* From c79c7cc13f4a7fe05e772d4d4a339e9121e8e345 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Fri, 9 Aug 2024 20:23:41 -0400 Subject: [PATCH 34/65] Use doc/conf.py and liense from diffpy.pdfffit2 --- doc/source/license.rst | 69 +++++++++++++++++++++--------------------- 1 file changed, 34 insertions(+), 35 deletions(-) diff --git a/doc/source/license.rst b/doc/source/license.rst index 826133d5..97502ecf 100644 --- a/doc/source/license.rst +++ b/doc/source/license.rst @@ -1,40 +1,39 @@ -License -======= +:tocdepth: -1 + +.. index:: license -This program is part of the DiffPy open-source project at Columbia -University and is available subject to the conditions and terms laid out -below. +License +####### -Copyright © 2009-2019, Trustees of Columbia University in the City of -New York, all rights reserved. +OPEN SOURCE LICENSE AGREEMENT +============================= +BSD 3-Clause License -For more information please visit the diffpy web-page at -http://diffpy.org or email Prof. Simon Billinge at sb2896@columbia.edu. +Copyright (c) {% now 'utc', '%Y' %}, The Trustees of Columbia University in +the City of New York. +All Rights Reserved. Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - - * Redistributions in binary form must reproduce the above - copyright notice, this list of conditions and the following - disclaimer in the documentation and/or other materials provided - with the distribution. - - * The name of COLUMBIA UNIVERSITY nor the names of its contributors - may be used to endorse or promote products derived from this - software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS -IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED -TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A -PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED -TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR -PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. From 190c28dfcd64e28ecdf3947cf8a6e31a3af598e4 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Fri, 9 Aug 2024 20:26:11 -0400 Subject: [PATCH 35/65] Modify conf.py --- doc/source/conf.py | 282 +++++++++++++++++++++++++++++++++++++-------- 1 file changed, 232 insertions(+), 50 deletions(-) diff --git a/doc/source/conf.py b/doc/source/conf.py index 444a86ac..0879959c 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -1,107 +1,289 @@ -# Configuration file for the Sphinx documentation builder. +#!/usr/bin/env python +# -*- coding: utf-8 -*- # -# This file only contains a selection of the most common options. For a full -# list see the documentation: -# http://www.sphinx-doc.org/en/master/config - -# -- Path setup -------------------------------------------------------------- +# diffpy.pdffit2 documentation build configuration file, created by +# sphinx-quickstart on Thu Jan 30 15:49:41 2014. +# +# This file is execfile()d with the current directory set to its +# containing dir. +# +# Note that not all possible configuration values are present in this +# autogenerated file. +# +# All configuration values have a default; values that are commented out +# serve to show the default. -import os +import sys +import time +from importlib.metadata import version +from pathlib import Path # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the -# documentation root, use os.path.abspath to make it absolute, like shown here. -# -import sys - -sys.path.insert(0, os.path.abspath("..")) +# documentation root, use Path().resolve() to make it absolute, like shown here. +# sys.path.insert(0, str(Path(".").resolve())) +sys.path.insert(0, str(Path("../..").resolve())) +sys.path.insert(0, str(Path("../../src").resolve())) +# abbreviations +ab_authors = "Billinge Group members and community contributors" -# -- Project information ----------------------------------------------------- +# -- General configuration ------------------------------------------------ -project = "snmf" -copyright = "2009-2023, Trustees of Columbia University in the City of New York, all rights reserved." -author = "Ran Gu, Adeolu Ajayi, Qiang Du, Simon J.L. Billinge" - -# The full version, including alpha/beta/rc tags -release = "0.1.0" - - -# -- General configuration --------------------------------------------------- +# If your documentation needs a minimal Sphinx version, state it here. +# needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. -import sphinx_rtd_theme - extensions = [ "sphinx.ext.autodoc", "sphinx.ext.napoleon", "sphinx.ext.todo", "sphinx.ext.viewcode", "sphinx.ext.intersphinx", + "sphinx_rtd_theme", "m2r", ] -napoleon_google_docstring = False -napoleon_use_param = False -napoleon_use_ivar = False # Add any paths that contain templates here, relative to this directory. templates_path = ["_templates"] -from jinja2 import Environment, FileSystemLoader, Template +# The suffix(es) of source filenames. +# You can specify multiple suffix as a list of string: +# +source_suffix = [".rst", ".md"] -source_suffix = ".rst" +# The encoding of source files. +# source_encoding = 'utf-8-sig' +# The master toctree document. master_doc = "index" -language = "en" +# General information about the project. +project = "diffpy.snmf" +copyright = "%Y, The Trustees of Columbia University in the City of New York" + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. + +fullversion = version(project) +# The short X.Y version. +version = "".join(fullversion.split(".post")[:1]) +# The full version, including alpha/beta/rc tags. +release = fullversion + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +# language = None + +# There are two options for replacing |today|: either, you set today to some +# non-false value, then it is used: +# today = '' +today = time.strftime("%B %d, %Y", time.localtime()) +year = today.split()[-1] +# Else, today_fmt is used as the format for a strftime call. +# today_fmt = '%B %d, %Y' +# substitute YEAR in the copyright string +copyright = copyright.replace("%Y", year) + # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. -# This pattern also affects html_static_path and html_extra_path. -exclude_patterns = ["Thumbs.db", ".DS_Store"] +exclude_patterns = ["build"] + +# The reST default role (used for this markup: `text`) to use for all +# documents. +# default_role = None + +# If true, '()' will be appended to :func: etc. cross-reference text. +# add_function_parentheses = True +# If true, the current module name will be prepended to all description +# unit titles (such as .. function::). +# add_module_names = True + +# If true, sectionauthor and moduleauthor directives will be shown in the +# output. They are ignored by default. +# show_authors = False + +# The name of the Pygments (syntax highlighting) style to use. pygments_style = "sphinx" -todo_include_todos = True +# A list of ignored prefixes for module index sorting. +modindex_common_prefix = ["diffpy.snmf"] -# -- Options for HTML output ------------------------------------------------- +# Display all warnings for missing links. +nitpicky = True + +# -- Options for HTML output ---------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. +# html_theme = "sphinx_rtd_theme" -html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] -html_theme_options = {} +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +# +html_theme_options = { + "navigation_with_keys": "true", +} + +# Add any paths that contain custom themes here, relative to this directory. +# html_theme_path = [] + +# The name for this set of Sphinx documents. If None, it defaults to +# " v documentation". +# html_title = None + +# A shorter title for the navigation bar. Default is the same as html_title. +# html_short_title = None + +# The name of an image file (relative to this directory) to place at the top +# of the sidebar. +# html_logo = None + +# The name of an image file (within the static path) to use as favicon of the +# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 +# pixels large. +# html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ["_static"] +# html_static_path = ['_static'] + +# Add any extra paths that contain custom files (such as robots.txt or +# .htaccess) here, relative to this directory. These files are copied +# directly to the root of the documentation. +# html_extra_path = [] -htmlhelp_basename = "snmfdoc" +# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, +# using the given strftime format. +# html_last_updated_fmt = '%b %d, %Y' +# If true, SmartyPants will be used to convert quotes and dashes to +# typographically correct entities. +# html_use_smartypants = True + +# Custom sidebar templates, maps document names to template names. +# html_sidebars = {} + +# Additional templates that should be rendered to pages, maps page names to +# template names. +# html_additional_pages = {} + +# If false, no module index is generated. +# html_domain_indices = True + +# If false, no index is generated. +# html_use_index = True + +# If true, the index is split into individual pages for each letter. +# html_split_index = False + +# If true, links to the reST sources are added to the pages. +# html_show_sourcelink = True + +# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. +# html_show_sphinx = True + +# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. +# html_show_copyright = True + +# If true, an OpenSearch description file will be output, and all pages will +# contain a tag referring to it. The value of this option must be the +# base URL from which the finished HTML is served. +# html_use_opensearch = '' + +# This is the file name suffix for HTML files (e.g. ".xhtml"). +# html_file_suffix = None + +# Output file base name for HTML help builder. +basename = "diffpy.snmf".replace(" ", "").replace(".", "") +htmlhelp_basename = basename + "doc" + + +# -- Options for LaTeX output --------------------------------------------- + +latex_elements = { + # The paper size ('letterpaper' or 'a4paper'). + # 'papersize': 'letterpaper', + # The font size ('10pt', '11pt' or '12pt'). + # 'pointsize': '10pt', + # Additional stuff for the LaTeX preamble. + # 'preamble': '', +} + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, +# author, documentclass [howto, manual, or own class]). latex_documents = [ - (master_doc, "snmf.tex", "Snmf Documentation", "author", "manual"), + ("index", "diffpy.snmf.tex", "diffpy.snmf Documentation", ab_authors, "manual"), ] -man_pages = [(master_doc, "snmf", "Snmf Documentation", [author], 1)] +# The name of an image file (relative to this directory) to place at the top of +# the title page. +# latex_logo = None +# For "manual" documents, if this is true, then toplevel headings are parts, +# not chapters. +# latex_use_parts = False + +# If true, show page references after internal links. +# latex_show_pagerefs = False + +# If true, show URL addresses after external links. +# latex_show_urls = False + +# Documents to append as an appendix to all manuals. +# latex_appendices = [] + +# If false, no module index is generated. +# latex_domain_indices = True + + +# -- Options for manual page output --------------------------------------- + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [("index", "diffpy.pdffit2", "diffpy.pdffit2 Documentation", ab_authors, 1)] + +# If true, show URL addresses after external links. +# man_show_urls = False + + +# -- Options for Texinfo output ------------------------------------------- + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) texinfo_documents = [ ( - master_doc, - "snmf", - "Snmf Documentation", - author, - "snmf", + "index", + "diffpy.pdffit2", + "diffpy.pdffit2 Documentation", + ab_authors, + "diffpy.pdffit2", "One line description of project.", "Miscellaneous", ), ] -epub_title = project -epub_author = author -epub_publisher = author -epub_copyright = copyright +# Documents to append as an appendix to all manuals. +# texinfo_appendices = [] + +# If false, no module index is generated. +# texinfo_domain_indices = True + +# How to display URL addresses: 'footnote', 'no', or 'inline'. +# texinfo_show_urls = 'footnote' + +# If true, do not generate a @detailmenu in the "Top" node's menu. +# texinfo_no_detailmenu = False + -epub_exclude_files = ["search.html"] +# Example configuration for intersphinx: refer to the Python standard library. +# intersphinx_mapping = {'http://docs.python.org/': None} \ No newline at end of file From 75e1f15eddc080028a3f84dfb6b5a993cd0bc732 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sat, 10 Aug 2024 00:30:58 +0000 Subject: [PATCH 36/65] [pre-commit.ci] auto fixes from pre-commit hooks --- doc/source/conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/conf.py b/doc/source/conf.py index 0879959c..0814b315 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -286,4 +286,4 @@ # Example configuration for intersphinx: refer to the Python standard library. -# intersphinx_mapping = {'http://docs.python.org/': None} \ No newline at end of file +# intersphinx_mapping = {'http://docs.python.org/': None} From 7aab5eb55c99d03dd9ad94aeb54b1e5955c9fa84 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sat, 10 Aug 2024 19:00:50 -0400 Subject: [PATCH 37/65] Change of instances of pdffit2 to snmf --- doc/source/conf.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/doc/source/conf.py b/doc/source/conf.py index 0814b315..eea3def9 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- # -# diffpy.pdffit2 documentation build configuration file, created by +# diffpy.snmf documentation build configuration file, created by # sphinx-quickstart on Thu Jan 30 15:49:41 2014. # # This file is execfile()d with the current directory set to its @@ -249,7 +249,7 @@ # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). -man_pages = [("index", "diffpy.pdffit2", "diffpy.pdffit2 Documentation", ab_authors, 1)] +man_pages = [("index", "diffpy.snmf", "diffpy.snmf Documentation", ab_authors, 1)] # If true, show URL addresses after external links. # man_show_urls = False @@ -263,10 +263,10 @@ texinfo_documents = [ ( "index", - "diffpy.pdffit2", - "diffpy.pdffit2 Documentation", + "diffpy.snmf", + "diffpy.snmf Documentation", ab_authors, - "diffpy.pdffit2", + "diffpy.snmf", "One line description of project.", "Miscellaneous", ), From 5b9bd964b19e341baaaf2bf96c2aed650cbc4757 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sat, 10 Aug 2024 19:02:53 -0400 Subject: [PATCH 38/65] Fix date in readme.rst --- doc/source/license.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/source/license.rst b/doc/source/license.rst index 97502ecf..91267b96 100644 --- a/doc/source/license.rst +++ b/doc/source/license.rst @@ -9,7 +9,7 @@ OPEN SOURCE LICENSE AGREEMENT ============================= BSD 3-Clause License -Copyright (c) {% now 'utc', '%Y' %}, The Trustees of Columbia University in +Copyright (c) 2024, The Trustees of Columbia University in the City of New York. All Rights Reserved. @@ -36,4 +36,4 @@ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file From 8163037ecbf2e39ac3fe34eb4ffd4496246698d1 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sat, 10 Aug 2024 23:03:06 +0000 Subject: [PATCH 39/65] [pre-commit.ci] auto fixes from pre-commit hooks --- doc/source/license.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/license.rst b/doc/source/license.rst index 91267b96..cfab61c2 100644 --- a/doc/source/license.rst +++ b/doc/source/license.rst @@ -36,4 +36,4 @@ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. From 94d1d90a454fd64bed3f4ccbbfbf6cfdf66dce1d Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sat, 10 Aug 2024 19:08:29 -0400 Subject: [PATCH 40/65] Update release news --- news/cookie.rst | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/news/cookie.rst b/news/cookie.rst index d15f9d43..bdf76b4c 100644 --- a/news/cookie.rst +++ b/news/cookie.rst @@ -4,7 +4,8 @@ **Changed:** -* +* Support Python version 3.12 +* Remove support for Python version 3.9 **Deprecated:** @@ -17,6 +18,7 @@ **Fixed:** * Repo structure modified to the new diffpy standard +* Code linting based on .pre-commit-config.yaml **Security:** From bdacc6d6ff7d72a1743d03a6bff067b6bc20f76d Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sat, 10 Aug 2024 19:13:52 -0400 Subject: [PATCH 41/65] Fix dev status to 4 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 01f1f5b2..1250363b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -16,7 +16,7 @@ keywords = ['diffpy', 'PDF'] readme = "README.rst" requires-python = ">=3.10" classifiers = [ - 'Development Status :: 5 - Production/Stable', + 'Development Status :: 4 - Beta', 'Environment :: Console', 'Intended Audience :: Developers', 'Intended Audience :: Science/Research', From b19d0245772577149f9383cae712379bff63c88a Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sat, 10 Aug 2024 19:18:15 -0400 Subject: [PATCH 42/65] Add earlier year next to copyright 2024 --- doc/source/conf.py | 2 +- doc/source/license.rst | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/source/conf.py b/doc/source/conf.py index eea3def9..a9211517 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -62,7 +62,7 @@ # General information about the project. project = "diffpy.snmf" -copyright = "%Y, The Trustees of Columbia University in the City of New York" +copyright = "2009-%Y, The Trustees of Columbia University in the City of New York" # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the diff --git a/doc/source/license.rst b/doc/source/license.rst index cfab61c2..b9364226 100644 --- a/doc/source/license.rst +++ b/doc/source/license.rst @@ -9,7 +9,7 @@ OPEN SOURCE LICENSE AGREEMENT ============================= BSD 3-Clause License -Copyright (c) 2024, The Trustees of Columbia University in +Copyright (c) 2009-2024, The Trustees of Columbia University in the City of New York. All Rights Reserved. From 4f020be26a7307e5773cc0f648973bef6f026698 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sat, 10 Aug 2024 22:32:42 -0400 Subject: [PATCH 43/65] Fix copyright years to 2023-2024 --- doc/source/conf.py | 2 +- doc/source/license.rst | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/source/conf.py b/doc/source/conf.py index a9211517..195ee2ff 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -62,7 +62,7 @@ # General information about the project. project = "diffpy.snmf" -copyright = "2009-%Y, The Trustees of Columbia University in the City of New York" +copyright = "2023-%Y, The Trustees of Columbia University in the City of New York" # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the diff --git a/doc/source/license.rst b/doc/source/license.rst index b9364226..2dd94692 100644 --- a/doc/source/license.rst +++ b/doc/source/license.rst @@ -9,7 +9,7 @@ OPEN SOURCE LICENSE AGREEMENT ============================= BSD 3-Clause License -Copyright (c) 2009-2024, The Trustees of Columbia University in +Copyright (c) 2023-2024, The Trustees of Columbia University in the City of New York. All Rights Reserved. From 62bfc1f36f941f1ff41f550d8677fcdeeea07f8d Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sun, 11 Aug 2024 11:46:00 -0400 Subject: [PATCH 44/65] Add 2023 to LICNESE.rst --- LICENSE.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE.rst b/LICENSE.rst index 95a04ac9..74fd798d 100644 --- a/LICENSE.rst +++ b/LICENSE.rst @@ -1,6 +1,6 @@ BSD 3-Clause License -Copyright (c) 2024, The Trustees of Columbia University +Copyright (c) 2023-2024, The Trustees of Columbia University in the City of New York. All rights reserved. From 939bbddff80ae11b291e8edf97d6a8b2bac0b492 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sun, 11 Aug 2024 11:49:30 -0400 Subject: [PATCH 45/65] Make quickstart section clear with TBA --- doc/source/quickstart.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/quickstart.rst b/doc/source/quickstart.rst index 6751c104..0eba4eb8 100644 --- a/doc/source/quickstart.rst +++ b/doc/source/quickstart.rst @@ -1,6 +1,6 @@ .. _quick_start: -Tutorial +Tutorial (To be addded) ################# Welcome! This will be a quick tutorial to accquaint users with `snmf`. From 019659e1f56e2e34c8fa596b502c5116783344f0 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sun, 11 Aug 2024 22:18:37 -0400 Subject: [PATCH 46/65] Remove older doc.yml and add cookie on push --- .github/workflows/docs.yml | 2 ++ .github/workflows/documentation.yml | 37 ----------------------------- 2 files changed, 2 insertions(+), 37 deletions(-) delete mode 100644 .github/workflows/documentation.yml diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 2909d67f..0619d218 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -4,6 +4,8 @@ on: push: branches: - main + - cookie + - cookie-doc release: jobs: diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml deleted file mode 100644 index 22a7cd12..00000000 --- a/.github/workflows/documentation.yml +++ /dev/null @@ -1,37 +0,0 @@ -name: Documentation -on: - push: - branches: [ main ] - workflow_dispatch: - -jobs: - docs: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - - uses: conda-incubator/setup-miniconda@v2 - with: - activate-environment: build - auto-update-conda: true - - - name: Install requirements - run: | - conda install -n build -c conda-forge --file requirements/docs.txt --quiet --yes - sudo apt-get install python3-sphinx - - - name: Install the package - run: python -m pip install . - - - name: Build documents - run: | - conda activate build - make -C doc html - - - name: Deploy - uses: peaceiris/actions-gh-pages@v3 - with: - publish_branch: gh-pages - github_token: ${{ secrets.GITHUB_TOKEN }} - publish_dir: ./doc/build/html - force_orphan: true From e2ab8db0373a74e7b1f69480be0610223270aacb Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sun, 11 Aug 2024 22:32:54 -0400 Subject: [PATCH 47/65] Add citation to README.rst --- README.rst | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/README.rst b/README.rst index fddcf3f4..b3b88337 100644 --- a/README.rst +++ b/README.rst @@ -44,9 +44,15 @@ For more information about the diffpy.snmf library, please consult our `online d Citation -------- -If you use diffpy.snmf in a scientific publication, we would like you to cite this package as +If you use this program for a scientific research that leads +to publication, we ask that you acknowledge use of the program +by citing the following paper in your publication: + + Ran Gu, Yevgeny Rakita, Ling Lan, Zach Thatcher, Gabrielle E. Kamm, Daniel O’Nolan, Brennan Mcbride, Allison Wustrow, James R. Neilson, Karena W. Chapman, Qiang Du, and Simon J. L. Billinge, + `Stretched Non-negative Matrix Factorization + `__, + arXiv:2311.15173 [cond-mat.mtrl-sci] (2023). - diffpy.snmf Package, https://github.com/diffpy/diffpy.snmf Installation ------------ From f9074730898afa55a409cf824906ebdd811f9f1a Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Sun, 11 Aug 2024 22:35:22 -0400 Subject: [PATCH 48/65] Add citation to doc index.rst --- doc/source/index.rst | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/doc/source/index.rst b/doc/source/index.rst index 74061dde..735ea366 100644 --- a/doc/source/index.rst +++ b/doc/source/index.rst @@ -38,6 +38,19 @@ within the chemical phases through its addition of stretching factors. It is important to note that the user must specify the number of component signals to obtain from the experimental data. Non-physical results may be obtained if the number of anticipated component signals is too high. +Citation +-------- + +If you use this program for a scientific research that leads +to publication, we ask that you acknowledge use of the program +by citing the following paper in your publication: + + Ran Gu, Yevgeny Rakita, Ling Lan, Zach Thatcher, Gabrielle E. Kamm, Daniel O’Nolan, Brennan Mcbride, Allison Wustrow, James R. Neilson, Karena W. Chapman, Qiang Du, and Simon J. L. Billinge, + `Stretched Non-negative Matrix Factorization + `__, + arXiv:2311.15173 [cond-mat.mtrl-sci] (2023). + + Authors ------- From daf401db473f49f218acc30f3b12454d30135628 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Mon, 12 Aug 2024 08:22:07 -0400 Subject: [PATCH 49/65] Clean up index.rst with redundant info --- doc/source/index.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/source/index.rst b/doc/source/index.rst index 735ea366..0a1bc3f8 100644 --- a/doc/source/index.rst +++ b/doc/source/index.rst @@ -2,7 +2,7 @@ Welcome to SNMF's Documentation! ==================================== ``SNMF``: This library implements the stretched non negative matrix factorization (sNMF) and sparse stretched NMF -(ssNMF) algorithms described in ... +(ssNMF) algorithms. This algorithm is designed to do an NMF factorization on a set of signals ignoring any uniform stretching of the signal on the independent variable axis. For example, for powder diffraction data taken from samples containing multiple @@ -54,7 +54,7 @@ by citing the following paper in your publication: Authors ------- -``snmf`` implements the algorithms described in ...., developed by members of the Billinge Group at +``diffpy.snmf`` is developed by members of the Billinge Group at Columbia University, Brookhaven National Laboratory, Stony Brook University, Nankai University, and Colorado State University including Ran Gu, Yevgeny Rakita, Ling Lan, Zach Thatcher, Gabrielle E. Kamm, Daniel O'Nolan, Brennan Mcbride, Jame R. Neilson, Karena W. Chapman, Qiang Du, and Simon J. L. Billinge. From 1e5050daea60f76c7a1dc4585602aa16ba56b423 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Mon, 12 Aug 2024 08:24:12 -0400 Subject: [PATCH 50/65] Add package description in README.rst --- README.rst | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/README.rst b/README.rst index b3b88337..2d9c0dc4 100644 --- a/README.rst +++ b/README.rst @@ -37,7 +37,10 @@ A python package implementing the stretched NMF algorithm. -* LONGER DESCRIPTION HERE +``diff.pysnmf`` is a Python package that increases the insight one can obtain from a measured series time-dependent signals +through applying the stretched nonnegative matrix factorization (sNMF) and spare stretched nonnegative matrix factorization +algorithms (ssNMF). The package seeks to answer the question: "What are the structural signals composing my measured signal at +each moment in time?" For more information about the diffpy.snmf library, please consult our `online documentation `_. From 50bf2fddd42c38e601677f2cf66029e2dd7b9057 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Mon, 12 Aug 2024 08:28:19 -0400 Subject: [PATCH 51/65] Fix typo from diff.pysnmf to diffpy.snmf --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 2d9c0dc4..f1e47ca4 100644 --- a/README.rst +++ b/README.rst @@ -37,7 +37,7 @@ A python package implementing the stretched NMF algorithm. -``diff.pysnmf`` is a Python package that increases the insight one can obtain from a measured series time-dependent signals +``diffpy.snmf`` is a Python package that increases the insight one can obtain from a measured series time-dependent signals through applying the stretched nonnegative matrix factorization (sNMF) and spare stretched nonnegative matrix factorization algorithms (ssNMF). The package seeks to answer the question: "What are the structural signals composing my measured signal at each moment in time?" From b1a3b87c3d777d934a936fca9dbb248ab60a50a3 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Mon, 12 Aug 2024 11:32:01 -0400 Subject: [PATCH 52/65] Update docs, main.yml to Aug 11, 2024 --- .github/workflows/docs.yml | 15 ++++++++++++++- .github/workflows/main.yml | 14 +++++--------- 2 files changed, 19 insertions(+), 10 deletions(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 0619d218..2d1b300f 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -38,8 +38,21 @@ jobs: - name: build documents run: make -C doc html + - name: Run tests and upload coverage + shell: bash -l {0} + run: | + conda activate test + coverage run -m pytest -vv -s + coverage report -m + codecov + + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v4 + env: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} + - name: Deploy uses: peaceiris/actions-gh-pages@v4 with: github_token: ${{ secrets.GITHUB_TOKEN }} - publish_dir: ./doc/build/html + publish_dir: ./doc/build/html \ No newline at end of file diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 294255e1..bca9e90b 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -4,6 +4,7 @@ on: push: branches: - main + - CI pull_request: workflow_dispatch: @@ -18,10 +19,9 @@ jobs: - name: check out diffpy.snmf uses: actions/checkout@v3 with: - repository: diffpy/diffpy.snmf - # for bookkeeping have diffpy.snmf at the same level as everything else in the - # directory tree + repository: REPO_NAME path: . + fetch-depth: 0 # avoid shallow clone with no tags - name: initialize miniconda # this uses a marketplace action that sets up miniconda in a way that makes @@ -50,10 +50,6 @@ jobs: shell: bash -l {0} run: | conda activate test - coverage run run_tests.py + coverage run -m pytest -vv -s coverage report -m - - - name: Upload coverage reports to Codecov - uses: codecov/codecov-action@v3 -# with: -# token: ${{ secrets.CODECOV_TOKEN }} + codecov \ No newline at end of file From 917883f35d7e4b2c229348a993595a2afec2b3cc Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Mon, 12 Aug 2024 11:34:30 -0400 Subject: [PATCH 53/65] Update make.bat and makefile files to cookiecutter --- .github/workflows/docs.yml | 2 +- .github/workflows/main.yml | 2 +- doc/Makefile | 200 ++++++++++++++++++++++++++++++++++--- doc/make.bat | 5 +- 4 files changed, 192 insertions(+), 17 deletions(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 2d1b300f..bd9780bc 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -55,4 +55,4 @@ jobs: uses: peaceiris/actions-gh-pages@v4 with: github_token: ${{ secrets.GITHUB_TOKEN }} - publish_dir: ./doc/build/html \ No newline at end of file + publish_dir: ./doc/build/html diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index bca9e90b..46767a80 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -52,4 +52,4 @@ jobs: conda activate test coverage run -m pytest -vv -s coverage report -m - codecov \ No newline at end of file + codecov diff --git a/doc/Makefile b/doc/Makefile index d0c3cbf1..fdc96a7a 100644 --- a/doc/Makefile +++ b/doc/Makefile @@ -1,20 +1,194 @@ -# Minimal makefile for Sphinx documentation +# Makefile for Sphinx documentation # -# You can set these variables from the command line, and also -# from the environment for the first two. -SPHINXOPTS ?= -SPHINXBUILD ?= sphinx-build -SOURCEDIR = source +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = sphinx-build +PAPER = BUILDDIR = build +BASENAME = $(subst .,,$(subst $() $(),,diffpy.snmf)) + +# User-friendly check for sphinx-build +ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) +$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) +endif + +# Internal variables. +PAPEROPT_a4 = -D latex_paper_size=a4 +PAPEROPT_letter = -D latex_paper_size=letter +ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source +# the i18n builder cannot share the environment and doctrees with the others +I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source + +.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext -# Put it first so that "make" without argument is like "make help". help: - @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + @echo "Please use \`make ' where is one of" + @echo " html to make standalone HTML files" + @echo " dirhtml to make HTML files named index.html in directories" + @echo " singlehtml to make a single large HTML file" + @echo " pickle to make pickle files" + @echo " json to make JSON files" + @echo " htmlhelp to make HTML files and a HTML help project" + @echo " qthelp to make HTML files and a qthelp project" + @echo " devhelp to make HTML files and a Devhelp project" + @echo " epub to make an epub" + @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" + @echo " latexpdf to make LaTeX files and run them through pdflatex" + @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" + @echo " text to make text files" + @echo " man to make manual pages" + @echo " texinfo to make Texinfo files" + @echo " info to make Texinfo files and run them through makeinfo" + @echo " gettext to make PO message catalogs" + @echo " changes to make an overview of all changed/added/deprecated items" + @echo " xml to make Docutils-native XML files" + @echo " pseudoxml to make pseudoxml-XML files for display purposes" + @echo " linkcheck to check all external links for integrity" + @echo " doctest to run all doctests embedded in the documentation (if enabled)" + +clean: + rm -rf $(BUILDDIR)/* + +html: + $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." + +dirhtml: + $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." + +singlehtml: + $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml + @echo + @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." + +pickle: + $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle + @echo + @echo "Build finished; now you can process the pickle files." + +json: + $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json + @echo + @echo "Build finished; now you can process the JSON files." + +htmlhelp: + $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp + @echo + @echo "Build finished; now you can run HTML Help Workshop with the" \ + ".hhp project file in $(BUILDDIR)/htmlhelp." + +qthelp: + $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp + @echo + @echo "Build finished; now you can run "qcollectiongenerator" with the" \ + ".qhcp project file in $(BUILDDIR)/qthelp, like this:" + @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/$(BASENAME).qhcp" + @echo "To view the help file:" + @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/$(BASENAME).qhc" + +devhelp: + $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp + @echo + @echo "Build finished." + @echo "To view the help file:" + @echo "# mkdir -p $$HOME/.local/share/devhelp/$(BASENAME)" + @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/$(BASENAME)" + @echo "# devhelp" + +epub: + $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub + @echo + @echo "Build finished. The epub file is in $(BUILDDIR)/epub." + +latex: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo + @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." + @echo "Run \`make' in that directory to run these through (pdf)latex" \ + "(use \`make latexpdf' here to do that automatically)." + +latexpdf: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through pdflatex..." + $(MAKE) -C $(BUILDDIR)/latex all-pdf + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +latexpdfja: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through platex and dvipdfmx..." + $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +text: + $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text + @echo + @echo "Build finished. The text files are in $(BUILDDIR)/text." + +man: + $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man + @echo + @echo "Build finished. The manual pages are in $(BUILDDIR)/man." + +texinfo: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo + @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." + @echo "Run \`make' in that directory to run these through makeinfo" \ + "(use \`make info' here to do that automatically)." + +info: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo "Running Texinfo files through makeinfo..." + make -C $(BUILDDIR)/texinfo info + @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." + +gettext: + $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale + @echo + @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." + +changes: + $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes + @echo + @echo "The overview file is in $(BUILDDIR)/changes." + +linkcheck: + $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck + @echo + @echo "Link check complete; look for any errors in the above output " \ + "or in $(BUILDDIR)/linkcheck/output.txt." + +doctest: + $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest + @echo "Testing of doctests in the sources finished, look at the " \ + "results in $(BUILDDIR)/doctest/output.txt." + +# Manual publishing to the gh-pages branch -.PHONY: help Makefile +GITREPOPATH = $(shell cd $(CURDIR) && git rev-parse --git-dir) +GITREMOTE = origin +GITREMOTEURL = $(shell git config --get remote.$(GITREMOTE).url) +GITLASTCOMMIT = $(shell git rev-parse --short HEAD) -# Catch-all target: route all unknown targets to Sphinx using the new -# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). -%: Makefile - @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) +publish: + @test -d build/html || \ + ( echo >&2 "Run 'make html' first!"; false ) + git show-ref --verify --quiet refs/heads/gh-pages || \ + git branch --track gh-pages $(GITREMOTE)/gh-pages + test -d build/gh-pages || \ + git clone -s -b gh-pages $(GITREPOPATH) build/gh-pages + cd build/gh-pages && \ + git pull $(GITREMOTEURL) gh-pages + rsync -acv --delete --exclude=.git --exclude=.rsync-exclude \ + --exclude-from=build/gh-pages/.rsync-exclude \ + --link-dest=$(CURDIR)/build/html build/html/ build/gh-pages/ + cd build/gh-pages && \ + git add --all . && \ + git diff --cached --quiet || \ + git commit -m "Sync with the source at $(GITLASTCOMMIT)." + cd build/gh-pages && \ + git push origin gh-pages diff --git a/doc/make.bat b/doc/make.bat index 9534b018..2be83069 100644 --- a/doc/make.bat +++ b/doc/make.bat @@ -9,6 +9,7 @@ if "%SPHINXBUILD%" == "" ( ) set SOURCEDIR=source set BUILDDIR=build +set SPHINXPROJ=PackagingScientificPython if "%1" == "" goto help @@ -25,11 +26,11 @@ if errorlevel 9009 ( exit /b 1 ) -%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% +%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% goto end :help -%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% +%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% :end popd From e0afc0229e8047fa44151651d40ebb6984e10456 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Mon, 12 Aug 2024 11:37:49 -0400 Subject: [PATCH 54/65] Add repository to placeholder --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 46767a80..581821ea 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -19,7 +19,7 @@ jobs: - name: check out diffpy.snmf uses: actions/checkout@v3 with: - repository: REPO_NAME + repository: diffpy/diffpy.snmf path: . fetch-depth: 0 # avoid shallow clone with no tags From 4bc33e7aa9b64d5e86dbc94b58d9ebe3a098d088 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Mon, 12 Aug 2024 19:00:17 -0400 Subject: [PATCH 55/65] Update REAMDE longer description --- README.rst | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/README.rst b/README.rst index f1e47ca4..aa574deb 100644 --- a/README.rst +++ b/README.rst @@ -37,10 +37,13 @@ A python package implementing the stretched NMF algorithm. -``diffpy.snmf`` is a Python package that increases the insight one can obtain from a measured series time-dependent signals -through applying the stretched nonnegative matrix factorization (sNMF) and spare stretched nonnegative matrix factorization -algorithms (ssNMF). The package seeks to answer the question: "What are the structural signals composing my measured signal at -each moment in time?" +``diffpy.snmf`` implements the stretched non negative matrix factorization (sNMF) and sparse stretched NMF +(ssNMF) algorithms. + +This algorithm is designed to do an NMF factorization on a set of signals ignoring any uniform stretching of the signal +on the independent variable axis. For example, for powder diffraction data taken from samples containing multiple +chemical phases where the measurements were done at different temperatures and the materials were undergoing thermal +expansion. For more information about the diffpy.snmf library, please consult our `online documentation `_. From cfdc5296bd2415123a616e5333e34b5b01d41cf5 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Mon, 12 Aug 2024 19:22:34 -0400 Subject: [PATCH 56/65] Remove run test file --- run_tests.py | 18 ------------------ 1 file changed, 18 deletions(-) delete mode 100644 run_tests.py diff --git a/run_tests.py b/run_tests.py deleted file mode 100644 index 829a2e31..00000000 --- a/run_tests.py +++ /dev/null @@ -1,18 +0,0 @@ -#!/usr/bin/env python - -import sys - -import pytest - -if __name__ == "__main__": - # show output results from every test function - args = ["-v"] - # show the message output for skipped and expected failure tests - if len(sys.argv) > 1: - args.extend(sys.argv[1:]) - print("pytest arguments: {}".format(args)) - # # compute coverage stats for xpdAcq - # call pytest and exit with the return code from pytest so that - # travis will fail correctly if tests fail - exit_res = pytest.main(args) - sys.exit(exit_res) From 997cfa76a35c4179e40d636271561d2276b215aa Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Mon, 12 Aug 2024 19:23:13 -0400 Subject: [PATCH 57/65] Remove run test file in tests folder --- src/diffpy/snmf/tests/run.py | 34 ---------------------------------- 1 file changed, 34 deletions(-) delete mode 100644 src/diffpy/snmf/tests/run.py diff --git a/src/diffpy/snmf/tests/run.py b/src/diffpy/snmf/tests/run.py deleted file mode 100644 index 02b30c9d..00000000 --- a/src/diffpy/snmf/tests/run.py +++ /dev/null @@ -1,34 +0,0 @@ -#!/usr/bin/env python -############################################################################## -# -# (c) 2024 The Trustees of Columbia University in the City of New York. -# All rights reserved. -# -# File coded by: Billinge Group members and community contributors. -# -# See GitHub contributions for a more detailed list of contributors. -# https://github.com/diffpy/diffpy.snmf/graphs/contributors -# -# See LICENSE.rst for license information. -# -############################################################################## -"""Convenience module for executing all unit tests with -python -m diffpy.snmf.tests.run -""" - -import sys - -import pytest - -if __name__ == "__main__": - # show output results from every test function - args = ["-v"] - # show the message output for skipped and expected failure tests - if len(sys.argv) > 1: - args.extend(sys.argv[1:]) - print("pytest arguments: {}".format(args)) - # call pytest and exit with the return code from pytest - exit_res = pytest.main(args) - sys.exit(exit_res) - -# End of file From 5446afd4a874ab4efcb5330fc97e4d42710b3ef1 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Mon, 12 Aug 2024 19:59:50 -0400 Subject: [PATCH 58/65] Make a clear ref to the paper in readme --- doc/source/index.rst | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/doc/source/index.rst b/doc/source/index.rst index 0a1bc3f8..1122b4fd 100644 --- a/doc/source/index.rst +++ b/doc/source/index.rst @@ -2,7 +2,8 @@ Welcome to SNMF's Documentation! ==================================== ``SNMF``: This library implements the stretched non negative matrix factorization (sNMF) and sparse stretched NMF -(ssNMF) algorithms. +(ssNMF) algorithms described in the paper "Stretched Non-negative Matrix Factorization" by Ran Gu et al. (2023), +which is referenced under the Citation section below. This algorithm is designed to do an NMF factorization on a set of signals ignoring any uniform stretching of the signal on the independent variable axis. For example, for powder diffraction data taken from samples containing multiple @@ -54,7 +55,7 @@ by citing the following paper in your publication: Authors ------- -``diffpy.snmf`` is developed by members of the Billinge Group at +``snmf`` implements the algorithms described in ...., developed by members of the Billinge Group at Columbia University, Brookhaven National Laboratory, Stony Brook University, Nankai University, and Colorado State University including Ran Gu, Yevgeny Rakita, Ling Lan, Zach Thatcher, Gabrielle E. Kamm, Daniel O'Nolan, Brennan Mcbride, Jame R. Neilson, Karena W. Chapman, Qiang Du, and Simon J. L. Billinge. From 7ca7cedbb2155bd3eaec2a16620fbee760a7f0f7 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Tue, 13 Aug 2024 13:08:31 -0400 Subject: [PATCH 59/65] Fix doc.yml --- .github/workflows/docs.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index bd9780bc..8ca4bff8 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -5,7 +5,7 @@ on: branches: - main - cookie - - cookie-doc + - cookie-docs-fix release: jobs: @@ -55,4 +55,4 @@ jobs: uses: peaceiris/actions-gh-pages@v4 with: github_token: ${{ secrets.GITHUB_TOKEN }} - publish_dir: ./doc/build/html + publish_dir: ./doc/build/html \ No newline at end of file From 25eca53841100c7baf48a1604b3968b16a4e8262 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Tue, 13 Aug 2024 13:12:41 -0400 Subject: [PATCH 60/65] Update cooke docs.yml --- .github/workflows/docs.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 8ca4bff8..d8e8ccbd 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -26,6 +26,8 @@ jobs: - name: install requirements run: >- + coonda create -n test python=3.12 --quiet --yes + conda activate test conda install -n build -c conda-forge --file requirements/build.txt --file requirements/run.txt From fb0665ae59b658d880a4c8c04da94ad7e31b17d3 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Tue, 13 Aug 2024 13:19:15 -0400 Subject: [PATCH 61/65] Fix typo coonda to conda --- .github/workflows/docs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index d8e8ccbd..7f61e751 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -26,7 +26,7 @@ jobs: - name: install requirements run: >- - coonda create -n test python=3.12 --quiet --yes + conda create -n test python=3.12 --quiet --yes conda activate test conda install -n build -c conda-forge --file requirements/build.txt From 2103e4ca02eaee10c755ca1627a349db308f87a6 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Tue, 13 Aug 2024 13:24:36 -0400 Subject: [PATCH 62/65] fix yml syntax in docs.yml --- .github/workflows/docs.yml | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 7f61e751..36abf2ed 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -25,14 +25,10 @@ jobs: auto-update-conda: true - name: install requirements - run: >- + run: | conda create -n test python=3.12 --quiet --yes conda activate test - conda install -n build -c conda-forge - --file requirements/build.txt - --file requirements/run.txt - --file requirements/docs.txt - --quiet --yes + conda install -n build -c conda-forge --file requirements/build.txt --file requirements/run.txt --file requirements/docs.txt --quiet --yes - name: install the package run: python -m pip install . --no-deps @@ -57,4 +53,4 @@ jobs: uses: peaceiris/actions-gh-pages@v4 with: github_token: ${{ secrets.GITHUB_TOKEN }} - publish_dir: ./doc/build/html \ No newline at end of file + publish_dir: ./doc/build/html From 8faeb7d81393c94e983499050fef6a8c378704c5 Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Tue, 13 Aug 2024 13:32:08 -0400 Subject: [PATCH 63/65] Add install test.txt --- .github/workflows/docs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 36abf2ed..4eb1f135 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -28,7 +28,7 @@ jobs: run: | conda create -n test python=3.12 --quiet --yes conda activate test - conda install -n build -c conda-forge --file requirements/build.txt --file requirements/run.txt --file requirements/docs.txt --quiet --yes + conda install -n build -c conda-forge --file requirements/build.txt --file requirements/run.txt --file requirements/test.txt --file requirements/docs.txt --quiet --yes - name: install the package run: python -m pip install . --no-deps From 107faf78870a3e26e6b2a45a1e0edda6bb7c472b Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Tue, 13 Aug 2024 14:00:47 -0400 Subject: [PATCH 64/65] Debug coverage installation --- .github/workflows/docs.yml | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 4eb1f135..4626ec3a 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -22,16 +22,21 @@ jobs: - uses: conda-incubator/setup-miniconda@v2 with: activate-environment: build + environment-file: ./environment.yml + python-version: 3 auto-update-conda: true - name: install requirements run: | - conda create -n test python=3.12 --quiet --yes - conda activate test - conda install -n build -c conda-forge --file requirements/build.txt --file requirements/run.txt --file requirements/test.txt --file requirements/docs.txt --quiet --yes - - - name: install the package - run: python -m pip install . --no-deps + conda config --set always_yes yes --set changeps1 no + conda config --add channels conda-forge + conda create -n build python=3.12 + conda activate build + conda install --file requirements/build.txt + conda install --file requirements/run.txt + conda install --file requirements/test.txt + conda install --file requirements/docs.txt + python -m pip install . --no-deps - name: build documents run: make -C doc html @@ -39,7 +44,7 @@ jobs: - name: Run tests and upload coverage shell: bash -l {0} run: | - conda activate test + conda activate build coverage run -m pytest -vv -s coverage report -m codecov From 96ba768d3a5a0f77752acc79e04209468e26cccb Mon Sep 17 00:00:00 2001 From: Sangjoon Bob Lee Date: Tue, 13 Aug 2024 14:11:01 -0400 Subject: [PATCH 65/65] Remove cookie branches to prevent Codecov deployment --- .github/workflows/docs.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 4626ec3a..f7f7590a 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -4,8 +4,6 @@ on: push: branches: - main - - cookie - - cookie-docs-fix release: jobs: