def cross_spectrum(clm1, clm2, normalization='4pi', degrees=None, lmax=None, convention='power', unit='per_l', base=10.): """ Return the cross-spectrum of the spherical harmonic coefficients as a function of spherical harmonic degree. Usage ----- array = cross_spectrum(clm1, clm2, [normalization, degrees, lmax, convention, unit, base]) Returns ------- array : ndarray, shape (len(degrees)) 1-D ndarray of the spectrum. Parameters ---------- clm1 : ndarray, shape (2, lmax + 1, lmax + 1) ndarray containing the first set of spherical harmonic coefficients. clm2 : ndarray, shape (2, lmax + 1, lmax + 1) ndarray containing the second set of spherical harmonic coefficients. normalization : str, optional, default = '4pi' '4pi', 'ortho', 'schmidt', or 'unnorm' for geodesy 4pi normalized, orthonormalized, Schmidt semi-normalized, or unnormalized coefficients, respectively. lmax : int, optional, default = len(clm[0,:,0]) - 1. Maximum spherical harmonic degree to output. degrees : ndarray, optional, default = numpy.arange(lmax+1) Array containing the spherical harmonic degrees where the spectrum is computed. convention : str, optional, default = 'power' The type of spectrum to return: 'power' for power spectrum, 'energy' for energy spectrum, and 'l2norm' for the l2-norm spectrum. unit : str, optional, default = 'per_l' If 'per_l', return the total contribution to the spectrum for each spherical harmonic degree l. If 'per_lm', return the average contribution to the spectrum for each coefficient at spherical harmonic degree l. If 'per_dlogl', return the spectrum per log interval dlog_a(l). base : float, optional, default = 10. The logarithm base when calculating the 'per_dlogl' spectrum. Description ----------- This function returns either the cross-power spectrum, cross-energy spectrum, or l2-cross-norm spectrum. Total cross-power is defined as the integral of the clm1 times the conjugate of clm2 over all space, divided by the area the functions span. If the mean of the functions is zero, this is equivalent to the covariance of the two functions. The total cross-energy is the integral of clm1 times the conjugate of clm2 over all space and is 4pi times the total power. The l2-cross-norm is the sum of clm1 times the conjugate of clm2 over all angular orders as a function of spherical harmonic degree. The output spectrum can be expresed using one of three units. 'per_l' returns the contribution to the total spectrum from all angular orders at degree l. 'per_lm' returns the average contribution to the total spectrum from a single coefficient at degree l, and is equal to the 'per_l' spectrum divided by (2l+1). 'per_dlogl' returns the contribution to the total spectrum from all angular orders over an infinitessimal logarithmic degree band. The contrubution in the band dlog_a(l) is spectrum(l, 'per_dlogl')*dlog_a(l), where a is the base, and where spectrum(l, 'per_dlogl) is equal to spectrum(l, 'per_l')*l*log(a). """ if normalization.lower() not in ('4pi', 'ortho', 'schmidt', 'unnorm'): raise ValueError("The normalization must be '4pi', 'ortho', " + "'schmidt', or 'unnorm'. Input value was {:s}." .format(repr(normalization))) if convention.lower() not in ('power', 'energy', 'l2norm'): raise ValueError("convention must be 'power', 'energy', or " + "'l2norm'. Input value was {:s}" .format(repr(convention))) if unit.lower() not in ('per_l', 'per_lm', 'per_dlogl'): raise ValueError("unit must be 'per_l', 'per_lm', or 'per_dlogl'." + "Input value was {:s}".format(repr(unit))) if _np.iscomplexobj(clm1) is not _np.iscomplexobj(clm2): raise ValueError('clm1 and clm2 must both be either real or ' + 'complex. \nclm1 is complex : {:s}\n' .format(repr(_np.iscomplexobj(clm1))) + 'clm2 is complex : {:s}' .format(repr(_np.iscomplexobj(clm2)))) if lmax is None: lmax = len(clm1[0, :, 0]) - 1 if degrees is None: degrees = _np.arange(lmax+1) if _np.iscomplexobj(clm1): array = _np.empty(len(degrees), dtype='complex') else: array = _np.empty(len(degrees)) if normalization.lower() == 'unnorm': if convention.lower() == 'l2norm': raise ValueError("convention can not be set to 'l2norm' when " + "using unnormalized harmonics.") for i, l in enumerate(degrees): ms = _np.arange(l+1) conv = _factorial(l+ms) / (2. * l + 1.) / _factorial(l-ms) if _np.iscomplexobj(clm): array[i] = (conv[0:l + 1] * clm1[0, l, 0:l + 1] * clm2[0, l, 0:l + 1].conjugate()).real.sum() + \ (conv[1:l + 1] * clm1[1, l, 1:l + 1] * clm2[1, l, 1:l + 1].conjugate()).real.sum() else: conv[1:l + 1] = conv[1:l + 1] / 2. array[i] = (conv[0:l + 1] * clm1[0, l, 0:l+1]**2).sum() + \ (conv[1:l + 1] * clm2[1, l, 1:l+1]**2).sum() else: for i, l in enumerate(degrees): if _np.iscomplexobj(clm1): array[i] = (clm1[0, l, 0:l + 1] * clm2[0, l, 0:l + 1].conjugate()).sum() + \ (clm1[1, l, 1:l + 1] * clm2[1, l, 1:l + 1].conjugate()).sum() else: array[i] = (clm1[0, l, 0:l + 1] * clm2[0, l, 0:l + 1]).sum() \ + (clm1[1, l, 1:l + 1] * clm2[1, l, 1:l + 1]).sum() if convention.lower() == 'l2norm': return array else: if normalization.lower() == '4pi': pass elif normalization.lower() == 'schmidt': array /= (2. * degrees + 1.) elif normalization.lower() == 'ortho': array /= (4. * _np.pi) if convention.lower() == 'energy': array *= 4. * _np.pi if unit.lower() == 'per_l': pass elif unit.lower() == 'per_lm': array /= (2. * degrees + 1.) elif unit.lower() == 'per_dlogl': array *= degrees * _np.log(base) return array
def cross_spectrum(clm1, clm2, normalization='4pi', degrees=None, lmax=None, convention='power', unit='per_l', base=10.): """ Return the cross-spectrum of the spherical harmonic coefficients as a function of spherical harmonic degree. Usage ----- array = cross_spectrum(clm1, clm2, [normalization, degrees, lmax, convention, unit, base]) Returns ------- array : ndarray, shape (len(degrees)) 1-D ndarray of the spectrum. Parameters ---------- clm1 : ndarray, shape (2, lmax + 1, lmax + 1) ndarray containing the first set of spherical harmonic coefficients. clm2 : ndarray, shape (2, lmax + 1, lmax + 1) ndarray containing the second set of spherical harmonic coefficients. normalization : str, optional, default = '4pi' '4pi', 'ortho', 'schmidt', or 'unnorm' for geodesy 4pi normalized, orthonormalized, Schmidt semi-normalized, or unnormalized coefficients, respectively. lmax : int, optional, default = len(clm[0,:,0]) - 1. Maximum spherical harmonic degree to output. degrees : ndarray, optional, default = numpy.arange(lmax+1) Array containing the spherical harmonic degrees where the spectrum is computed. convention : str, optional, default = 'power' The type of spectrum to return: 'power' for power spectrum, 'energy' for energy spectrum, and 'l2norm' for the l2-norm spectrum. unit : str, optional, default = 'per_l' If 'per_l', return the total contribution to the spectrum for each spherical harmonic degree l. If 'per_lm', return the average contribution to the spectrum for each coefficient at spherical harmonic degree l. If 'per_dlogl', return the spectrum per log interval dlog_a(l). base : float, optional, default = 10. The logarithm base when calculating the 'per_dlogl' spectrum. Notes ----- This function returns either the cross-power spectrum, cross-energy spectrum, or l2-cross-norm spectrum. Total cross-power is defined as the integral of the clm1 times the conjugate of clm2 over all space, divided by the area the functions span. If the mean of the functions is zero, this is equivalent to the covariance of the two functions. The total cross-energy is the integral of clm1 times the conjugate of clm2 over all space and is 4pi times the total power. The l2-cross-norm is the sum of clm1 times the conjugate of clm2 over all angular orders as a function of spherical harmonic degree. The output spectrum can be expresed using one of three units. 'per_l' returns the contribution to the total spectrum from all angular orders at degree l. 'per_lm' returns the average contribution to the total spectrum from a single coefficient at degree l, and is equal to the 'per_l' spectrum divided by (2l+1). 'per_dlogl' returns the contribution to the total spectrum from all angular orders over an infinitessimal logarithmic degree band. The contrubution in the band dlog_a(l) is spectrum(l, 'per_dlogl')*dlog_a(l), where a is the base, and where spectrum(l, 'per_dlogl) is equal to spectrum(l, 'per_l')*l*log(a). """ if normalization.lower() not in ('4pi', 'ortho', 'schmidt', 'unnorm'): raise ValueError("The normalization must be '4pi', 'ortho', " + "'schmidt', or 'unnorm'. Input value was {:s}.". format(repr(normalization))) if convention.lower() not in ('power', 'energy', 'l2norm'): raise ValueError( "convention must be 'power', 'energy', or " + "'l2norm'. Input value was {:s}".format(repr(convention))) if unit.lower() not in ('per_l', 'per_lm', 'per_dlogl'): raise ValueError("unit must be 'per_l', 'per_lm', or 'per_dlogl'." + "Input value was {:s}".format(repr(unit))) if _np.iscomplexobj(clm1) is not _np.iscomplexobj(clm2): raise ValueError( 'clm1 and clm2 must both be either real or ' + 'complex. \nclm1 is complex : {:s}\n'.format( repr(_np.iscomplexobj(clm1))) + 'clm2 is complex : {:s}'.format(repr(_np.iscomplexobj(clm2)))) if lmax is None: lmax = len(clm1[0, :, 0]) - 1 if degrees is None: degrees = _np.arange(lmax + 1) if _np.iscomplexobj(clm1): array = _np.empty(len(degrees), dtype='complex') else: array = _np.empty(len(degrees)) if normalization.lower() == 'unnorm': if convention.lower() == 'l2norm': raise ValueError("convention can not be set to 'l2norm' when " + "using unnormalized harmonics.") for i, l in enumerate(degrees): ms = _np.arange(l + 1) conv = _factorial(l + ms) / (2. * l + 1.) / _factorial(l - ms) if _np.iscomplexobj(clm1): array[i] = (conv[0:l + 1] * clm1[0, l, 0:l + 1] * clm2[0, l, 0:l + 1].conjugate()).real.sum() + \ (conv[1:l + 1] * clm1[1, l, 1:l + 1] * clm2[1, l, 1:l + 1].conjugate()).real.sum() else: conv[1:l + 1] = conv[1:l + 1] / 2. array[i] = (conv[0:l + 1] * clm1[0, l, 0:l+1]**2).sum() + \ (conv[1:l + 1] * clm2[1, l, 1:l+1]**2).sum() else: for i, l in enumerate(degrees): if _np.iscomplexobj(clm1): array[i] = (clm1[0, l, 0:l + 1] * clm2[0, l, 0:l + 1].conjugate()).sum() + \ (clm1[1, l, 1:l + 1] * clm2[1, l, 1:l + 1].conjugate()).sum() else: array[i] = (clm1[0, l, 0:l + 1] * clm2[0, l, 0:l + 1]).sum() \ + (clm1[1, l, 1:l + 1] * clm2[1, l, 1:l + 1]).sum() if convention.lower() == 'l2norm': return array else: if normalization.lower() == '4pi': pass elif normalization.lower() == 'schmidt': array /= (2. * degrees + 1.) elif normalization.lower() == 'ortho': array /= (4. * _np.pi) if convention.lower() == 'energy': array *= 4. * _np.pi if unit.lower() == 'per_l': pass elif unit.lower() == 'per_lm': array /= (2. * degrees + 1.) elif unit.lower() == 'per_dlogl': array *= degrees * _np.log(base) return array
def convert(coeffs_in, normalization_in=None, normalization_out=None, csphase_in=None, csphase_out=None, lmax=None): """ Convert an array of spherical harmonic coefficients to a different normalization convention. Usage ----- coeffs_out = convert(coeffs_in, [normalization_in, normalization_out, csphase_in, csphase_out, lmax]) Returns ------- coeffs_out : ndarray, size (2, lmax+1, lmax+1) An array of spherical harmonic coefficients with the new normalization convention. Parameters ---------- coeffs_in : ndarray The array of imput spherical harmonic coefficients. normalization_in : str, optional, default = None Normalization of the output coefficients: '4pi', 'ortho' 'schmidt', or 'unnorm', for geodesy 4pi normalized, orthonormalized, Schmidt semi-normalized, or unnormalized coefficients, respectively. normalization_out : str, optional, default = None Normalization of the output coefficients: '4pi', 'ortho' 'schmidt', or 'unnorm', for geodesy 4pi normalized, orthonormalized, Schmidt semi-normalized, or unnormalized coefficients, respectively. csphase_in : int, optional, default = None Condon-Shortley phase convention of the input coefficients: 1 to exclude the phase factor, or -1 to include it. csphase_out : int, optional, default = None Condon-Shortley phase convention of the output coefficients: 1 to exclude the phase factor, or -1 to include it. lmax : int, optional, default = coeffs.shape[1] - 1 Maximum spherical harmonic degree to output. If lmax is larger than that of the input coefficients, the output array will be zero padded. Description ----------- This routine will convert an array of spherical harmonic coefficients to a different normalization convention and different Condon-Shortley phase convention. Optionally, a different maximum spherical harmonic degree can be specified. If this degree is smaller than that of the input coefficients, the input coefficients will be truncated. If this degree is larger than the input coefficients, then the output coefficients will be zero padded. """ # check argument consistency if normalization_in is not None: if type(normalization_in) != str: raise ValueError('normalization_in must be a string. ' + 'Input type was {:s}' .format(str(type(normalization_in)))) if normalization_in.lower() not in ('4pi', 'ortho', 'schmidt', 'unnorm'): raise ValueError( "normalization_in must be '4pi', 'ortho', 'schmidt', or " + "'unnorm'. Provided value was {:s}" .format(repr(normalization_in)) ) if normalization_out is None: raise ValueError("normalization_in and normalization_out " + "must both be specified.") if normalization_out is not None: if type(normalization_out) != str: raise ValueError('normalization_out must be a string. ' + 'Input type was {:s}' .format(str(type(normalization_out)))) if normalization_out.lower() not in ('4pi', 'ortho', 'schmidt', 'unnorm'): raise ValueError( "normalization_out must be '4pi', 'ortho', 'schmidt', or" + " 'unnorm'. Provided value was {:s}" .format(repr(normalization_out)) ) if normalization_in is None: raise ValueError("normalization_in and normalization_out " + "must both be specified.") if csphase_in is not None: if csphase_in != 1 and csphase_in != -1: raise ValueError( "csphase_in must be 1 or -1. Input value was {:s}" .format(repr(csphase_in))) if csphase_out is None: raise ValueError("csphase_in and csphase_out must both be " + "specified.") if csphase_out is not None: if csphase_out != 1 and csphase_out != -1: raise ValueError( "csphase_out must be 1 or -1. Input value was {:s}" .format(repr(csphase_out))) if csphase_in is None: raise ValueError("csphase_in and csphase_out must both be " + "specified.") lmaxin = coeffs_in.shape[1] - 1 if ((normalization_in == 'unnorm' or normalization_out == 'unnorm') and lmaxin > 85): _warnings.warn("Conversions with unnormalized coefficients are " + "stable only for degrees less than or equal to " + "85. lmax of the input coefficients will be " + "truncated after degree 85. The spherical " + "harmonic degree of the input coefficients was " + "{:d}.".format(lmaxin), category=RuntimeWarning) lmaxin = 85 if lmax is None: lmaxout = lmaxin else: lmaxout = lmax lconv = min(lmaxin, lmaxout) degrees = _np.arange(lconv + 1) if _np.iscomplexobj(coeffs_in): coeffs = _np.zeros((2, lmaxout+1, lmaxout+1), dtype=complex) else: coeffs = _np.zeros((2, lmaxout+1, lmaxout+1)) coeffs[:, :lconv+1, :lconv+1] = coeffs_in[:, :lconv+1, :lconv+1] if normalization_in == normalization_out: pass elif normalization_in == '4pi' and normalization_out == 'schmidt': for l in degrees: coeffs[:, l, :l+1] *= _np.sqrt(2. * l + 1.) elif normalization_in == '4pi' and normalization_out == 'ortho': coeffs *= _np.sqrt(4. * _np.pi) elif normalization_in == '4pi' and normalization_out == 'unnorm': for l in degrees: ms = _np.arange(l+1) conv = (2. * l + 1.) * _factorial(l-ms) / _factorial(l+ms) if not _np.iscomplexobj(coeffs): conv[1:] *= 2. coeffs[:, l, :l+1] *= _np.sqrt(conv) elif normalization_in == 'schmidt' and normalization_out == '4pi': for l in degrees: coeffs[:, l, :l+1] /= _np.sqrt(2. * l + 1.) elif normalization_in == 'schmidt' and normalization_out == 'ortho': for l in degrees: coeffs[:, l, :l+1] *= _np.sqrt(4. * _np.pi / (2. * l + 1.)) elif normalization_in == 'schmidt' and normalization_out == 'unnorm': for l in degrees: ms = _np.arange(l+1) conv = _factorial(l-ms) / _factorial(l+ms) if not _np.iscomplexobj(coeffs): conv[1:] *= 2. coeffs[:, l, :l+1] *= _np.sqrt(conv) elif normalization_in == 'ortho' and normalization_out == '4pi': coeffs /= _np.sqrt(4. * _np.pi) elif normalization_in == 'ortho' and normalization_out == 'schmidt': for l in degrees: coeffs[:, l, :l+1] *= _np.sqrt((2. * l + 1.) / (4. * _np.pi)) elif normalization_in == 'ortho' and normalization_out == 'unnorm': for l in degrees: ms = _np.arange(l+1) conv = (2. * l + 1.) * _factorial(l-ms) \ / 4. / _np.pi / _factorial(l+ms) if not _np.iscomplexobj(coeffs): conv[1:] *= 2. coeffs[:, l, :l+1] *= _np.sqrt(conv) elif normalization_in == 'unnorm' and normalization_out == '4pi': for l in degrees: ms = _np.arange(l+1) conv = _factorial(l+ms) / (2. * l + 1.) / _factorial(l-ms) if not _np.iscomplexobj(coeffs): conv[1:] /= 2. coeffs[:, l, :l+1] *= _np.sqrt(conv) elif normalization_in == 'unnorm' and normalization_out == 'schmidt': for l in degrees: ms = _np.arange(l+1) conv = _factorial(l+ms) / _factorial(l-ms) if not _np.iscomplexobj(coeffs): conv[1:] /= 2. coeffs[:, l, :l+1] *= _np.sqrt(conv) elif normalization_in == 'unnorm' and normalization_out == 'ortho': for l in degrees: ms = _np.arange(l+1) conv = 4. * _np.pi * _factorial(l+ms) / (2. * l + 1.) / \ _factorial(l-ms) if not _np.iscomplexobj(coeffs): conv[1:] /= 2. coeffs[:, l, :l+1] *= _np.sqrt(conv) if csphase_in != csphase_out: for m in degrees: if m % 2 == 1: coeffs[:, m:lconv+1, m] = - coeffs[:, m:lconv+1, m] return coeffs
def convert(coeffs_in, normalization_in=None, normalization_out=None, csphase_in=None, csphase_out=None, lmax=None): """ Convert an array of spherical harmonic coefficients to a different normalization convention. Usage ----- coeffs_out = convert(coeffs_in, [normalization_in, normalization_out, csphase_in, csphase_out, lmax]) Returns ------- coeffs_out : ndarray, size (2, lmax+1, lmax+1) An array of spherical harmonic coefficients with the new normalization convention. Parameters ---------- coeffs_in : ndarray The array of imput spherical harmonic coefficients. normalization_in : str, optional, default = None Normalization of the output coefficients: '4pi', 'ortho' 'schmidt', or 'unnorm', for geodesy 4pi normalized, orthonormalized, Schmidt semi-normalized, or unnormalized coefficients, respectively. normalization_out : str, optional, default = None Normalization of the output coefficients: '4pi', 'ortho' 'schmidt', or 'unnorm', for geodesy 4pi normalized, orthonormalized, Schmidt semi-normalized, or unnormalized coefficients, respectively. csphase_in : int, optional, default = None Condon-Shortley phase convention of the input coefficients: 1 to exclude the phase factor, or -1 to include it. csphase_out : int, optional, default = None Condon-Shortley phase convention of the output coefficients: 1 to exclude the phase factor, or -1 to include it. lmax : int, optional, default = coeffs.shape[1] - 1 Maximum spherical harmonic degree to output. If lmax is larger than that of the input coefficients, the output array will be zero padded. Description ----------- This routine will convert an array of spherical harmonic coefficients to a different normalization convention and different Condon-Shortley phase convention. Optionally, a different maximum spherical harmonic degree can be specified. If this degree is smaller than that of the input coefficients, the input coefficients will be truncated. If this degree is larger than the input coefficients, then the output coefficients will be zero padded. """ # check argument consistency if normalization_in is not None: if type(normalization_in) != str: raise ValueError( 'normalization_in must be a string. ' + 'Input type was {:s}'.format(str(type(normalization_in)))) if normalization_in.lower() not in ('4pi', 'ortho', 'schmidt', 'unnorm'): raise ValueError( "normalization_in must be '4pi', 'ortho', 'schmidt', or " + "'unnorm'. Provided value was {:s}".format( repr(normalization_in))) if normalization_out is None: raise ValueError("normalization_in and normalization_out " + "must both be specified.") if normalization_out is not None: if type(normalization_out) != str: raise ValueError( 'normalization_out must be a string. ' + 'Input type was {:s}'.format(str(type(normalization_out)))) if normalization_out.lower() not in ('4pi', 'ortho', 'schmidt', 'unnorm'): raise ValueError( "normalization_out must be '4pi', 'ortho', 'schmidt', or" + " 'unnorm'. Provided value was {:s}".format( repr(normalization_out))) if normalization_in is None: raise ValueError("normalization_in and normalization_out " + "must both be specified.") if csphase_in is not None: if csphase_in != 1 and csphase_in != -1: raise ValueError( "csphase_in must be 1 or -1. Input value was {:s}".format( repr(csphase_in))) if csphase_out is None: raise ValueError("csphase_in and csphase_out must both be " + "specified.") if csphase_out is not None: if csphase_out != 1 and csphase_out != -1: raise ValueError( "csphase_out must be 1 or -1. Input value was {:s}".format( repr(csphase_out))) if csphase_in is None: raise ValueError("csphase_in and csphase_out must both be " + "specified.") lmaxin = coeffs_in.shape[1] - 1 if ((normalization_in == 'unnorm' or normalization_out == 'unnorm') and lmaxin > 85): _warnings.warn("Conversions with unnormalized coefficients are " + "stable only for degrees less than or equal to " + "85. lmax of the input coefficients will be " + "truncated after degree 85. The spherical " + "harmonic degree of the input coefficients was " + "{:d}.".format(lmaxin), category=RuntimeWarning) lmaxin = 85 if lmax is None: lmaxout = lmaxin else: lmaxout = lmax lconv = min(lmaxin, lmaxout) degrees = _np.arange(lconv + 1) if _np.iscomplexobj(coeffs_in): coeffs = _np.zeros((2, lmaxout + 1, lmaxout + 1), dtype=complex) else: coeffs = _np.zeros((2, lmaxout + 1, lmaxout + 1)) coeffs[:, :lconv + 1, :lconv + 1] = coeffs_in[:, :lconv + 1, :lconv + 1] if normalization_in == normalization_out: pass elif normalization_in == '4pi' and normalization_out == 'schmidt': for l in degrees: coeffs[:, l, :l + 1] *= _np.sqrt(2. * l + 1.) elif normalization_in == '4pi' and normalization_out == 'ortho': coeffs *= _np.sqrt(4. * _np.pi) elif normalization_in == '4pi' and normalization_out == 'unnorm': for l in degrees: ms = _np.arange(l + 1) conv = (2. * l + 1.) * _factorial(l - ms) / _factorial(l + ms) if not _np.iscomplexobj(coeffs): conv[1:] *= 2. coeffs[:, l, :l + 1] *= _np.sqrt(conv) elif normalization_in == 'schmidt' and normalization_out == '4pi': for l in degrees: coeffs[:, l, :l + 1] /= _np.sqrt(2. * l + 1.) elif normalization_in == 'schmidt' and normalization_out == 'ortho': for l in degrees: coeffs[:, l, :l + 1] *= _np.sqrt(4. * _np.pi / (2. * l + 1.)) elif normalization_in == 'schmidt' and normalization_out == 'unnorm': for l in degrees: ms = _np.arange(l + 1) conv = _factorial(l - ms) / _factorial(l + ms) if not _np.iscomplexobj(coeffs): conv[1:] *= 2. coeffs[:, l, :l + 1] *= _np.sqrt(conv) elif normalization_in == 'ortho' and normalization_out == '4pi': coeffs /= _np.sqrt(4. * _np.pi) elif normalization_in == 'ortho' and normalization_out == 'schmidt': for l in degrees: coeffs[:, l, :l + 1] *= _np.sqrt((2. * l + 1.) / (4. * _np.pi)) elif normalization_in == 'ortho' and normalization_out == 'unnorm': for l in degrees: ms = _np.arange(l + 1) conv = (2. * l + 1.) * _factorial(l-ms) \ / 4. / _np.pi / _factorial(l+ms) if not _np.iscomplexobj(coeffs): conv[1:] *= 2. coeffs[:, l, :l + 1] *= _np.sqrt(conv) elif normalization_in == 'unnorm' and normalization_out == '4pi': for l in degrees: ms = _np.arange(l + 1) conv = _factorial(l + ms) / (2. * l + 1.) / _factorial(l - ms) if not _np.iscomplexobj(coeffs): conv[1:] /= 2. coeffs[:, l, :l + 1] *= _np.sqrt(conv) elif normalization_in == 'unnorm' and normalization_out == 'schmidt': for l in degrees: ms = _np.arange(l + 1) conv = _factorial(l + ms) / _factorial(l - ms) if not _np.iscomplexobj(coeffs): conv[1:] /= 2. coeffs[:, l, :l + 1] *= _np.sqrt(conv) elif normalization_in == 'unnorm' and normalization_out == 'ortho': for l in degrees: ms = _np.arange(l + 1) conv = 4. * _np.pi * _factorial(l+ms) / (2. * l + 1.) / \ _factorial(l-ms) if not _np.iscomplexobj(coeffs): conv[1:] /= 2. coeffs[:, l, :l + 1] *= _np.sqrt(conv) if csphase_in != csphase_out: for m in degrees: if m % 2 == 1: coeffs[:, m:lconv + 1, m] = -coeffs[:, m:lconv + 1, m] return coeffs
def mag_spectrum(clm, a, r, potential=False, normalization='schmidt', degrees=None, lmax=None, convention='power', unit='per_l', base=10.): """ Return the spectrum of the magnetic field as a function of spherical harmonic degree. Usage ----- array = mag_spectrum(clm, a, r, [potential, normalization, degrees, lmax, convention, unit, base]) Returns ------- array : ndarray, shape (len(degrees)) 1-D ndarray of the spectrum. Parameters ---------- clm : ndarray, shape (2, lmax + 1, lmax + 1) ndarray containing the spherical harmonic coefficients. a : float The reference radius of the spherical harmonic coefficients. r : float The radius at which the spectrum is evaluated. potential : bool, optional, default = False If True, calculate the spectrum of the magnetic potential. Otherwise, calculate the spectrum of the magnetic intensity (default). normalization : str, optional, default = 'schmidt' '4pi', 'ortho', 'schmidt', or 'unnorm' for geodesy 4pi normalized, orthonormalized, Schmidt semi-normalized, or unnormalized coefficients, respectively. lmax : int, optional, default = len(clm[0,:,0]) - 1. Maximum spherical harmonic degree to output. degrees : ndarray, optional, default = numpy.arange(lmax+1) Array containing the spherical harmonic degrees where the spectrum is computed. convention : str, optional, default = 'power' The type of spectrum to return: 'power' for power spectrum, 'energy' for energy spectrum, and 'l2norm' for the l2-norm spectrum. unit : str, optional, default = 'per_l' If 'per_l', return the total contribution to the spectrum for each spherical harmonic degree l. If 'per_lm', return the average contribution to the spectrum for each coefficient at spherical harmonic degree l. If 'per_dlogl', return the spectrum per log interval dlog_a(l). base : float, optional, default = 10. The logarithm base when calculating the 'per_dlogl' spectrum. Notes ----- This function returns either the power spectrum, energy spectrum, or l2-norm spectrum of a global magnetic field. Total power is defined as the integral of the function squared over all space, divided by the area the function spans. If the mean of the function is zero, this is equivalent to the variance of the function. The total energy is the integral of the function squared over all space and is 4pi times the total power. The l2-norm is simply the sum of the magnitude of the spherical harmonic coefficients squared. The default behaviour of this function is to return the Lowes-Mauersberger spectrum, which is the total power of the magnetic field strength as a function of spherical harmonic degree (see below). The magnetic potential and magnetic field are defined respectively by the two equations U = a**2 \sum_{l, m}^L (a/r)**(l+1) glm Ylm, B = - \Del U. Here, a is the reference radius of the spherical harmonic coefficients, r is the radius at which the function is evalulated, and L is the maximum spherical harmonic degree of the expansion. If the optional parameter potential is set to True, the spectrum of the magnetic potential will be calculated. Otherwise, the default behavior is to calculate the spectrum of the magnetic field intensity. The output spectrum can be expresed using one of three units. 'per_l' returns the contribution to the total spectrum from all angular orders at degree l. 'per_lm' returns the average contribution to the total spectrum from a single coefficient at degree l, and is equal to the 'per_l' spectrum divided by (2l+1). 'per_dlogl' returns the contribution to the total spectrum from all angular orders over an infinitessimal logarithmic degree band. The contrubution in the band dlog_a(l) is spectrum(l, 'per_dlogl')*dlog_a(l), where a is the base, and where spectrum(l, 'per_dlogl) is equal to spectrum(l, 'per_l')*l*log(a). When no optional parameters are specified, the Lowes-Mauersberger power spectrum is calculated. Explicitly, this corrresponds to convention = 'power', unit = 'per_l', and potential = False. """ if normalization.lower() not in ('4pi', 'ortho', 'schmidt', 'unnorm'): raise ValueError("The normalization must be '4pi', 'ortho', " + "'schmidt', or 'unnorm'. Input value was {:s}.". format(repr(normalization))) if convention.lower() not in ('power', 'energy', 'l2norm'): raise ValueError( "convention must be 'power', 'energy', or " + "'l2norm'. Input value was {:s}".format(repr(convention))) if unit.lower() not in ('per_l', 'per_lm', 'per_dlogl'): raise ValueError("unit must be 'per_l', 'per_lm', or 'per_dlogl'." + "Input value was {:s}".format(repr(unit))) if lmax is None: lmax = len(clm[0, :, 0]) - 1 if degrees is None: degrees = _np.arange(lmax + 1) array = _np.empty(len(degrees)) if normalization.lower() == 'unnorm': if convention.lower() == 'l2norm': raise ValueError("convention can not be set to 'l2norm' when " + "using unnormalized harmonics.") for i, l in enumerate(degrees): ms = _np.arange(l + 1) conv = _factorial(l + ms) / (2. * l + 1.) / _factorial(l - ms) if _np.iscomplexobj(clm): array[i] = (conv[0:l + 1] * clm[0, l, 0:l + 1] * clm[0, l, 0:l + 1].conjugate()).real.sum() + \ (conv[1:l + 1] * clm[1, l, 1:l + 1] * clm[1, l, 1:l + 1].conjugate()).real.sum() else: conv[1:l + 1] = conv[1:l + 1] / 2. array[i] = (conv[0:l + 1] * clm[0, l, 0:l+1]**2).sum() + \ (conv[1:l + 1] * clm[1, l, 1:l+1]**2).sum() else: for i, l in enumerate(degrees): if _np.iscomplexobj(clm): array[i] = (clm[0, l, 0:l + 1] * clm[0, l, 0:l + 1].conjugate()).real.sum() + \ (clm[1, l, 1:l + 1] * clm[1, l, 1:l + 1].conjugate()).real.sum() else: array[i] = (clm[0, l, 0:l+1]**2).sum() + \ (clm[1, l, 1:l+1]**2).sum() if convention.lower() == 'l2norm': return array else: if normalization.lower() == '4pi': pass elif normalization.lower() == 'schmidt': array /= (2. * degrees + 1.) elif normalization.lower() == 'ortho': array /= (4. * _np.pi) if convention.lower() == 'energy': array *= 4. * _np.pi if unit.lower() == 'per_l': pass elif unit.lower() == 'per_lm': array /= (2. * degrees + 1.) elif unit.lower() == 'per_dlogl': array *= degrees * _np.log(base) if potential is True: array *= (a**2) * (a / r)**(2 * degrees + 2) else: array *= (a / r)**(2 * degrees + 4) * (degrees + 1) * (2 * degrees + 1) return array
def surprise(expectation, outcome, k_max=None): if not _numpy.issubdtype(expectation.dtype, _numpy.float): raise ValueError("expectation must be of float type") if not _numpy.issubdtype(outcome.dtype, _numpy.integer): raise ValueError("outcome must be of integer type") if k_max is None: k_max = outcome.max()*2 max_outcome = outcome.max() surprise_pix = _numpy.zeros(expectation.shape) k_mask = outcome < k_max # surprise_pix[k_mask] = # -_numpy.log(expectation[k_mask]**outcome[k_mask] # *_numpy.exp(-expectation[k_mask]) # / _factorial(outcome[k_mask])) surprise_pix[k_mask] = -(outcome[k_mask]*_numpy.log(expectation[k_mask]) + -expectation[k_mask] - _numpy.log(_factorial(outcome[k_mask]))) surprise_pix[~k_mask] = (-(expectation[~k_mask] - outcome[~k_mask])**2 / (2*expectation[~k_mask])) surprise = surprise_pix.sum() surprise_expectation_pix = _numpy.zeros(surprise_pix.shape) surprise_expectation = 0 for k in range(max_outcome): if k < k_max: fac_k = _factorial(k) this_exp = (expectation**k*_numpy.exp(-expectation)/fac_k * _numpy.log(expectation**k * _numpy.exp(-expectation) / fac_k)) this_exp[_numpy.isnan(this_exp)] = 0 surprise_expectation_pix -= this_exp else: this_exp = (_numpy.exp(-(expectation - k)**2 / (2 * expectation)) * (-(expectation - k)**2 / (2 * expectation))) surprise_expectation_pix -= this_exp surprise_expectation = surprise_expectation_pix.sum() surprise_variance_pix = _numpy.zeros(surprise_pix.shape) for k in range(max_outcome): if k < k_max: fac_k = _factorial(k) surprise_variance_pix += ( expectation**k * _numpy.exp(-expectation) / fac_k * (k*_numpy.log(expectation) - expectation - _numpy.log(fac_k))**2) else: surprise_variance_pix += ( _numpy.exp(-(expectation - k)**2 / (2*expectation)) * (-(expectation - k)**2 / (2*expectation))**2) surprise_variance_pix -= surprise_expectation_pix**2 surprise_variance = surprise_variance_pix.sum() score = abs(surprise - surprise_expectation) / surprise_variance return score
def mag_spectrum(clm, a, r, potential=False, normalization='schmidt', degrees=None, lmax=None, convention='power', unit='per_l', base=10.): """ Return the spectrum of the magnetic field as a function of spherical harmonic degree. Usage ----- array = mag_spectrum(clm, a, r, [potential, normalization, degrees, lmax, convention, unit, base]) Returns ------- array : ndarray, shape (len(degrees)) 1-D ndarray of the spectrum. Parameters ---------- clm : ndarray, shape (2, lmax + 1, lmax + 1) ndarray containing the spherical harmonic coefficients. a : float The reference radius of the spherical harmonic coefficients. r : float The radius at which the spectrum is evaluated. potential : bool, optional, default = False If True, calculate the spectrum of the magnetic potential. Otherwise, calculate the spectrum of the magnetic intensity (default). normalization : str, optional, default = 'schmidt' '4pi', 'ortho', 'schmidt', or 'unnorm' for geodesy 4pi normalized, orthonormalized, Schmidt semi-normalized, or unnormalized coefficients, respectively. lmax : int, optional, default = len(clm[0,:,0]) - 1. Maximum spherical harmonic degree to output. degrees : ndarray, optional, default = numpy.arange(lmax+1) Array containing the spherical harmonic degrees where the spectrum is computed. convention : str, optional, default = 'power' The type of spectrum to return: 'power' for power spectrum, 'energy' for energy spectrum, and 'l2norm' for the l2-norm spectrum. unit : str, optional, default = 'per_l' If 'per_l', return the total contribution to the spectrum for each spherical harmonic degree l. If 'per_lm', return the average contribution to the spectrum for each coefficient at spherical harmonic degree l. If 'per_dlogl', return the spectrum per log interval dlog_a(l). base : float, optional, default = 10. The logarithm base when calculating the 'per_dlogl' spectrum. Description ----------- This function returns either the power spectrum, energy spectrum, or l2-norm spectrum of a global magnetic field. Total power is defined as the integral of the function squared over all space, divided by the area the function spans. If the mean of the function is zero, this is equivalent to the variance of the function. The total energy is the integral of the function squared over all space and is 4pi times the total power. The l2-norm is simply the sum of the magnitude of the spherical harmonic coefficients squared. The default behaviour of this function is to return the Lowes-Mauersberger spectrum, which is the total power of the magnetic field strength as a function of spherical harmonic degree (see below). The magnetic potential and magnetic field are defined respectively by the two equations U = a**2 \sum_{l, m}^L (a/r)**(l+1) glm Ylm, B = - \Del U. Here, a is the reference radius of the spherical harmonic coefficients, r is the radius at which the function is evalulated, and L is the maximum spherical harmonic degree of the expansion. If the optional parameter potential is set to True, the spectrum of the magnetic potential will be calculated. Otherwise, the default behavior is to calculate the spectrum of the magnetic field intensity. The output spectrum can be expresed using one of three units. 'per_l' returns the contribution to the total spectrum from all angular orders at degree l. 'per_lm' returns the average contribution to the total spectrum from a single coefficient at degree l, and is equal to the 'per_l' spectrum divided by (2l+1). 'per_dlogl' returns the contribution to the total spectrum from all angular orders over an infinitessimal logarithmic degree band. The contrubution in the band dlog_a(l) is spectrum(l, 'per_dlogl')*dlog_a(l), where a is the base, and where spectrum(l, 'per_dlogl) is equal to spectrum(l, 'per_l')*l*log(a). When no optional parameters are specified, the Lowes-Mauersberger power spectrum is calculated. Explicitly, this corrresponds to convention = 'power', unit = 'per_l', and potential = False. """ if normalization.lower() not in ('4pi', 'ortho', 'schmidt', 'unnorm'): raise ValueError("The normalization must be '4pi', 'ortho', " + "'schmidt', or 'unnorm'. Input value was {:s}." .format(repr(normalization))) if convention.lower() not in ('power', 'energy', 'l2norm'): raise ValueError("convention must be 'power', 'energy', or " + "'l2norm'. Input value was {:s}" .format(repr(convention))) if unit.lower() not in ('per_l', 'per_lm', 'per_dlogl'): raise ValueError("unit must be 'per_l', 'per_lm', or 'per_dlogl'." + "Input value was {:s}".format(repr(unit))) if lmax is None: lmax = len(clm[0, :, 0]) - 1 if degrees is None: degrees = _np.arange(lmax+1) array = _np.empty(len(degrees)) if normalization.lower() == 'unnorm': if convention.lower() == 'l2norm': raise ValueError("convention can not be set to 'l2norm' when " + "using unnormalized harmonics.") for i, l in enumerate(degrees): ms = _np.arange(l+1) conv = _factorial(l+ms) / (2. * l + 1.) / _factorial(l-ms) if _np.iscomplexobj(clm): array[i] = (conv[0:l + 1] * clm[0, l, 0:l + 1] * clm[0, l, 0:l + 1].conjugate()).real.sum() + \ (conv[1:l + 1] * clm[1, l, 1:l + 1] * clm[1, l, 1:l + 1].conjugate()).real.sum() else: conv[1:l + 1] = conv[1:l + 1] / 2. array[i] = (conv[0:l + 1] * clm[0, l, 0:l+1]**2).sum() + \ (conv[1:l + 1] * clm[1, l, 1:l+1]**2).sum() else: for i, l in enumerate(degrees): if _np.iscomplexobj(clm): array[i] = (clm[0, l, 0:l + 1] * clm[0, l, 0:l + 1].conjugate()).real.sum() + \ (clm[1, l, 1:l + 1] * clm[1, l, 1:l + 1].conjugate()).real.sum() else: array[i] = (clm[0, l, 0:l+1]**2).sum() + \ (clm[1, l, 1:l+1]**2).sum() if convention.lower() == 'l2norm': return array else: if normalization.lower() == '4pi': pass elif normalization.lower() == 'schmidt': array /= (2. * degrees + 1.) elif normalization.lower() == 'ortho': array /= (4. * _np.pi) if convention.lower() == 'energy': array *= 4. * _np.pi if unit.lower() == 'per_l': pass elif unit.lower() == 'per_lm': array /= (2. * degrees + 1.) elif unit.lower() == 'per_dlogl': array *= degrees * _np.log(base) if potential is True: array *= (a**2) * (a / r)**(2 * degrees + 2) else: array *= (a / r)**(2 * degrees + 4) * (degrees + 1) * (2 * degrees + 1) return array