def correlations_multiple(data, correlations, periodic_boundary=True, cutoff=None): """Calculate 2-point stats for a multiple auto/cross correlation Args: data: the discretized data (n_samples,n_x,n_y,n_correlation) correlation_pair: the correlation pairs periodic_boundary: whether to assume a periodic boudnary (default is true) cutoff: the subarray of the 2 point stats to keep Returns: the 2-points stats array >>> data = np.arange(18).reshape(1, 3, 3, 2) >>> out = correlations_multiple(data, [[0, 1], [1, 1]]) >>> out dask.array<stack, shape=(1, 3, 3, 2), dtype=float64, chunksize=(1, 3, 3, 1)> >>> answer = np.array([[[58, 62, 58], [94, 98, 94], [58, 62, 58]]]) + 1. / 3. >>> assert(out.compute()[...,0], answer) """ return pipe( range(data.shape[-1]), map_(lambda x: (0, x)), lambda x: correlations if correlations else x, map_( lambda x: two_point_stats( data[..., x[0]], data[..., x[1]], periodic_boundary=periodic_boundary, cutoff=cutoff, ) ), list, lambda x: da.stack(x, axis=-1), )
def solve(x_data, elastic_modulus, poissons_ratio, macro_strain=1.0, delta_x=1.0): """Solve the elasticity problem Args: x_data: microstructure with shape (n_samples, n_x, ...) elastic_modulus: the elastic modulus in each phase poissons_ration: the poissons ratio for each phase macro_strain: the macro strain delta_x: the grid spacing Returns: a dictionary of strain, displacement and stress with stress and strain of shape (n_samples, n_x, ..., 3) and displacement shape of (n_samples, n_x + 1, ..., 2) """ def solve_one_sample(property_array): return pipe( get_fields(property_array.shape[:-1], delta_x), lambda x: get_problem(x, property_array, delta_x, macro_strain), lambda x: (x, x.solve()), lambda x: get_data(property_array.shape[:-1], *x), ) convert = lambda x: _convert_properties( len(x.shape) - 1, elastic_modulus, poissons_ratio)[x] solve_multiple_samples = sequence( do(_check(len(elastic_modulus), len(poissons_ratio))), convert, map_(solve_one_sample), lambda x: zip(*x), map_(np.array), lambda x: zip(("strain", "displacement", "stress"), tuple(x)), dict, ) shape = lambda x: (x.shape[0], ) + x.shape[1:] + (3, ) dis_shape = lambda x: (x.shape[0], ) + tuple(y + 1 for y in x.shape[1:]) + (2, ) if isinstance(x_data, np.ndarray): return solve_multiple_samples(x_data) return apply_dict_func( solve_multiple_samples, x_data, dict(strain=shape(x_data), stress=shape(x_data), displacement=dis_shape(x_data)), )
def _convert_properties(dim, elastic_modulus, poissons_ratio): """Convert from elastic modulus and Poisson's ratio to the Lame parameter and shear modulus Args: dim: whether 2D or 3D elastic_modulus: the elastic modulus in each phase poissons_ration: the poissons ratio for each phase Returns: array of shape (n_phases, 2) where for example [1, 0], gives the Lame parameter in the second phase """ return pipe( zip(elastic_modulus, poissons_ratio), map_( lambda x: pipe( ElasticConstants(young=x[0], poisson=x[1]), lambda y: (y.lam, dim / 3.0 * y.mu), ) ), list, np.array, )
def get_bc(max_x_func, domain, dim, bc_dict_func): """Get the periodic boundary condition Args: max_x_func: function for finding the maximum value of x domain: the Sfepy domain dim: the x, y or z direction bc_dict_func: function to generate the bc dict Returns: the boundary condition and the sfepy function """ dim_dict = lambda x: [ ("x", per.match_x_plane), ("y", per.match_y_plane), ("z", per.match_z_plane), ][dim][x] return pipe( domain.get_mesh_bounding_box(), lambda x: PeriodicBC( "periodic_{0}".format(dim_dict(0)), list( map_( get_region_func(max_x_func(x), dim_dict(0), domain), zip(("plus", "minus"), x[:, dim][::-1]), ) ), bc_dict_func(x), match="match_{0}_plane".format(dim_dict(0)), ), lambda x: (x, Function("match_{0}_plane".format(dim_dict(0)), dim_dict(1))), )
def center_slice(x_data, cutoff): """Calculate region of interest around the center voxel upto the cutoff length Args: x_data: the data array (n_samples,n_x,n_y), first index is left unchanged cutoff: cutoff size Returns: reduced size array >>> a = np.arange(7).reshape(1, 7) >>> print(center_slice(a, 2)) [[1 2 3 4 5]] >>> a = np.arange(49).reshape(1, 7, 7) >>> print(center_slice(a, 1).shape) (1, 3, 3) >>> center_slice(np.arange(5), 1) Traceback (most recent call last): ... RuntimeError: Data should be greater than 1D ``center_slice`` can take a tuple of values >>> center_slice(np.arange(9).reshape(1, 3, 3), (1, 0)).shape (1, 3, 1) ``cutoff`` must have the correct shape >>> center_slice(np.arange(9).reshape(1, 3, 3), (1,)).shape Traceback (most recent call last): ... RuntimeError: cutoff should have length (x_data.ndim - 1) """ if x_data.ndim <= 1: raise RuntimeError("Data should be greater than 1D") if cutoff is None: return x_data if isinstance(cutoff, int): cutoff = (cutoff, ) * (x_data.ndim - 1) if x_data.ndim != len(cutoff) + 1: raise RuntimeError("cutoff should have length (x_data.ndim - 1)") return pipe( range(len(x_data.shape) - 1), map_(lambda x: slice( x_data.shape[1:][x] // 2 - cutoff[x], x_data.shape[1:][x] // 2 + cutoff[x] + 1, )), tuple, lambda x: (slice(len(x_data)), ) + x, lambda x: x_data[x], )
def calc_chem_d2f(eta): """Calculate the second derivative of the chemical free energy """ def calc_term(j): """Calculate a singe term in the free energy sum """ return a_j(j) * j * (j - 1) * eta ** (j - 2) return 0.1 * sum(map_(calc_term, range(2, 11)))
def calc_bulk_f(eta): # pragma: no cover """Calculate the bulk free energy """ def calc_term(j): """Calculate a single term in the bulk free energy """ return a_j(j) * eta ** j return 0.1 * sum(map_(calc_term, range(2, 11)))
def _func(coords, domain=None): # pylint: disable=unused-argument return pipe( (x_points, y_points, z_points), enumerate, map_(lambda x: flag_it(x[1], coords, x[0])), list, curry(fold)(lambda x, y: x & y), lambda x: (x & (coords[:, 0] < (max_x - eps(coords)))) if max_x is not None else x, np.where, first, )
def _solve_fe(x_data, elastic_modulus, poissons_ratio, macro_strain=1.0, delta_x=1.0): def solve_one_sample(property_array): return pipe( get_fields(property_array.shape[:-1], delta_x), lambda x: get_problem(x, property_array, delta_x, macro_strain), lambda x: (x, x.solve()), lambda x: get_data(property_array.shape[:-1], *x), ) convert = lambda x: _convert_properties( len(x.shape) - 1, elastic_modulus, poissons_ratio)[x] solve_multiple_samples = sequence( do(_check(len(elastic_modulus), len(poissons_ratio))), convert, map_(solve_one_sample), lambda x: zip(*x), map_(np.array), lambda x: zip(("strain", "displacement", "stress"), tuple(x)), dict, ) shape = lambda x: (x.shape[0], ) + x.shape[1:] + (3, ) dis_shape = lambda x: (x.shape[0], ) + tuple(y + 1 for y in x.shape[1:]) + (2, ) if isinstance(x_data, np.ndarray): return solve_multiple_samples(x_data) return apply_dict_func( solve_multiple_samples, x_data, dict(strain=shape(x_data), stress=shape(x_data), displacement=dis_shape(x_data)), )
def two_point_stats(arr1, arr2, periodic_boundary=True, cutoff=None): """Calculate the 2-points stats for two arrays Args: arr1: array used to calculate cross-correlations (n_samples,n_x,n_y) arr2: array used to calculate cross-correlations (n_samples,n_x,n_y) periodic_boundary: whether to assume a periodic boundary (default is true) cutoff: the subarray of the 2 point stats to keep Returns: the snipped 2-points stats >>> two_point_stats( ... da.from_array(np.arange(10).reshape(2, 5), chunks=(2, 5)), ... da.from_array(np.arange(10).reshape(2, 5), chunks=(2, 5)), ... ).shape (2, 5) """ cutoff_ = int((np.min(arr1.shape[1:]) - 1) / 2) if cutoff is None: cutoff = cutoff_ cutoff = min(cutoff, cutoff_) nonperiodic_padder = sequence( dapad( pad_width=[(0, 0)] + [(cutoff, cutoff)] * (arr1.ndim - 1), mode="constant", constant_values=0, ), lambda x: da.rechunk(x, (x.chunks[0], ) + x.shape[1:]), ) padder = identity if periodic_boundary else nonperiodic_padder nonperiodic_normalize = lambda x: x / auto_correlation( padder(np.ones_like(arr1))) normalize = identity if periodic_boundary else nonperiodic_normalize return sequence( map_(padder), list, star(cross_correlation), normalize, center_slice(cutoff=cutoff), )([arr1, arr2])
def func(min_xyz, max_xyz): def shift_(_, coors, __): return np.ones_like(coors[:, 0]) * macro_strain * (max_xyz[0] - min_xyz[0]) return pipe( [("plus", max_xyz[0]), ("minus", min_xyz[0])], map_(get_region_func(None, "x", domain)), list, lambda x: LinearCombinationBC( "lcbc", x, {"u.0": "u.0"}, Function("match_x_plane", per.match_x_plane), "shifted_periodic", arguments=(Function("shift", shift_),), ), lambda x: Conditions([x]), )
def get_periodic_bcs(domain): """Get the periodic boundary conditions Args: domain: the Sfepy domain Returns: the boundary conditions and sfepy functions """ zipped = lambda x, f: pipe( range(x, domain.get_mesh_bounding_box().shape[1]), map_(f(domain)), lambda x: zip(*x), list, ) return pipe( (zipped(0, get_periodic_bc_yz), zipped(1, get_periodic_bc_x)), lambda x: (Conditions(x[0][0] + x[1][0]), Functions(x[0][1] + x[1][1])), )
def center_slice(x_data, cutoff): """Calculate region of interest around the center voxel upto the cutoff length Args: x_data: the data array (n_samples,n_x,n_y), first index is left unchanged cutoff: cutoff size Returns: reduced size array >>> a = np.arange(7).reshape(1, 7) >>> print(center_slice(a, 2)) [[1 2 3 4 5]] >>> a = np.arange(49).reshape(1, 7, 7) >>> print(center_slice(a, 1).shape) (1, 3, 3) >>> center_slice(np.arange(5), 1) Traceback (most recent call last): ... RuntimeError: Data should be greater than 1D """ if x_data.ndim <= 1: raise RuntimeError("Data should be greater than 1D") make_slice = sequence( lambda x: x_data.shape[1:][x] // 2, lambda x: slice(x - cutoff, x + cutoff + 1) ) return pipe( range(len(x_data.shape) - 1), map_(make_slice), tuple, lambda x: (slice(len(x_data)),) + x, lambda x: x_data[x], )
def _convert_properties(dim, elastic_modulus, poissons_ratio): """Convert from elastic modulus and Poisson's ratio to the Lame parameter and shear modulus Args: dim: whether 2D or 3D elastic_modulus: the elastic modulus in each phase poissons_ration: the poissons ratio for each phase Returns: array of shape (n_phases, 2) where for example [1, 0], gives the Lame parameter in the second phase >>> assert(np.allclose( ... _convert_properties( ... dim=2, elastic_modulus=(1., 2.), poissons_ratio=(1., 1.) ... ), ... np.array([[-0.5, 1. / 6.], [-1., 1. / 3.]]) ... )) Test case with 3 phases. >>> X2D = np.array([[[0, 1, 2, 1], ... [2, 1, 0, 0], ... [1, 0, 2, 2]]]) >>> X2D_property = _convert_properties( ... dim=2, elastic_modulus=(1., 2., 3.), poissons_ratio=(1., 1., 1.) ... )[X2D] >>> lame = lame0, lame1, lame2 = -0.5, -1., -1.5 >>> mu = mu0, mu1, mu2 = 1. / 6, 1. / 3, 1. / 2 >>> lm = list(zip(lame, mu)) >>> assert(np.allclose(X2D_property, ... [[lm[0], lm[1], lm[2], lm[1]], ... [lm[2], lm[1], lm[0], lm[0]], ... [lm[1], lm[0], lm[2], lm[2]]])) Test case with 2 phases. >>> X3D = np.array([[[0, 1], ... [0, 0]], ... [[1, 1], ... [0, 1]]]) >>> X3D_property = _convert_properties( ... dim=2, elastic_modulus=(1., 2.), poissons_ratio=(1., 1.) ... )[X3D] >>> assert(np.allclose( ... X3D_property, ... [[[lm[0], lm[1]], ... [lm[0], lm[0]]], ... [[lm[1], lm[1]], ... [lm[0], lm[1]]]] ... )) """ return pipe( zip(elastic_modulus, poissons_ratio), map_(lambda x: pipe( ElasticConstants(young=x[0], poisson=x[1]), lambda y: (y.lam, dim / 3.0 * y.mu), )), list, np.array, )
step_counter=int(data["step_counter"]), params=params, wall_time=time.time(), ) def sequence(*args): """Reverse compose """ return compose(*args[::-1]) # pylint: disable=invalid-name add_options = sequence( lambda x: x.items(), map_(lambda x: click.option("--" + x[0], default=x[1])), lambda x: compose(*x), ) @click.command() @click.option("--folder", default="data", help="name of data directory") @add_options(get_params()) def main(folder, **params): # pragma: no cover """Run the calculation Args: params: the list of parameters iterations: the number of iterations Returns:
def flag_it(points, coords, index): close = lambda x: (coords[:, index] < (x + eps(coords))) & ( coords[:, index] > (x - eps(coords)) ) return pipe(points, map_(close), list, np_or, lambda x: (len(points) == 0) | x)
def two_point_stats(arr1, arr2, periodic_boundary=True, cutoff=None, mask=None): r"""Calculate the 2-points stats for two arrays The discretized two point statistics are given by .. math:: f[r \; \vert \; l, l'] = \frac{1}{S} \sum_s m[s, l] m[s + r, l'] where :math:`f[r \; \vert \; l, l']` is the conditional probability of finding the local states :math:`l` and :math:`l` at a distance and orientation away from each other defined by the vector :math:`r`. `See this paper for more details on the notation. <https://doi.org/10.1007/s40192-017-0089-0>`_ The array ``arr1[i]`` (state :math:`l`) is correlated with ``arr2[i]`` (state :math:`l'`) for each sample ``i``. Both arrays must have the same number of samples and nominal states (integer value) or continuous variables. To calculate multiple different correlations for each sample, see :func:`~pymks.correlations_multiple`. To use ``two_point_stats`` as part of a Scikit-learn pipeline, see :class:`~pymks.TwoPointCorrelation`. Args: arr1: array used to calculate cross-correlations, shape ``(n_samples,n_x,n_y)`` arr2: array used to calculate cross-correlations, shape ``(n_samples,n_x,n_y)`` periodic_boundary: whether to assume a periodic boundary (default is ``True``) cutoff: the subarray of the 2 point stats to keep mask: array specifying confidence in the measurement at a pixel, shape ``(n_samples,n_x,n_y)``. In range [0,1]. Returns: the snipped 2-points stats If both arrays are Dask arrays then a Dask array is returned. >>> out = two_point_stats( ... da.from_array(np.arange(10).reshape(2, 5), chunks=(2, 5)), ... da.from_array(np.arange(10).reshape(2, 5), chunks=(2, 5)), ... ) >>> out.chunks ((2,), (5,)) >>> out.shape (2, 5) If either of the arrays are Numpy then a Numpy array is returned. >>> two_point_stats( ... np.arange(10).reshape(2, 5), ... np.arange(10).reshape(2, 5), ... ) array([[ 3., 4., 6., 4., 3.], [48., 49., 51., 49., 48.]]) Test masking >>> array = da.array([[[1, 0 ,0], [0, 1, 1], [1, 1, 0]]]) >>> mask = da.array([[[1, 1, 1], [1, 1, 1], [1, 0, 0]]]) >>> norm_mask = da.array([[[2, 4, 3], [4, 7, 4], [3, 4, 2]]]) >>> expected = da.array([[[1, 0, 1], [1, 4, 1], [1, 0, 1]]]) / norm_mask >>> assert np.allclose( ... two_point_stats(array, array, mask=mask, periodic_boundary=False)[:, 1:-1, 1:-1], ... expected ... ) The mask must be in the range 0 to 1. >>> array = da.array([[[1, 0], [0, 1]]]) >>> mask = da.array([[[2, 0], [0, 1]]]) >>> two_point_stats(array, array, mask=mask) Traceback (most recent call last): ... RuntimeError: Mask must be in range [0,1] """ # noqa: #501 n_is_even = 1 - np.array(arr1.shape[1:]) % 2 padding = np.array(arr1.shape[1:]) // 2 nonperiodic_padder = sequence( dapad( pad_width=[(0, 0)] + list(zip(padding, padding + n_is_even)), mode="constant", constant_values=0, ), lambda x: da.rechunk(x, (x.chunks[0], ) + x.shape[1:]), ) padder = identity if periodic_boundary else nonperiodic_padder if mask is not None: if da.max(mask).compute() > 1.0 or da.min(mask).compute() < 0.0: raise RuntimeError("Mask must be in range [0,1]") mask_array = lambda arr: arr * mask normalize = lambda x: x / auto_correlation(padder(mask)) else: mask_array = identity if periodic_boundary: # The periodic normalization could always be the # auto_correlation of the mask. But for the sake of # efficiency, we specify the periodic normalization in the # case there is no mask. normalize = sequence( lambda x: x / arr1[0].size, dapad( pad_width=[(0, 0)] + list(zip(0 * n_is_even, n_is_even)), mode="wrap", ), lambda x: da.rechunk(x, (x.chunks[0], ) + x.shape[1:]), ) else: normalize = lambda x: x / auto_correlation( padder(np.ones_like(arr1))) return sequence( map_(mask_array), map_(padder), list, star(cross_correlation), normalize, center_slice(cutoff=cutoff), )([arr1, arr2])
def correlations_multiple(data, correlations, periodic_boundary=True, cutoff=None): r"""Calculate 2-point stats for a multiple auto/cross correlation The discretized two point statistics are given by .. math:: f[r \; \vert \; l, l'] = \frac{1}{S} \sum_s m[s, l] m[s + r, l'] where :math:`f[r \; \vert \; l, l']` is the conditional probability of finding the local states :math:`l` and :math:`l'` at a distance and orientation away from each other defined by the vector :math:`r`. `See this paper for more details on the notation. <https://doi.org/10.1007/s40192-017-0089-0>`_ The correlations are calulated based on pairs given in ``correlations`` for each sample. To calculate a single correlation for two arrays, see :func:`~pymks.two_point_stats`. To use ``correlations_multiple`` as part of a Scikit-learn pipeline, see :class:`~pymks.TwoPointCorrelation`. Args: data: the discretized data with shape ``(n_samples, n_x, n_y, n_state)`` correlations: the correlation pairs, ``[[i0, j0], [i1, j1], ...]`` periodic_boundary: whether to assume a periodic boundary (default is true) cutoff: the subarray of the 2 point stats to keep Returns: the 2-points stats array If ``data`` is a Numpy array then ``correlations_multiple`` will return a Numpy array. >>> data = np.arange(18).reshape(1, 3, 3, 2) >>> out_np = correlations_multiple(data, [[0, 1], [1, 1]]) >>> out_np.shape (1, 3, 3, 2) >>> answer = np.array([[[58, 62, 58], [94, 98, 94], [58, 62, 58]]]) + 2. / 3. >>> assert np.allclose(out_np[..., 0], answer) However, if ``data`` is a Dask array then a Dask array is returned. >>> data = da.from_array(data, chunks=(1, 3, 3, 2)) >>> out = correlations_multiple(data, [[0, 1], [1, 1]]) >>> out.shape (1, 3, 3, 2) >>> out.chunks ((1,), (3,), (3,), (2,)) >>> assert np.allclose(out[..., 0], answer) """ return pipe( range(data.shape[-1]), map_(lambda x: (0, x)), lambda x: correlations if correlations else x, map_(lambda x: two_point_stats( data[..., x[0]], data[..., x[1]], periodic_boundary=periodic_boundary, cutoff=cutoff, )), list, lambda x: da.stack(x, axis=-1), lambda x: da.rechunk(x, x.chunks[:-1] + (-1, )), )
def two_point_stats(arr1, arr2, mask=None, periodic_boundary=True, cutoff=None): """Calculate the 2-points stats for two arrays Args: arr1: array used to calculate cross-correlations (n_samples,n_x,n_y) arr2: array used to calculate cross-correlations (n_samples,n_x,n_y) mask: array specifying confidence in the measurement at a pixel (n_samples,n_x,n_y). In range [0,1]. periodic_boundary: whether to assume a periodic boundary (default is true) cutoff: the subarray of the 2 point stats to keep Returns: the snipped 2-points stats >>> two_point_stats( ... da.from_array(np.arange(10).reshape(2, 5), chunks=(2, 5)), ... da.from_array(np.arange(10).reshape(2, 5), chunks=(2, 5)), ... ).shape (2, 5) Test masking >>> array = da.array([[[1, 0 ,0], [0, 1, 1], [1, 1, 0]]]) >>> mask = da.array([[[1, 1, 1], [1, 1, 1], [1, 0, 0]]]) >>> norm_mask = da.array([[[2, 4, 3], [4, 7, 4], [3, 4, 2]]]) >>> expected = da.array([[[1, 0, 1], [1, 4, 1], [1, 0, 1]]]) / norm_mask >>> assert np.allclose( ... two_point_stats(array, array, mask=mask, periodic_boundary=False), ... expected ... ) The mask must be in the range 0 to 1. >>> array = da.array([[[1, 0], [0, 1]]]) >>> mask = da.array([[[2, 0], [0, 1]]]) >>> two_point_stats(array, array, mask) Traceback (most recent call last): ... RuntimeError: Mask must be in range [0,1] """ cutoff_ = int((np.min(arr1.shape[1:]) - 1) / 2) if cutoff is None: cutoff = cutoff_ cutoff = min(cutoff, cutoff_) nonperiodic_padder = sequence( dapad( pad_width=[(0, 0)] + [(cutoff, cutoff)] * (arr1.ndim - 1), mode="constant", constant_values=0, ), lambda x: da.rechunk(x, (x.chunks[0], ) + x.shape[1:]), ) padder = identity if periodic_boundary else nonperiodic_padder if mask is not None: if da.max(mask).compute() > 1.0 or da.min(mask).compute() < 0.0: raise RuntimeError("Mask must be in range [0,1]") mask_array = lambda arr: arr * mask normalize = lambda x: x / auto_correlation(padder(mask)) else: mask_array = identity if periodic_boundary: # The periodic normalization could always be the # auto_correlation of the mask. But for the sake of # efficiency, we specify the periodic normalization in the # case there is no mask. normalize = lambda x: x / arr1[0].size else: normalize = lambda x: x / auto_correlation( padder(np.ones_like(arr1))) return sequence( map_(mask_array), map_(padder), list, star(cross_correlation), normalize, center_slice(cutoff=cutoff), )([arr1, arr2])