instantiate a parameter ensemble from a covariance matrix

        Parameters
        ----------
        pst : pyemu.Pst
            a control file instance
        cov : (pyemu.Cov)
            covariance matrix to use for drawing
        num_reals : int
            number of realizations to generate
        use_homegrown : bool
            flag to use home-grown full cov draws...much faster
            than numpy...
        group_chunks : bool
            flag to break up draws by par groups.  Only applies
            to homegrown, full cov case. Default is False
        fill_fixed : bool
            flag to fill in fixed parameters from the pst into the
            ensemble using the parval1 from the pst.  Default is True
        enforce_bounds : bool
            flag to enforce parameter bounds from the pst.  realized
            parameter values that violate bounds are simply changed to the
            value of the violated bound.  Default is False

        Returns
        -------
        ParameterEnsemble : ParameterEnsemble

def from_gaussian_draw(cls,pst,cov,num_reals=1,use_homegrown=True,group_chunks=False,
                           fill_fixed=True,enforce_bounds=False):
        """ instantiate a parameter ensemble from a covariance matrix

        Parameters
        ----------
        pst : pyemu.Pst
            a control file instance
        cov : (pyemu.Cov)
            covariance matrix to use for drawing
        num_reals : int
            number of realizations to generate
        use_homegrown : bool
            flag to use home-grown full cov draws...much faster
            than numpy...
        group_chunks : bool
            flag to break up draws by par groups.  Only applies
            to homegrown, full cov case. Default is False
        fill_fixed : bool
            flag to fill in fixed parameters from the pst into the
            ensemble using the parval1 from the pst.  Default is True
        enforce_bounds : bool
            flag to enforce parameter bounds from the pst.  realized
            parameter values that violate bounds are simply changed to the
            value of the violated bound.  Default is False

        Returns
        -------
        ParameterEnsemble : ParameterEnsemble


        """

        # set up some column names
        #real_names = ["{0:d}".format(i)
        #              for i in range(num_reals)]
        real_names = np.arange(num_reals,dtype=np.int64)

        li = pst.parameter_data.partrans == "log"
        vals = pst.parameter_data.parval1.copy()
        vals[li] = vals.loc[li].apply(np.log10)

        # make sure everything is cool WRT ordering
        if list(vals.index.values) != cov.row_names:
            common_names = get_common_elements(vals.index.values,
                                               cov.row_names)
            if len(common_names) == 0:
                raise Exception("ParameterEnsemble::from_gaussian_draw() error: cov and pst share no common names")
            vals = vals.loc[common_names]
            cov = cov.get(common_names)
        else:
            common_names = cov.row_names

        li = pst.parameter_data.partrans.loc[common_names] == "log"
        if cov.isdiagonal:
            #print("making diagonal cov draws")
            #print("building mean and std dicts")
            arr = np.zeros((num_reals,len(vals)))
            stds = {pname:std for pname,std in zip(common_names,np.sqrt(cov.x.flatten()))}
            means = {pname:val for pname,val in zip(common_names,vals)}
            #print("numpy draw")
            arr = np.random.randn(num_reals,len(common_names))
            #print("post-processing")
            adj_pars = set(pst.adj_par_names)
            for i,pname in enumerate(common_names):
                if pname in adj_pars:
                    #s = stds[pname]
                    #v = means[pname]
                    #arr[:,i] = np.random.normal(means[pname],stds[pname],
                    #                            size=num_reals)
                    arr[:,i] = (arr[:,i] * stds[pname]) + means[pname]
                else:
                    arr[:,i] = means[pname]
            #print("build df")
            df = pd.DataFrame(data=arr,columns=common_names,index=real_names)
        else:
            if use_homegrown:
                print("making full cov draws with home-grown goodness")
                # generate standard normal vectors


                # jwhite - 18-dec-17: the cholesky version is giving significantly diff
                # results compared to eigen solve, so turning this off for now - need to
                # learn more about this...
                # use_chol = False
                # if use_chol:
                #     a = np.linalg.cholesky(cov.as_2d)
                #
                # else:
                # decompose...
                if group_chunks:
                    par_cov = pst.parameter_data.loc[cov.names,:]
                    par_cov.loc[:,"idxs"] = np.arange(cov.shape[0])
                    #print("algning cov")
                    #cov.align(list(par_cov.parnme))
                    pargps = par_cov.pargp.unique()
                    #print("reserving reals matrix")
                    reals = np.zeros((num_reals,cov.shape[0]))

                    for ipg,pargp in enumerate(pargps):
                        pnames = list(par_cov.loc[par_cov.pargp==pargp,"parnme"])
                        idxs = par_cov.loc[par_cov.pargp == pargp, "idxs"]
                        #print("{0} of {1} drawing for par group '{2}' with {3} pars "
                        #      .format(ipg+1,len(pargps),pargp, len(idxs)))

                        s,e = idxs[0],idxs[-1]
                        #print("generating snv matrix")
                        snv = np.random.randn(num_reals, len(pnames))

                        cov_pg = cov.get(pnames)
                        if len(pnames) == 1:
                            std = np.sqrt(cov_pg.x)
                            reals[:,idxs] = vals[pnames].values[0] + (snv * std)
                        else:
                            try:
                                cov_pg.inv
                            except:
                                covname = "trouble_{0}.cov".format(pargp)
                                #print('saving toubled cov matrix to {0}'.format(covname))
                                cov_pg.to_ascii(covname)
                                #print(cov_pg.get_diagonal_vector())
                                raise Exception("error inverting cov for par group '{0}',"+\
                                                "saved trouble cov to {1}".
                                                format(pargp,covname))
                            v, w = np.linalg.eigh(cov_pg.as_2d)
                            # check for near zero eig values

                            #vdiag = np.diag(v)
                            for i in range(v.shape[0]):
                                if v[i] > 1.0e-10:
                                    pass
                                else:
                                    print("near zero eigen value found",v[i],\
                                          "at index",i," of ",v.shape[0])
                                    v[i] = 0.0
                            vsqrt = np.sqrt(v)
                            vsqrt[i:] = 0.0
                            v = np.diag(vsqrt)
                            a = np.dot(w, v)
                            pg_vals = vals[pnames]
                            for i in range(num_reals):
                                #v = snv[i,:]
                                #p = np.dot(a,v)
                                reals[i,idxs] =  pg_vals + np.dot(a,snv[i,:])
                else:

                    #print("generating snv matrix")
                    snv = np.random.randn(num_reals, cov.shape[0])

                    #print("eigen solve for full cov")
                    v, w = np.linalg.eigh(cov.as_2d)
                    #w, v, other = np.linalg.svd(cov.as_2d,full_matrices=True,compute_uv=True)
                    # vdiag = np.diag(v)
                    for i in range(v.shape[0]):
                        if v[i] > 1.0e-10:
                            pass
                        else:
                            print("near zero eigen value found", v[i], \
                                  "at index", i, " of ", v.shape[0])
                            v[i] = 0.0
                    # form projection matrix
                    #print("form projection")
                    a = np.dot(w, np.sqrt(np.diag(v)))
                    #print(a)
                    # project...
                    reals = []
                    for vec in snv:
                        real = vals + np.dot(a, vec)
                        reals.append(real)

                df = pd.DataFrame(reals, columns=common_names, index=real_names)

            #vals = pe.mean_values
            else:
                #print("making full cov draws with numpy")
                df = pd.DataFrame(data=np.random.multivariate_normal(vals, cov.as_2d,num_reals),
                                  columns = common_names,index=real_names)
            #print(df.shape,cov.shape)


        df.loc[:,li] = 10.0**df.loc[:,li]

        # replace the realizations for fixed parameters with the original
        # parval1 in the control file
        #print("handling fixed pars")
        #pe.pst.parameter_data.index = pe.pst.parameter_data.parnme
        if fill_fixed:
            par = pst.parameter_data
            fixed_vals = par.loc[par.partrans.apply(lambda x: x in ["fixed","tied"]),"parval1"]
            for fname,fval in zip(fixed_vals.index,fixed_vals.values):
                #print(fname)
                df.loc[:,fname] = fval

            #print("apply tied")
        new_pe = cls.from_dataframe(pst=pst,df=df)
        if enforce_bounds:
            new_pe.enforce()
        return new_pe