def joint(*factors):
     """
     Return a Factor that combines the scope and beliefs of the input factors.
     
     factorA.scope, factorB.scope, factorC.scope
         -> new_factor.scope:
     [c, a, d, e], [e, f, d], [g, e, a]
         -> [c, a, d, e, f, g] up to permutation
     factorA.beliefs.shape, factorB.beliefs.shape, factorC.beliefs.shape
         -> new_factor.shape:
     (4, 2, 5, 6), (6, 7, 5), (3, 6, 2)
         -> (4, 2, 5, 6, 7, 3) up to permutation
         
     Does not modify the input.
     """
     joint_scope = list(set.union(set(), *(set(factor.scope)
                     for factor in factors)))
     #the order that the joint scope variables appear in each factor's scope
     subscopes = [[rvar for rvar in joint_scope if rvar in factor.scope]
                     for factor in factors]
     #the index mapping of those subscopes to each factor's scope
     permutations = [tuple(map(factor.scope.index, subscope))
                     for (factor, subscope) in zip(factors, subscopes)]
     #make room for new variables in order to broadcast to a common shape
     slices = [tuple(slice(None) if rvar in factor.scope
                     else scipy.newaxis for rvar in joint_scope)
                     for factor in factors]
     beliefs = [factor.beliefs.transpose(permutation)[slice_]
                     for (factor, permutation, slice_)
                     in zip(factors, permutations, slices)]
     #scipy.multiply.reduce may not return an array in all circumstances;
     #furthermore, it may return an array of sub-arrays
     #TODO: rely on numpy 1.70 for access to keepdims = True
     #joint_beliefs = scipy.multiply.reduce(beliefs, keepdims = True).squeeze()
     #FOR NOW, use broadcast_arrays to match shapes of all beliefs
     #before the multiply operation to prevent the array nesting bug...
     #also, scipy.broadcast_arrays(*[]) fails so we need an explicit check
     if not beliefs:
         return Factor.null()
     joint_beliefs = scipy.multiply.reduce(scipy.broadcast_arrays(*beliefs))
     return Factor(joint_scope, joint_beliefs)
