//  [python]
def gwas(self):
        """
        Perform batch-wise computation of univariate test with linear mixed model (EMMAX):
        (1) compute spectral decomposition of K=UDU'
        (2) transform data: U'y, U'Z
        (3) estimate delta and compute variance components
        (4) calculate residual sum of squares of null model
        (5) batch-wise:
            (a) linearly transform marker
            (b) calculate effect size, residual sum of squares and standard error
            (c) calculate test statistic
        (6) calculate p-values
        Dataset:
            X: genotype matrix of shape (n,m) or (n,b) if batch-wise
            y: phenotype vector of shape (n)
            K: kinship matrix of shape (n,n)
            fixed: vector/matrix of fixed effects of shape (n) or (n,c)
        """
        start = time.time()
        self.freedom_deg = self.dataset.n_samples - self.dataset.fixed.shape[1]
        # get spectral decomposition
        self.D, self.U = torch.linalg.eigh(self.dataset.K)
        # linearly transform data, i.e. compute U'y and U'Z for fixed effects Z
        self.Uy = self.transform_input(X=self.dataset.y, U=self.U)  # shape (n)
        self.UZ = self.transform_input(X=self.dataset.fixed, U=self.U)  # shape (n) or (n,c)
        # estimate delta and compute variance components
        self.delta = self.estimate_delta(gridlen=100, logdelta_min=-10, logdelta_max=10, reml=True)
        D = self.D + self.delta
        ZD = self._zd(UZ=self.UZ, D=D)
        ZDZ = self._zdz(UZ=self.UZ, ZD=ZD)
        self.v_g, self.v_e = self.compute_var_components(D=D, UZ=self.UZ, ZD=ZD, ZDZ=ZDZ, reml=True)
        # calculate residual sum of squares of null model
        RSS_0 = self.get_rss_h0()  # shape: (1)
        self.freedom_deg -= 1
        # in batches:
        SE = []
        effect_size = []
        test_stat = []
        for batch in range(int(np.ceil(self.dataset.n_snps / self.batch_size))):
            # set bounds for SNP batch
            lower_bound, upper_bound = self._bounds(batch_size=self.batch_size, batch=batch)
            # load and transform batch of SNPs
            US = self._s_matrix(lower_bound=lower_bound, upper_bound=upper_bound)  # shape: (n,b)
            # transform data
            US = self.transform_input(X=US, U=self.U)
            # calculate effect size, residual sum of squares and standard error
            RSS_1, stds, betas = self.get_rss_and_se(D=D, S=US, ZD=ZD, ZDZ=ZDZ)
            SE.append(stds.to(torch.device("cpu")))
            effect_size.append(betas.to(torch.device("cpu")))
            # calculate test statistic
            test_stat.append(self.get_f_score(rss0=RSS_0, rss1=RSS_1).to(torch.device("cpu")))
            # free GPU space
            if self.device.type != "cpu":
                with torch.cuda.device(self.device):
                    del RSS_1
                    del US
                    del stds
                    del betas
                    torch.cuda.empty_cache()
        self.SE = torch.cat(SE, dim=0)  # shape: (m)
        self.effect_size = torch.cat(effect_size, dim=0)  # shape: (m)
        self.test_stat = torch.cat(test_stat, dim=0)  # shape: (m)
        time_test_stats = time.time()
        print("Have test statistics of %d SNPs. Elapsed time: %f" % (self.test_stat.shape[0], time_test_stats - start))
        print("Calculate P-values now")
        # compute p-values
        self.p_value = torch.tensor(list(map(self.get_p_value, self.test_stat)))
        print("Have P-values. Elapsed time: ", time.time() - time_test_stats)
        if self.device.type != "cpu":
            with torch.cuda.device(self.device):
                del D
                del ZD
                del ZDZ
                del self.dataset.K
                torch.cuda.empty_cache()