//  [python]
def setup(self, model_params, integration_params, initial_means=None):
        """ Before the LAND can be fit, model and integration parameters
        need to be set up and initial means may be specified.
        If no initial means are provided, a euclidean Gaussian Mixture model used
        to initialize them.
        
        Parameters
        ----------
        
        model_params:
            a dictionary specifying model parameters, e.g. 'maxiter' and 'tol'
        integration_params:
            parameters specific to the used integration method.
        initial_means: ndarray[K,D]
            initial means for the K components
        
        """
        self.model_params = model_params
        self.integration_params = integration_params
        self.times = {'start' : time.time()}
        
        K = model_params['K']
        N, D = self.data.shape
        self.K = K
        self.N = N
        self.D = D
        
        # initialize the parameters for the LAND
        self.resp = np.zeros((N, K))  # 1 failed, 0 not failed
        self.logmaps = np.zeros((K, N, D))
        self.logmaps[:] = np.nan
        self.consts = np.zeros((K, 1))
        self.sigmas = np.zeros((K, D, D))
        self.manifold_sigmas = np.zeros((K, D, D))
        self.weights = np.zeros((K, 1))  # The mixing components
        self.failed = np.zeros((N, K))  # 1 failed, 0 not failed
        self.V_samples = np.zeros((K, self.model_params['S'], D))
        self.volM_samples = np.zeros((self.model_params['S'], K))
        self.Z_eucl = np.zeros((K, 1))


        # initialize the means
        if initial_means is None:
            self.means = GaussianMixture(n_components=K, covariance_type='full', n_init=10).fit(self.data).means_
        else:
            self.means = np.atleast_2d(initial_means)
        if model_params.get('verbose'):
            print('initial means: %s' % str(self.means))
            
        # evaluate the measure at the data points - we need this if logmaps are evaluated at data points
        # TODO: it's enough to evaluate at the inducing points if the measure is expensive
        self.data_measures = np.apply_along_axis(lambda v: self.manifold.measure(v.reshape(D,1)), 1, self.data).reshape(-1,1)
        # for each component K, this holds information for reusing information during integration
        self.transfer_dict = {}
        for k in range(K):
            self.transfer_dict[k] = {}
        if 'method' not in integration_params.keys():
            print("specify an integration method! choices: ['MC','BQ']")
            sys.exit()
        if integration_params['method'] == 'BQ':
            self.quad_obj = BQuadrature(self)
        elif integration_params['method'] == 'MC':
            self.quad_obj = MCQuadrature(self)
        else:
            print("unsupported integration method: %s" % integration_params['method'])
        
        # choose some inducing points for BQ
        # to reduce the size of the dataset
        n_inducing = integration_params.get('n_inducing') if integration_params.get('n_inducing') is not None else min(30,self.data.shape[0])
        clusters = KMeans(n_clusters=n_inducing).fit(self.data)
        centers = clusters.cluster_centers_
        inducing_points = np.zeros(self.data.shape[0])
        for c in centers:
            mind = float('+inf')
            minp = None
            for i,d in enumerate(self.data):
                dist = np.linalg.norm(c-d)
                if dist < mind:
                    mind = dist
                    minp = i
            inducing_points[minp] = 1

        # array of 0s and 1s (if point belongs to the inducing points)
        self.inducing_points = inducing_points
        if model_params.get('verbose'):
            print("number of inducing points: %s" % (inducing_points.sum()))
            
        print("finished setting up the parameters, initializing components now.")
        
        # Initialize components
        solutions = {}
        for k in range(K):
            for n in range(N):
                if model_params.get('verbose'):
                    print('[Initialize: {}/{}] [Process point: {}/{}]'.format(k+1, K, n+1, N))
                key = 'k_' + str(k) + '_n_' + str(n)
                curve, logmap, curve_length, failed, sol \
                    = geodesics.compute_geodesic(self.solver, self.manifold,
                                                 self.means[k, :].reshape(-1, 1), self.data[n, :].reshape(-1, 1))
                #geodesics.plot_curve(curve, linewidth=1, c='r')
                if failed:
                    self.failed[n, k] = True
                    self.logmaps[k, n, :] = logmap.flatten()  # Note: The straight line geodesic
                    self.resp[n, k] = 1/curve_length  # If points are far lower responsibility
                    solutions[key] = None
                else:
                    self.failed[n, k] = False
                    self.logmaps[k, n, :] = logmap.flatten()
                    self.resp[n, k] = 1/curve_length
                    solutions[key] = curve(np.linspace(0,1,200)) # sol

            #plt.show()
            #plt.close()

        self.resp = self.resp / self.resp.sum(axis=1, keepdims=True)   # Compute the responsibilities
        self.weights = np.sum(self.resp, axis=0).reshape(-1, 1) / N

        #print("the max resps are:")
        #print(self.resp.argmax(axis=1))

        # create the directory for saving the LAND objects
        if integration_params["save_lands"]:
            land_savedir = integration_params["savedir"] + "lands/" 
            try:
                os.mkdir(land_savedir)
            except FileExistsError:
                print("Directory " , land_savedir ,  " already exists")

        # Use the closest points to estimate the Sigmas and the normalization constants
        for k in range(K):
            print(k)
            inds_k = (self.resp.argmax(axis=1) == k)  # The closest points to the k-th center
            self.sigmas[k, :, :] = np.cov(self.logmaps[k, inds_k, :].T)
            self.quad_obj.estimate_norm_constant(k, consider_failed=True)


        print("initialization complete.")
        print("LAND means:")
        print(self.means)
        print("weights:")
        print(self.weights)
        print("")
        print("consts:")
        print(self.consts)

        if integration_params["save_lands"]:
            try:
                plt.figure()
                plt.scatter(self.data[:,0], self.data[:,1], c=self.resp.argmax(axis=1))
                plt.scatter(self.means[:,0],self.means[:,1],marker="D",c='r',s=50)
                plt.savefig(integration_params["savedir"] + "initialization.png", dpi=300)
                plt.close()
            except Exception as e:
                print(e)

        if integration_params["save_lands"]:
            try:
                dill.dump({'mean' : self.means, 'sigmas' : self.sigmas, 'weights' : self.weights, 'consts' : self.consts},\
                    open(integration_params["savedir"] + "initialization.pkl","wb"))
            except Exception as e:
                print(e)

        # dump the LAND object after initialization is finished
        if integration_params["save_lands"]:
            try:
                dill.dump(solutions, open(integration_params["savedir"] + "solutions.pkl","wb"))
                landcopy = self.getDumpableLand()
                dill.dump(landcopy, open(integration_params["savedir"] + "land-initialization.pkl","wb"))
            except Exception as e:
                print(e)

        self.negLogLikelihoods = []
        self.times['initialization'] = time.time() - self.times['start']