Exemple #2
0
    def multi_epoch(self,
                    velocity,
                    sigvel,
                    mass,
                    dates,
                    pfalse=1e-4,
                    log_minv=-3,
                    log_maxv=4,
                    log_stepv=0.02):
        """Returns a callable Basefitter which computes the log-likelihood to reproduce the observed multi-epoch radial velocity distribution.

        Uses the current settings of the binary properties to calculate the distribution of radial velocity offsets due to binary orbital motions.

        Arguments:
        - `velocity`: list-like with for every star an array-like containing the observed radial velocities in km/s.
        - `sigvel`: list-like with for every star an array-like containing the measurement uncertainties in km/s.
        - `mass`: 1D array-like (or single number) giving best estimate for mass of the observed stars in solar masses.
        - `dates`: list-like with for every star an array-like containing the date of observations in years.
        - `pfalse`: probability of false detection (i.e. detecting a single star as a binary). Lowering this will decrease the number of detected binaries.
        - `log_minv`: 10_log of the lowest velocity bin in km/s (should be significantly smaller than the velocity dispersion).
        - `log_maxv`: 10_log maximum of the largest velocity bin.
        - `log_stepv`: step size in 10_log(velocity) space.
        """
        if sp.asarray(mass).ndim == 0:
            mass = [mass] * len(velocity)
        for test_length in ('sigvel', 'mass', 'dates'):
            if len(locals()[test_length]) != len(velocity):
                raise ValueError(
                    '%s does not have the same length as the velocity list' %
                    test_length)
        unique_dates = sp.unique(reduce(sp.append, dates))
        vbin = {}
        for date in unique_dates:
            vbin.update({date: self.velocity(1., time=date)[0]})

        vmean = []
        sigmean = []
        single_mass = []
        pdet_single = []
        pdet_rvvar = []
        pbin = []
        is_single = []
        vbord = 10**sp.arange(log_minv, log_maxv, log_stepv)
        vbound = sp.append(-vbord[::-1], sp.append(0, vbord))

        for mult_vel, mult_sigvel, pmass, epochs in zip(
                velocity, sigvel, mass, dates):
            epochs, mult_vel, mult_sigvel = sp.broadcast_arrays(
                epochs, mult_vel, mult_sigvel)
            if epochs.size == 1:
                mean_rv = mult_vel[0]
                mean_sig = mult_sigvel[0]
                rv_binoffset = vbin[
                    epochs[0]]  # + sp.randn(self.size) * mult_sigvel[0]
                pdet = 0.
                rvvariable = False
            else:
                weight = mult_sigvel**-2
                rv_offset_per_epoch = sp.zeros((self.size, len(epochs)))
                rv_binoffset_per_epoch = sp.zeros((self.size, len(epochs)))
                for ixepoch, (date, sv) in enumerate(zip(epochs, mult_sigvel)):
                    rv_binoffset_per_epoch[:, ixepoch] = vbin[date]
                    rv_offset_per_epoch[:,
                                        ixepoch] = rv_binoffset_per_epoch[:, ixepoch] * pmass**(
                                            1. / 3.) + sp.randn(self.size) * sv
                rv_offset_mean = sp.sum(
                    rv_offset_per_epoch * weight[sp.newaxis, :],
                    -1) / sp.sum(weight)
                chisq = sp.sum(
                    (rv_offset_per_epoch - rv_offset_mean[:, sp.newaxis])**2. *
                    weight[sp.newaxis, :], -1)
                isdetected = sp.stats.chisqprob(chisq,
                                                len(epochs) - 1) < pfalse
                pdet = float(sp.sum(isdetected)) / isdetected.size
                rv_binoffset = (sp.sum(
                    rv_binoffset_per_epoch * weight[sp.newaxis, :], -1) /
                                sp.sum(weight))[~isdetected]

                mean_rv = sp.sum(mult_vel * weight) / sp.sum(weight)
                mean_sig = sp.sum(weight)**-.5
                rvvariable = sp.stats.chisqprob(
                    sp.sum((mean_rv - mult_vel)**2 * weight),
                    len(epochs) - 1) < pfalse
            if rvvariable:
                pdet_rvvar.append(pdet)
            else:
                vmean.append(mean_rv)
                sigmean.append(mean_sig)
                pdet_single.append(pdet)
                single_mass.append(pmass)
                prob_bin = sp.histogram(
                    abs(rv_binoffset), bins=sp.append(
                        0, vbord))[0] * 1. / rv_binoffset.size
                pbin.append(
                    sp.append(prob_bin[::-1], prob_bin) / 2. /
                    (vbound[1:] - vbound[:-1]))
            is_single.append(not rvvariable)
        pbin = sp.array(pbin).T
        vmean = sp.array(vmean)
        sigmean = sp.array(sigmean)
        single_mass = sp.array(single_mass)
        pdet_single = sp.array(pdet_single)
        pdet_rvvar = sp.array(pdet_rvvar)
        is_single = sp.array(is_single, dtype='bool')

        return fitter.BinaryFit(vmean, sigmean, single_mass, vbound, pbin,
                                pdet_single, pdet_rvvar, is_single)
