Train the model using the specified initialization strategy

        Args:
            init_method:    initialization strategy:
                                'default': variance is equally split across the different random effect terms. For mulit-trait models the empirical covariance between traits is used
                                'random': variance component parameters (scales) are sampled from a normal distribution with mean 0 and std 1,
                                None: no initialization is considered. Initial parameters can be specifies by using the single covariance getTraitCovarfun() 
            inference:      inference gp method, by default algebrically efficient inference (i.e., gp2kronSum, gp2KronSumLR, gp3KronSumLR) will be used when possible.
                            For models with high a standard inference scheme (gp_base) will be used. 
            n_times:        number of restarts to converge
            perturb:        if true, the initial point (if random initializaiton is not being used) is perturbed with gaussian noise for each restart (default, False)
            perturbSize:    std of the gassian noise used to perturb the initial point
            verbose:        print if convergence is achieved and how many restarted were needed

def optimize(self, init_method='default', inference=None, n_times=10, perturb=False, pertSize=1e-3, verbose=None):
        """
        Train the model using the specified initialization strategy

        Args:
            init_method:    initialization strategy:
                                'default': variance is equally split across the different random effect terms. For mulit-trait models the empirical covariance between traits is used
                                'random': variance component parameters (scales) are sampled from a normal distribution with mean 0 and std 1,
                                None: no initialization is considered. Initial parameters can be specifies by using the single covariance getTraitCovarfun() 
            inference:      inference gp method, by default algebrically efficient inference (i.e., gp2kronSum, gp2KronSumLR, gp3KronSumLR) will be used when possible.
                            For models with high a standard inference scheme (gp_base) will be used. 
            n_times:        number of restarts to converge
            perturb:        if true, the initial point (if random initializaiton is not being used) is perturbed with gaussian noise for each restart (default, False)
            perturbSize:    std of the gassian noise used to perturb the initial point
            verbose:        print if convergence is achieved and how many restarted were needed
        """
        #verbose = dlimix.getVerbose(verbose)
        assert init_method in ['default', 'random', None], 'VarianceDecomposition: specified init_method not valid'
        if init_method=='default':  self._init_params_default()

        # determine if repeats should be considered 
        if init_method is not 'random' and not perturb:     n_times = 1

        # determine inference
        if inference is None:   inference = self._det_inference()
        else:                   self._check_inference(inference)
        self._inference = inference

        if self.gp is None:        self._initGP()

        params0 = self.gp.getParams()
        for i in range(n_times):
            if init_method=='random':   
                params = {'covar': sp.randn(params0['covar'].shape[0])}
                self.gp.setParams(params)
            elif perturb:
                params = {'covar': params0['covar'] + pertSize * sp.randn(params0['covar'].shape[0])}
                self.gp.setParams(params)
            conv, info = self.gp.optimize()
            if conv:    break

        if verbose:
            if conv==False:
                print('No local minimum found for the tested initialization points')
            else:
                print(('Local minimum found at iteration %d' % i))

        return conv