Exemple #3
0
    def multi_epoch(self, velocity, sigvel, mass, dates, pfalse=1e-4, log_minv=-3, log_maxv=4, log_stepv=0.02):
        """Returns a callable Basefitter which computes the log-likelihood to reproduce the observed multi-epoch radial velocity distribution.

        Uses the current settings of the binary properties to calculate the distribution of radial velocity offsets due to binary orbital motions.

        Arguments:
        - `velocity`: list-like with for every star an array-like containing the observed radial velocities in km/s.
        - `sigvel`: list-like with for every star an array-like containing the measurement uncertainties in km/s.
        - `mass`: 1D array-like (or single number) giving best estimate for mass of the observed stars in solar masses.
        - `dates`: list-like with for every star an array-like containing the date of observations in years.
        - `pfalse`: probability of false detection (i.e. detecting a single star as a binary). Lowering this will decrease the number of detected binaries.
        - `log_minv`: 10_log of the lowest velocity bin in km/s (should be significantly smaller than the velocity dispersion).
        - `log_maxv`: 10_log maximum of the largest velocity bin.
        - `log_stepv`: step size in 10_log(velocity) space.
        """
        if sp.asarray(mass).ndim == 0:
            mass = [mass] * len(velocity)
        for test_length in ('sigvel', 'mass', 'dates'):
            if len(locals()[test_length]) != len(velocity):
                raise ValueError('%s does not have the same length as the velocity list' % test_length)
        unique_dates = sp.unique(reduce(sp.append, dates))
        vbin = {}
        for date in unique_dates:
            vbin.update({date: self.velocity(1., time=date)[0]})

        vmean = []
        sigmean = []
        single_mass = []
        pdet_single = []
        pdet_rvvar = []
        pbin = []
        is_single = []
        vbord = 10 ** sp.arange(log_minv, log_maxv, log_stepv)
        vbound = sp.append(-vbord[::-1], sp.append(0, vbord))

        for mult_vel, mult_sigvel, pmass, epochs in zip(velocity, sigvel, mass, dates):
            epochs, mult_vel, mult_sigvel = sp.broadcast_arrays(epochs, mult_vel, mult_sigvel)
            if epochs.size == 1:
                mean_rv = mult_vel[0]
                mean_sig = mult_sigvel[0]
                rv_binoffset = vbin[epochs[0]]# + sp.randn(self.size) * mult_sigvel[0]
                pdet = 0.
                rvvariable = False
            else:
                weight = mult_sigvel ** -2
                rv_offset_per_epoch = sp.zeros((self.size, len(epochs)))
                rv_binoffset_per_epoch = sp.zeros((self.size, len(epochs)))
                for ixepoch, (date, sv) in enumerate(zip(epochs, mult_sigvel)):
                    rv_binoffset_per_epoch[:, ixepoch] = vbin[date]
                    rv_offset_per_epoch[:, ixepoch] = rv_binoffset_per_epoch[:, ixepoch] * pmass ** (1. / 3.)  + sp.randn(self.size) * sv
                rv_offset_mean = sp.sum(rv_offset_per_epoch * weight[sp.newaxis, :], -1) / sp.sum(weight)
                chisq = sp.sum((rv_offset_per_epoch - rv_offset_mean[:, sp.newaxis]) ** 2. * weight[sp.newaxis, :], -1)
                isdetected = sp.stats.chisqprob(chisq, len(epochs) - 1) < pfalse
                pdet = float(sp.sum(isdetected)) / isdetected.size
                rv_binoffset = (sp.sum(rv_binoffset_per_epoch * weight[sp.newaxis, :], -1) / sp.sum(weight))[~isdetected]

                mean_rv = sp.sum(mult_vel * weight) / sp.sum(weight)
                mean_sig = sp.sum(weight) ** -.5
                rvvariable = sp.stats.chisqprob(sp.sum((mean_rv - mult_vel) ** 2 * weight), len(epochs) - 1) < pfalse
            if rvvariable:
                pdet_rvvar.append(pdet)
            else:
                vmean.append(mean_rv)
                sigmean.append(mean_sig)
                pdet_single.append(pdet)
                single_mass.append(pmass)
                prob_bin = sp.histogram(abs(rv_binoffset), bins=sp.append(0, vbord))[0] * 1. / rv_binoffset.size
                pbin.append(sp.append(prob_bin[::-1], prob_bin) / 2. / (vbound[1:] - vbound[:-1]))
            is_single.append(not rvvariable)
        pbin = sp.array(pbin).T
        vmean = sp.array(vmean)
        sigmean = sp.array(sigmean)
        single_mass = sp.array(single_mass)
        pdet_single = sp.array(pdet_single)
        pdet_rvvar = sp.array(pdet_rvvar)
        is_single = sp.array(is_single, dtype='bool')

        return fitter.BinaryFit(vmean, sigmean, single_mass, vbound, pbin, pdet_single, pdet_rvvar, is_single)