From 15b1d9c81534601ee43bb04f9a6f8b3bf387067b Mon Sep 17 00:00:00 2001 From: Starlitnightly Date: Wed, 30 Oct 2024 00:06:00 +0800 Subject: [PATCH] Updated the MultiVelo calculation function (#706) --- dynamo/__init__.py | 1 + dynamo/multi.py | 1 + dynamo/multivelo/ATACseqTools.py | 406 ++ dynamo/multivelo/ChromatinVelocity.py | 213 + dynamo/multivelo/MultiConfiguration.py | 97 + dynamo/multivelo/MultiIO.py | 415 ++ dynamo/multivelo/MultiPreprocessor.py | 718 ++ dynamo/multivelo/MultiQC.py | 141 + dynamo/multivelo/MultiVelo.py | 115 + dynamo/multivelo/__init__.py | 10 + dynamo/multivelo/dynamical_chrom_func.py | 6386 ++++++++++++++++++ dynamo/multivelo/globals.py | 58 + dynamo/multivelo/neural_nets/dir0.pt | Bin 0 -> 117788 bytes dynamo/multivelo/neural_nets/dir1.pt | Bin 0 -> 25884 bytes dynamo/multivelo/neural_nets/dir2_m1.pt | Bin 0 -> 238912 bytes dynamo/multivelo/neural_nets/dir2_m2.pt | Bin 0 -> 114816 bytes dynamo/multivelo/old_MultiVelocity.py | 1401 ++++ dynamo/multivelo/old_MultiomicVectorField.py | 445 ++ dynamo/multivelo/pyWNN.py | 270 + dynamo/multivelo/settings.py | 27 + dynamo/multivelo/sparse_matrix_utils.py | 94 + dynamo/tools/utils.py | 1 + requirements.txt | 1 + setup.cfg | 3 + 24 files changed, 10803 insertions(+) create mode 100644 dynamo/multi.py create mode 100644 dynamo/multivelo/ATACseqTools.py create mode 100644 dynamo/multivelo/ChromatinVelocity.py create mode 100644 dynamo/multivelo/MultiConfiguration.py create mode 100644 dynamo/multivelo/MultiIO.py create mode 100644 dynamo/multivelo/MultiPreprocessor.py create mode 100644 dynamo/multivelo/MultiQC.py create mode 100644 dynamo/multivelo/MultiVelo.py create mode 100644 dynamo/multivelo/__init__.py create mode 100644 dynamo/multivelo/dynamical_chrom_func.py create mode 100644 dynamo/multivelo/globals.py create mode 100644 dynamo/multivelo/neural_nets/dir0.pt create mode 100644 dynamo/multivelo/neural_nets/dir1.pt create mode 100644 dynamo/multivelo/neural_nets/dir2_m1.pt create mode 100644 dynamo/multivelo/neural_nets/dir2_m2.pt create mode 100644 dynamo/multivelo/old_MultiVelocity.py create mode 100644 dynamo/multivelo/old_MultiomicVectorField.py create mode 100644 dynamo/multivelo/pyWNN.py create mode 100644 dynamo/multivelo/settings.py create mode 100644 dynamo/multivelo/sparse_matrix_utils.py diff --git a/dynamo/__init__.py b/dynamo/__init__.py index 870f8c30..1a4a5fcf 100755 --- a/dynamo/__init__.py +++ b/dynamo/__init__.py @@ -22,6 +22,7 @@ from . import sample_data from . import configuration from . import ext +from . import multi from .data_io import * from .dynamo_logger import ( diff --git a/dynamo/multi.py b/dynamo/multi.py new file mode 100644 index 00000000..9b876f67 --- /dev/null +++ b/dynamo/multi.py @@ -0,0 +1 @@ +from .multivelo import * \ No newline at end of file diff --git a/dynamo/multivelo/ATACseqTools.py b/dynamo/multivelo/ATACseqTools.py new file mode 100644 index 00000000..cf30145a --- /dev/null +++ b/dynamo/multivelo/ATACseqTools.py @@ -0,0 +1,406 @@ +import anndata as ad +from anndata import AnnData + +from concurrent.futures import as_completed, ThreadPoolExecutor + +from mudata import MuData +import numpy as np +from os import PathLike +import pandas as pd + +import scanpy as sc +from scipy.sparse import coo_matrix, csr_matrix, diags, hstack +from tqdm import tqdm +from typing import ( + Literal, + Union +) + +# Imports from dynamo +from ..dynamo_logger import ( + LoggerManager, + main_info, +) + + +# Imports from MultiDynamo +from .MultiConfiguration import MDKM + + +def extend_gene_coordinates( + bedtool, + upstream: int = 2000, + downstream: int = 0 +): + from pybedtools import BedTool + extended_genes = [] + for feature in bedtool: + if feature[2] == 'gene': + start = max(0, int(feature.start) - upstream) + end = int(feature.end) + downstream + extended_genes.append((feature.chrom, start, end, feature.name)) + return BedTool(extended_genes) + + +def annotate_integrated_mdata(mdata: MuData, + celltypist_model: str = 'Immune_All_Low.pkl' + ) -> MuData: + import celltypist + from celltypist import models + # Extract the RNA data + rna_adata = mdata.mod['rna'].copy() + + # ... revert to counts + rna_adata.X = rna_adata.layers['counts'].copy() + + # ... normalize counts so total number per cell is 10,000 (required by celltypist) + sc.pp.normalize_total(rna_adata, + target_sum=1e4) + + # ... pseudo-log transform (x -> log(1 + x)) for better dynamical range (and required by celltypist) + sc.pp.log1p(rna_adata) + + # ... rerun PCA - CellTypist can need larger number than we already computed + sc.pp.pca(rna_adata, n_comps=50) + + # ... recompute the neighborhood graph for majority voting + sc.pp.neighbors(rna_adata, + n_neighbors=50, + n_pcs=50) + + # Download celltypist models for annotation + models.download_models(force_update=True) + + # Select the low resolution immun cell model + model = models.Model.load(model=celltypist_model) + + # Compute cell type labels + predictions = celltypist.annotate(rna_adata, + model=celltypist_model, + majority_voting=True) + + # Transfer the predictions back to the RNA AnnData object + rna_adata = predictions.to_adata() + + # Create dictionary from cell indices to cell types + cellindex_to_celltype_dict = rna_adata.obs['majority_voting'].to_dict() + + # Apply the index map to both RNA and ATAC AnnData objects + atac_adata, rna_adata = mdata.mod['atac'].copy(), mdata.mod['rna'].copy() + atac_adata.obs['cell_type'] = atac_adata.obs.index.map( + lambda cell_idx: cellindex_to_celltype_dict.get(cell_idx, 'Undefined')) + rna_adata.obs['cell_type'] = rna_adata.obs.index.map( + lambda cell_idx: cellindex_to_celltype_dict.get(cell_idx, 'Undefined')) + + return MuData({'atac': atac_adata.copy(), 'rna': rna_adata.copy()}) + + +def gene_activity( + atac_adata: AnnData, + gtf_path: PathLike, + upstream: int = 2000, + downstream: int = 0 +) -> Union[AnnData, None]: + from pybedtools import BedTool + # Drop UCSC convention for naming of chromosomes - This assumes we are using ENSEMBL-format of GTF + atac_adata.var.index = [c.lstrip('chr') for c in atac_adata.var.index] + + # Read GTF to annotate genes + main_info('reading GTF', indent_level=3) + gene_annotations = BedTool(gtf_path) + + # Extend gene coordinates + main_info('extending genes to estimate regulatory regions', indent_level=3) + peak_extension_logger = LoggerManager.gen_logger('extend_genes') + peak_extension_logger.log_time() + + extended_genes = extend_gene_coordinates(gene_annotations, + upstream=upstream, + downstream=downstream) + + peak_extension_logger.finish_progress(progress_name='extend_genes', indent_level=3) + + # Extract ATAC-seq peak coordinates + chrom_list = atac_adata.var_names.str.split(':').str[0] # .astype(int) + start_list = atac_adata.var_names.str.split(':').str[1].str.split('-').str[0] # .astype(int).astype(int) + end_list = atac_adata.var_names.str.split(':').str[1].str.split('-').str[1] # .astype(int).astype(int) + + # Convert ATAC-seq peak data to BedTool format + atac_peaks = BedTool.from_dataframe(pd.DataFrame({ + 'chrom': chrom_list, + 'start': start_list, + 'end': end_list + })) + + # Find overlaps between peaks and extended genes + main_info('overlapping peaks and extended genes', indent_level=3) + linked_peaks = atac_peaks.intersect(extended_genes, wa=True, wb=True) + + # Create a DataFrame from the linked peaks + linked_peaks_df = linked_peaks.to_dataframe( + names=['chrom', 'peak_start', 'peak_end', 'chrom_gene', 'gene_start', 'gene_end', 'gene_name']) + + # Create a dictionary to map peak indices to gene names + main_info('building dictionaries', indent_level=3) + peak_to_gene = linked_peaks_df.set_index(['chrom', 'peak_start', 'peak_end'])['gene_name'].to_dict() + peak_to_gene = {f'{chrom}:{start}-{end}': gene_name for (chrom, start, end), gene_name in peak_to_gene.items()} + + # Get the list of peaks from the ATAC-seq data + peaks = atac_adata.var.index + gene_names = np.array([peak_to_gene.get(peak, '') for peak in peaks]) + + # Get the unique genes + unique_genes = np.unique(gene_names) + + # Initialize a sparse matrix for gene activity scores + n_cells, n_genes = atac_adata.n_obs, len(unique_genes) + + # Create a mapping from gene names to column indices in the sparse matrix + gene_to_idx = {gene: idx for idx, gene in enumerate(unique_genes)} + + def process_peak(i): + gene = gene_names[i] + return gene_to_idx.get(gene, -1), atac_adata[:, i].X + + # Fill the sparse matrix with aggregated counts in parallel + results = [] + with ThreadPoolExecutor() as executor: + futures = [executor.submit(process_peak, i) for i in range(len(peaks))] + with tqdm(total=len(peaks), desc="Processing peaks") as pbar: + for future in as_completed(futures): + result = future.result() + if result is not None: + results.append(result) + pbar.update(1) + + # Aggregate results in batches to minimize overhead + main_info('aggregating results', indent_level=3) + aggregation_logger = LoggerManager.gen_logger('aggregating_results') + aggregation_logger.log_time() + + data = [] + rows = [] + cols = [] + + # Loop through the results to gather data for COO matrix + for col_idx, sparse_col_vector in results: + # Extract row indices and data from the sparse column vector + coo = sparse_col_vector.tocoo() + data.extend(coo.data) + rows.extend(coo.row) + cols.extend([col_idx] * len(coo.row)) + + # Create a COO matrix from collected data + coo_matrix_all = coo_matrix((data, (rows, cols)), shape=(n_cells, n_genes)) + + # Convert COO matrix to CSR format + gene_activity_matrix = coo_matrix_all.tocsr() + + aggregation_logger.finish_progress(progress_name='aggregating_results', indent_level=3) + + # Add the sparse gene activity matrix as a new .obsm + atac_adata.obsm[MDKM.ATAC_GENE_ACTIVITY_KEY] = gene_activity_matrix + atac_adata.uns[MDKM.ATAC_GENE_ACTIVITY_GENES_KEY] = pd.Index(unique_genes) + + return atac_adata + + +def integrate(mdata: MuData, + integration_method: Literal['moscot', 'multivi'] = 'multivi', + alpha: float = 0.5, + entropic_regularization: float = 0.01, + gtf_path: Union[PathLike, str] = None, + max_epochs: int = 500, + lr: float = 0.0001, + ) -> MuData: + # Split into scATAC-seq and scRNA-seq AnnData objects + atac_adata, rna_adata = mdata.mod['atac'].copy(), mdata.mod['rna'].copy() + atac_adata.obs['modality'], rna_adata.obs['modality'] = 'atac', 'rna' + + if atac_adata.uns[MDKM.MATCHED_ATAC_RNA_DATA_KEY]: + main_info('Integration: matched multiome, so just filtering cells') + + # Restrict to cells common to both AnnData objects + shared_cells = pd.Index(np.intersect1d(rna_adata.obs_names, atac_adata.obs_names)) + atac_adata_filtered = atac_adata[shared_cells, :].copy() + rna_adata_filtered = rna_adata[shared_cells, :].copy() + + return MuData({'atac': atac_adata_filtered, 'rna': rna_adata_filtered}) + elif integration_method == 'moscot': + return integrate_via_moscot(mdata=mdata, + alpha=alpha, + entropic_regularization=entropic_regularization) + elif integration_method == 'multivi': + return integrate_via_multivi(mdata=mdata, + gtf_path=gtf_path, + lr=lr, + max_epochs=max_epochs) + else: + raise ValueError(f'Unknown integration method {integration_method} requested.') + + +def integrate_via_moscot(mdata: MuData, + alpha: float = 0.7, + entropic_regularization: float = 0.01, + gtf_path: Union[PathLike, str] = None, + ) -> MuData: + pass + + +def integrate_via_multivi(mdata: MuData, + gtf_path: Union[PathLike, str] = None, + lr: float = 0.0001, + max_epochs: int = 500, + ) -> MuData: + import scvi + main_info('Integration via MULTIVI ...') + integration_logger = LoggerManager.gen_logger('integration_via_multivi') + integration_logger.log_time() + + # Split into scATAC-seq and scRNA-seq AnnData objects + atac_adata, rna_adata = mdata.mod['atac'].copy(), mdata.mod['rna'].copy() + atac_adata.obs['modality'], rna_adata.obs['modality'] = 'atac', 'rna' + + # Check whether cell indices need to be prepended by 'atac' and 'rna' + if ':' not in atac_adata.obs_names[0]: + atac_adata.obs_names = atac_adata.obs_names.map(lambda x: f'atac:{x}') + num_atac_cells, num_atac_peaks = atac_adata.n_obs, atac_adata.n_vars + + if ':' not in rna_adata.obs_names[0]: + rna_adata.obs_names = rna_adata.obs_names.map(lambda x: f'rna:{x}') + num_rna_cells = rna_adata.n_obs + + # Check whether gene activity was pre-computed + if MDKM.ATAC_GENE_ACTIVITY_KEY not in atac_adata.obsm.keys(): + main_info('Computing gene activities', indent_level=2) + atac_adata = gene_activity(atac_adata=atac_adata, + gtf_path=gtf_path) + gene_activity_matrix = atac_adata.obsm[MDKM.ATAC_GENE_ACTIVITY_KEY] + + # Restrict to gene names common to gene activity matrix from atac-seq data and + # counts matrix from rna-seq data + gene_names_atac = atac_adata.uns[MDKM.ATAC_GENE_ACTIVITY_GENES_KEY] + gene_names_rna = rna_adata.var_names + common_genes = gene_names_rna.intersection(gene_names_atac) + num_genes = len(common_genes) + + # Filter gene activity and scATAC-seq data into a single AnnData object, with a + # batch label indicating the origin + main_info('Preparing ATAC-seq data for MULTIVI', indent_level=2) + gene_activity_filtered = gene_activity_matrix[:, [gene_names_atac.get_loc(gene) for gene in common_genes]] + + # Assemble multi-ome for the ATAC-seq data + # ... X + atac_multiome_X = hstack([gene_activity_filtered, atac_adata.X]) + + # ... obs + atac_multiome_obs = atac_adata.obs[['modality']].copy() + + # ... var + multiome_var = pd.concat((rna_adata.var.loc[common_genes].copy(), atac_adata.var.copy()), axis=1) + + atac_multiome = AnnData(X=csr_matrix(atac_multiome_X), + obs=atac_multiome_obs, + var=multiome_var) + + # Assemble multi-ome for RNA-seq data + main_info('Preparing RNA-seq data for MULTIVI', indent_level=2) + rna_adata_filtered = rna_adata[:, common_genes].copy() + + # ... X + rna_multiome_X = hstack([rna_adata_filtered.X.copy(), csr_matrix((num_rna_cells, num_atac_peaks))]) + + # ... obs + rna_multiome_obs = rna_adata_filtered.obs[['modality']].copy() + + # ... var - NTD + + rna_multiome = AnnData(X=csr_matrix(rna_multiome_X), + obs=rna_multiome_obs, + var=multiome_var) + + # Concatenate the data + combined_adata = ad.concat([atac_multiome, rna_multiome], axis=0) + + # Setup AnnData object for scvi-tools + main_info('Setting up combined data for MULTIVI', indent_level=2) + scvi.model.MULTIVI.setup_anndata(combined_adata, batch_key='modality') + + # Instantiate the SCVI model + main_info('Instantiating MULTIVI model', indent_level=2) + multivi_model = scvi.model.MULTIVI(adata=combined_adata, n_genes=num_genes, n_regions=num_atac_peaks) + + # Train the model + main_info('Training MULTIVI model', indent_level=2) + multivi_model.train(max_epochs=max_epochs, lr=lr) + + # Extract the latent representation + combined_adata.obsm['latent'] = multivi_model.get_latent_representation() + + # Impute counts from latent space + # ... X + main_info('Imputing RNA expression', indent_level=2) + imputed_rna_X = multivi_model.get_normalized_expression() + + # ... obs + multiome_obs = pd.concat((atac_multiome_obs, rna_multiome_obs)) + + # ... var + rna_multiome_var = rna_adata.var.loc[common_genes].copy() + + imputed_rna_adata = AnnData(X=imputed_rna_X, + obs=multiome_obs, + var=rna_multiome_var, + ) + + # ... X + main_info('Imputing accessibility', indent_level=2) + imputed_atac_X = multivi_model.get_accessibility_estimates() + + # ... obs - NTD + + # ... var + atac_multiome_var = atac_adata.var.copy() + + imputed_atac_adata = AnnData(X=imputed_atac_X, + obs=multiome_obs, + var=atac_multiome_var, + ) + + # Knit together into one harmonized MuData object + harmonized_mdata = MuData({'atac': imputed_atac_adata, 'rna': imputed_rna_adata}) + + integration_logger.finish_progress(progress_name='integration_via_multivi', indent_level=3) + + return harmonized_mdata + + +def tfidf_normalize( + atac_adata: AnnData, + log_tf: bool = True, + log_idf: bool = True, + log_tfidf: bool = False, + mv_algorithm: bool = True, + scale_factor: float = 1e4, +) -> None: + import muon as mu + # This computes the term frequency / inverse domain frequency normalization. + if mv_algorithm: + # MultiVelo's method + npeaks = atac_adata.X.sum(1) + npeaks_inv = csr_matrix(1.0 / npeaks) + tf = atac_adata.X.multiply(npeaks_inv) + idf = diags(np.ravel(atac_adata.X.shape[0] / atac_adata.X.sum(0))).log1p() + tf_idf = tf.dot(idf) * scale_factor + atac_adata.layers[MDKM.ATAC_TFIDF_LAYER] = np.log1p(tf_idf) + else: + atac_adata = mu.atac.pp.tfidf(data=atac_adata, + log_tf=log_tf, + log_idf=log_idf, + log_tfidf=log_tfidf, + scale_factor=scale_factor, + from_layer='counts', + to_layer=MDKM.ATAC_TFIDF_LAYER, + copy=True) + + return atac_adata diff --git a/dynamo/multivelo/ChromatinVelocity.py b/dynamo/multivelo/ChromatinVelocity.py new file mode 100644 index 00000000..6501f365 --- /dev/null +++ b/dynamo/multivelo/ChromatinVelocity.py @@ -0,0 +1,213 @@ +import numpy as np +from scipy.sparse import issparse +from typing import Literal + +# Import from dynamo +from ..dynamo_logger import ( + main_exception, +) + + +# ChromatinVelocity class - patterned after MultiVelo, but retains accessibility at individual CRE +class ChromatinVelocity: + def __init__(self, + c, + u, + s, + ss, + us, + uu, + fit_args=None, + gene=None, + r2_adjusted=False): + self.gene = gene + self.outlier = np.clip(fit_args['outlier'], a_min=80, a_max=100) + self.r2_adjusted = r2_adjusted + self.total_n = len(u) + + # Convert all sparse vectors to dense ones + c = c.A if issparse(c) else c + s = s.A if issparse(s) else s + u = u.A if issparse(u) else u + ss = ss.A if ((ss is not None) and issparse(ss)) else ss + us = us.A if ((us is not None) and issparse(us)) else us + uu = uu.A if ((uu is not None) and issparse(uu)) else uu + + # In distinction to MultiVelo c will be (total_n, n_peak) array + # Sweep the minimum value in each column from the array + self.offset_c = np.min(c, axis=0) + self.c_all = c - self.offset_c + + # The other moments are (total_n, ) arrays + self.s_all, self.u_all = np.ravel(np.array(s, dtype=np.float64)), np.ravel(np.array(u, dtype=np.float64)) + self.offset_s, self.offset_u = np.min(self.s_all), np.min(self.u_all) + self.s_all -= self.offset_s + self.u_all -= self.offset_u + + # For 'stochastic' method also need second moments + if ss is not None: + self.ss_all = np.ravel(np.array(ss, dtype=np.float64)) + if us is not None: + self.us_all = np.ravel(np.array(us, dtype=np.float64)) + if uu is not None: + self.uu_all = np.ravel(np.array(uu, dtype=np.float64)) + + # Ensure at least one element in each cell is positive + any_c_positive = np.any(self.c_all > 0, axis=1) + self.non_zero = np.ravel(any_c_positive) | np.ravel(self.u_all > 0) | np.ravel(self.s_all > 0) + + # remove outliers + # ... for chromatin, we'll be more stringent - if *any* peak count for a cell + # is an outlier, we'll remove that cell + self.non_outlier = np.all(self.c_all <= np.percentile(self.c_all, self.outlier, axis=0), axis=1) + self.non_outlier &= np.ravel(self.u_all <= np.percentile(self.u_all, self.outlier)) + self.non_outlier &= np.ravel(self.s_all <= np.percentile(self.s_all, self.outlier)) + self.c = self.c_all[self.non_zero & self.non_outlier] + self.u = self.u_all[self.non_zero & self.non_outlier] + self.s = self.s_all[self.non_zero & self.non_outlier] + self.ss = (None if ss is None + else self.ss_all[self.non_zero & self.non_outlier]) + self.us = (None if us is None + else self.us_all[self.non_zero & self.non_outlier]) + self.uu = (None if uu is None + else self.uu_all[self.non_zero & self.non_outlier]) + self.low_quality = len(self.u) < 10 + + # main_info(f'{len(self.u)} cells passed filter and will be used to fit regressions.') + + # 4 rate parameters + self.alpha_c = 0.1 + self.alpha = 0.0 + self.beta = 0.0 + self.gamma_det = 0.0 + self.gamma_stoch = 0.0 + + # other parameters or results + self.loss_det = np.inf + self.loss_stoch = np.inf + self.r2_det = 0 + self.r2_stoch = 0 + self.residual_det = None + self.residual_stoch = None + self.residual2_stoch = None + + self.steady_state_func = None + + # Select the cells for regression + w_sub_for_c = np.any(self.c >= 0.1 * np.max(self.c, axis=0), axis=1) + w_sub = w_sub_for_c & (self.u >= 0.1 * np.max(self.u)) & (self.s >= 0.1 * np.max(self.s)) + c_sub = self.c[w_sub] + w_sub_for_c = np.any(self.c >= np.mean(c_sub, axis=0) + np.std(c_sub, axis=0)) + w_sub = w_sub_for_c & (self.u >= 0.1 * np.max(self.u)) & (self.s >= 0.1 * np.max(self.s)) + self.w_sub = w_sub + if np.sum(self.w_sub) < 10: + self.low_quality = True + + # This method originated from MultiVelo - Corrected R^2 + def compute_deterministic(self): + # Steady state slope - no different than usual transcriptomic version + u_high = self.u[self.w_sub] + s_high = self.s[self.w_sub] + wu_high = u_high >= np.percentile(u_high, 95) + ws_high = s_high >= np.percentile(s_high, 95) + ss_u = u_high[wu_high | ws_high] + ss_s = s_high[wu_high | ws_high] + + gamma_det = np.dot(ss_u, ss_s) / np.dot(ss_s, ss_s) + self.steady_state_func = lambda x: gamma_det * x + residual_det = self.u_all - self.steady_state_func(self.s_all) + + loss_det = np.dot(residual_det, residual_det) / len(self.u_all) + + if self.r2_adjusted: + gamma_det = np.dot(self.u, self.s) / np.dot(self.s, self.s) + residual_det = self.u_all - gamma_det * self.s_all + + total_det = self.u_all - np.mean(self.u_all) + # total_det = self.u_all # Since fitting only slope with zero intercept, should not include mean + + self.gamma_det = gamma_det + self.loss_det = loss_det + self.residual_det = residual_det + + self.r2_det = 1 - np.dot(residual_det, residual_det) / np.dot(total_det, total_det) + + + # This method originated from MultiVelo + def compute_stochastic(self): + self.compute_deterministic() + + var_ss = 2 * self.ss - self.s + cov_us = 2 * self.us + self.u + s_all_ = 2 * self.s_all ** 2 - (2 * self.ss_all - self.s_all) + u_all_ = (2 * self.us_all + self.u_all) - 2 * self.u_all * self.s_all + gamma2 = np.dot(cov_us, var_ss) / np.dot(var_ss, var_ss) + residual2 = cov_us - gamma2 * var_ss + std_first = np.std(self.residual_det) + std_second = np.std(residual2) + + # chromatin adjusted steady-state slope + u_high = self.u[self.w_sub] + s_high = self.s[self.w_sub] + wu_high = u_high >= np.percentile(u_high, 95) + ws_high = s_high >= np.percentile(s_high, 95) + ss_u = u_high * (wu_high | ws_high) + ss_s = s_high * (wu_high | ws_high) + a = np.hstack((ss_s / std_first, var_ss[self.w_sub] / std_second)) + b = np.hstack((ss_u / std_first, cov_us[self.w_sub] / std_second)) + + gamma_stoch = np.dot(b, a) / np.dot(a, a) + self.steady_state_func = lambda x: gamma_stoch * x + self.residual_stoch = self.u_all - self.steady_state_func(self.s_all) + self.residual2_stoch = u_all_ - self.steady_state_func(s_all_) + loss_stoch = np.dot(self.residual_stoch, self.residual_stoch) / len(self.u_all) + + self.gamma_stoch = gamma_stoch + self.loss_stoch = loss_stoch + self.r2_stoch = 1 - np.dot(self.residual_stoch, self.residual_stoch) / np.dot(self.u_all, self.u_all) + + def get_gamma(self, + mode: Literal['deterministic', 'stochastic'] = 'stochastic'): + if mode == 'deterministic': + return self.gamma_det + elif mode == 'stochastic': + return self.gamma_stoch + else: + main_exception(f"Unknown mode {mode} - must be one of 'deterministic' or 'stochastic'") + + def get_loss(self, + mode: Literal['deterministic', 'stochastic'] = 'stochastic'): + if mode == 'deterministic': + return self.loss_det + elif mode == 'stochastic': + return self.loss_stoch + else: + main_exception(f"Unknown mode {mode} - must be one of 'deterministic' or 'stochastic'") + + def get_r2(self, + mode: Literal['deterministic', 'stochastic'] = 'stochastic'): + if mode == 'deterministic': + return self.r2_det + elif mode == 'stochastic': + return self.r2_stoch + else: + main_exception(f"Unknown mode {mode} - must be one of 'deterministic' or 'stochastic'") + + def get_variance_velocity(self, + mode: Literal['deterministic', 'stochastic'] = 'stochastic'): + if mode == 'stochastic': + return self.residual2_stoch + else: + main_exception("Should not call get_variance_velocity for mode other than 'stochastic'") + + def get_velocity(self, + mode: Literal['deterministic', 'stochastic'] = 'stochastic'): + vel = None # Make the lint checker happy + if mode == 'deterministic': + vel = self.residual_det + elif mode == 'stochastic': + vel = self.residual_stoch + else: + main_exception(f"Unknown mode {mode} - must be one of 'deterministic' or 'stochastic'") + + return vel diff --git a/dynamo/multivelo/MultiConfiguration.py b/dynamo/multivelo/MultiConfiguration.py new file mode 100644 index 00000000..41b03670 --- /dev/null +++ b/dynamo/multivelo/MultiConfiguration.py @@ -0,0 +1,97 @@ +from ..configuration import DynamoAdataKeyManager + +class MultiDynamoMdataKeyManager(DynamoAdataKeyManager): + # A class to manage the keys used in MuData object used for MultiDynamo + # Universal keys - independent of modality + INFERRED_BATCH_KEY = 'inferred_batch' + + # .mod + # ... 'atac' + # ... ... layers + ATAC_COUNTS_LAYER = 'counts' + ATAC_FIRST_MOMENT_CHROM_LAYER = 'M_c' + ATAC_TFIDF_LAYER = 'X_tfidf' # Also X? + ATAC_CHROMATIN_VELOCITY_LAYER = 'lifted_velo_c' + + # ... ... .obs + + # ... ... .obsm + ATAC_GENE_ACTIVITY_KEY = 'gene_activity' # Computed gene activity matrix - for unmatched data only + ATAC_OBSM_LSI_KEY = 'X_lsi' + ATAC_OBSM_PC_KEY = 'X_pca' + + # ... ... .obsp + + # ... ... .uns + ATAC_GENE_ACTIVITY_GENES_KEY = 'gene_activity_genes' # Genes for gene activity matrix + MATCHED_ATAC_RNA_DATA_KEY = 'matched_atac_rna_data' # Indicates whether ATAC- and RNA-seq data are matched + + # ... ... .var (atac:*) + + # ... ... .varm + ATAC_VARM_LSI_KEY = 'LSI' + + # ... 'cite' + # ... ... layers + + # ... ... .obs + + # ... ... .obsm + + # ... ... .obsp + + # ... ... .uns + MATCHED_CITE_RNA_DATA_KEY = 'matched_cite_rna_data' # Indicates whether CITE- and RNA-seq data are matched + + # ... ... .var (cite:*) + + # ... ... .varm + + # ... 'hic' + # ... ... layers + + # ... ... .obs + + # ... ... .obsm + + # ... ... .obsp + + # ... ... .uns + MATCHED_HIC_RNA_DATA_KEY = 'matched_hic_rna_data' # Indicates whether HiC- and RNA-seq data are matched + + # ... ... .var (hic:*) + + # ... ... .varm + + # ... 'rna' + # Most things are handled by DynamoAdataKeyManager; these are in addition to thos defined in dynamo + # ... ... layers + RNA_COUNTS_LAYER = 'counts' + RNA_COUNTS_LAYER_FROM_LOOM = 'matrix' + RNA_FIRST_MOMENT_CHROM_LAYER = 'M_c' + RNA_FIRST_MOMENT_SPLICED_LAYER = 'M_s' + RNA_FIRST_MOMENT_UNSPLICED_LAYER = 'M_u' + RNA_SECOND_MOMENT_SS_LAYER = 'M_ss' + RNA_SECOND_MOMENT_US_LAYER = 'M_us' + RNA_SECOND_MOMENT_UU_LAYER = 'M_uu' + RNA_SPLICED_LAYER = 'spliced' + RNA_SPLICED_VELOCITY_LAYER = 'velocity_S' + RNA_UNSPLICED_LAYER = 'unspliced' + + # ... ... .obs + + # ... ... .obsm + RNA_OBSM_PC_KEY = 'X_pca' + + # ... ... .obsp + + # ... ... .uns + + # ... ... .var (rna:*) + + # ... ... .varm + + def bogus_function(self): + pass + +MDKM = MultiDynamoMdataKeyManager diff --git a/dynamo/multivelo/MultiIO.py b/dynamo/multivelo/MultiIO.py new file mode 100644 index 00000000..2fd97171 --- /dev/null +++ b/dynamo/multivelo/MultiIO.py @@ -0,0 +1,415 @@ +from anndata import ( + AnnData, + read_loom +) +from .MultiConfiguration import MDKM + +from mudata import MuData + + +import numpy as np +import os +from os import PathLike +import pandas as pd +from pathlib import Path +import re +from typing import ( + Dict, + Literal, + Union +) + +# Imports from dynamo +from ..dynamo_logger import ( + LoggerManager, + main_exception, + main_info, +) + +# Imports from MultiDynamo +from .old_MultiVelocity import MultiVelocity +from .MultiPreprocessor import aggregate_peaks_10x + + +def add_splicing_data( + mdata: MuData, + multiome_base_path: PathLike | str, + rna_splicing_loom: PathLike | str = 'multiome.loom', + cellranger_path_structure: bool = True +) -> MuData: + # Extract accessibility and transcriptomic counts + atac_adata, rna_adata = mdata.mod['atac'], mdata.mod['rna'] + + # Read in spicing data + splicing_data_path = os.path.join(multiome_base_path, + 'velocyto' if cellranger_path_structure else '', + rna_splicing_loom) + ldata = read_loom(filename=Path(splicing_data_path)) + + # Merge splicing data with transcriptomic data + rna_adata.var_names_make_unique() + ldata.var_names_make_unique() + + common_obs = pd.unique(rna_adata.obs_names.intersection(ldata.obs_names)) + common_vars = pd.unique(rna_adata.var_names.intersection(ldata.var_names)) + + if len(common_obs) == 0: + # Try cleaning cell indices, if intersection of indices is vacuous + clean_obs_names(rna_adata) + clean_obs_names(ldata) + common_obs = rna_adata.obs_names.intersection(ldata.obs_names) + + # Restrict to common cell indices and genes + rna_adata = rna_adata[common_obs, common_vars].copy() + ldata = ldata[common_obs, common_vars].copy() + + # Transfer layers from ldata + for key, data in ldata.layers.items(): + if key not in rna_adata.layers: + rna_adata.layers[key] = data.copy() + + # Copy over the loom counts to a counts layer + rna_adata.layers[MDKM.RNA_COUNTS_LAYER] = rna_adata.layers[MDKM.RNA_COUNTS_LAYER_FROM_LOOM].copy() + + mdata = MuData({'atac': atac_adata, 'rna': rna_adata}) + + return mdata + + +# These are convenience functions pattern after (but not identical to) ones in scvelo +def clean_obs_names( + adata: AnnData, + alphabet: Literal['[AGTCBDHKMNRSVWY]'] = '[AGTCBDHKMNRSVWY]', + batch_key: str = MDKM.INFERRED_BATCH_KEY, + id_length: int = 16 +) -> AnnData: + if adata.obs_names.map(len).unique().size == 1: + # Here if all cell indices have the same numbers of characters + # ... find (first) instance of id_length valid nucleotides in the first cell index + start, end = re.search(alphabet * id_length, adata.obs_names[0]).span() + + # ... truncate the cell indices to the valid nucleotides + new_obs_names = [obs_name[start:end] for obs_name in adata.obs_names] + + # ... any characters prior to the characters that define the new cell index + # might specify the batch, so save it as tuple with the new cell index + prefixes = [ + obs_name.replace(new_obs_name, "") + for obs_name, new_obs_name in zip(adata.obs_names, new_obs_names) + ] + else: + # Here if cell indices have different lengths + prefixes, new_obs_names = [], [] + for obs_name in adata.obs_names: + # ... loop over the cell indices individually; find the (first) instance + # of id_length valid nucleotides in each cell index + start, end = re.search(alphabet * id_length, adata.obs_names[0]).span() + + # ... truncate the cell indices to the valid nucleotides + new_obs_names.append(obs_name[start:end]) + + # ... any characters prior to the characters that define the new cell index + # might specify the batch, so save it as tuple with the new cell index + prefixes.append(obs_name.replace(obs_name[start:end], "")) + + adata.obs_names = new_obs_names + adata.obs_names_make_unique() + + if len(prefixes[0]) > 0 and len(np.unique(prefixes)) > 1: + # If non-trival list of prefices (non-trivial length and more than one different), + # then add MDKM.INFERRED_BATCH_KEY to cell metadata + adata.obs[batch_key] = ( + pd.Categorical(prefixes) + if len(np.unique(prefixes)) < adata.n_obs + else prefixes + ) + + return adata + + +def homogenize_mudata_obs_names( + mdata: MuData, + alphabet: Literal['[AGTCBDHKMNRSVWY]'] = '[AGTCBDHKMNRSVWY]', + batch_key: str = MDKM.INFERRED_BATCH_KEY, + id_length: int = 16 +) -> MuData: + cleaned_modality_dict = {} + for modality, modality_adata in mdata.mod.items(): + cleaned_modality_adata = clean_obs_names(adata=modality_adata, + alphabet=alphabet, + batch_key=batch_key, + id_length=id_length) + cleaned_modality_dict[modality] = cleaned_modality_adata.copy() + return MuData(cleaned_modality_dict) + + +def read(path_dict: Dict) -> MultiVelocity: + pass # Can significantly simply + +# ... from unmatched scRNA-seq and scATAC-seq data +def read_10x_atac_rna_h5_old( + atac_path: Union[PathLike, str], + rna_path: Union[PathLike, str], + atac_counts_matrix: Union[PathLike, str] = 'filtered_peak_bc_matrix', + rna_h5_fn: Union[PathLike, str] = 'filtered_feature_bc_matrix.h5', + rna_splicing_loom: Union[PathLike, str] = 'multiome.loom', + alphabet: Literal['[AGTCBDHKMNRSVWY]'] = '[AGTCBDHKMNRSVWY]', + batch_key: str = MDKM.INFERRED_BATCH_KEY, + cellranger_path_structure: bool = True, + id_length: int = 16 +) -> MuData: + from muon import atac as ac + import muon as mu + import scvi + main_info('Deserializing UNMATCHED scATAC-seq and scRNA-seq data ...') + temp_logger = LoggerManager.gen_logger('read_10x_atac_rna_h5') + temp_logger.log_time() + + # Read scATAC-seq h5 file + # ... counts + main_info(f'reading scATAC-seq data', indent_level=2) + atac_matrix_path = os.path.join(atac_path, + 'outs' if cellranger_path_structure else '', + atac_counts_matrix) + + atac_adata = scvi.data.read_10x_atac(atac_matrix_path) + + # Read scRNA-seq h5 file + main_info(f'reading scRNA-seq data', indent_level=2) + rna_h5_path = os.path.join(rna_path, + 'outs' if cellranger_path_structure else '', + rna_h5_fn) + + rna_adata = mu.read_10x_h5(filename=Path(rna_h5_path)).mod['rna'] + + # Assemble MuData object + main_info(f'combining scATAC-seq data and scRNA-seq data into MuData object ...', indent_level=2) + mdata = MuData({'atac': atac_adata, 'rna': rna_adata}) + + # Flag the scATAC-seq data as unmatched to the scRNA-seq data + main_info(f' .uns[{MDKM.MATCHED_ATAC_RNA_DATA_KEY}] = False', indent_level=3) + mdata.mod['atac'].uns[MDKM.MATCHED_ATAC_RNA_DATA_KEY] = False + + # Add path to fragment file + main_info(f" path to fragments file in .uns['files']", indent_level=3) + mdata.mod['atac'].uns['files'] = {'fragments': os.path.join(atac_path, + 'outs' if cellranger_path_structure else '', + 'fragments.tsv.gz')} + + # Add 'outs' paths + # ... atac + mdata.mod['atac'].uns['base_data_path'] = atac_path + + # ... rna + mdata.mod['rna'].uns['base_data_path'] = rna_path + + # Add peak annotation + main_info(f'adding peak annotation ...', indent_level=2) + ac.tl.add_peak_annotation(data=mdata, annotation=os.path.join(atac_path, + 'outs' if cellranger_path_structure else '', + 'peak_annotation.tsv')) + + # Homogenize cell indices across modalities + main_info(f'homogenizing cell indices ...', indent_level=2) + mdata = homogenize_mudata_obs_names(mdata=mdata, + alphabet=alphabet, + batch_key=batch_key, + id_length=id_length) + + # Add transcriptomic splicing data + main_info(f'adding splicing data ...', indent_level=2) + mdata = add_splicing_data(mdata=mdata, + multiome_base_path=rna_path, + rna_splicing_loom=rna_splicing_loom, + cellranger_path_structure=cellranger_path_structure) + + temp_logger.finish_progress(progress_name='read_10x_atac_rna_h5') + + return mdata + + +# ... from matched 10X multiome +def read_10x_multiome_h5_old( + multiome_base_path: Union[PathLike, str], + multiome_h5_fn: Union[PathLike, str] = 'filtered_feature_bc_matrix.h5', + rna_splicing_loom: Union[PathLike, str] = 'multiome.loom', + alphabet: Literal['[AGTCBDHKMNRSVWY]'] = '[AGTCBDHKMNRSVWY]', + batch_key: str = MDKM.INFERRED_BATCH_KEY, + cellranger_path_structure: bool = True, + id_length: int = 16 +) -> MuData: + import muon as mu + from muon import atac as ac + + main_info('Deserializing MATCHED scATAC-seq and scRNA-seq data ...') + temp_logger = LoggerManager.gen_logger('read_10x_multiome_h5') + temp_logger.log_time() + + # Assemble absolute path to multiomic data + full_multiome_path = os.path.join(multiome_base_path, + 'outs' if cellranger_path_structure else '', + multiome_h5_fn) + + # Read the multiome h5 file + main_info(f'reading the multiome h5 file ...', indent_level=2) + mdata = mu.read_10x_h5(Path(full_multiome_path), extended=True) + + # Flag the scATAC-seq data as matched to the scRNA-seq data + main_info(f' .uns[{MDKM.MATCHED_ATAC_RNA_DATA_KEY}] = True', indent_level=3) + mdata.mod['atac'].uns[MDKM.MATCHED_ATAC_RNA_DATA_KEY] = True + + # Add 'outs' paths - Note: for multiome they are identical + # ... atac + mdata.mod['atac'].uns['base_data_path'] = multiome_base_path + + # ... rna + mdata.mod['rna'].uns['base_data_path'] = multiome_base_path + + # Add path to fragment file + main_info(f" path to fragments file in .uns['files'] ...", indent_level=3) + mdata.mod['atac'].uns['files'] = {'fragments': os.path.join(multiome_base_path, + 'outs' if cellranger_path_structure else '', + 'fragments.tsv.gz')} + + # Add peak annotation + main_info(f'adding peak annotation ...', indent_level=2) + ac.tl.add_peak_annotation(data=mdata, annotation=os.path.join(multiome_base_path, + 'outs' if cellranger_path_structure else '', + 'peak_annotation.tsv')) + + # Homogenize cell indices across modalities + main_info(f'homogenizing cell indices ...', indent_level=2) + mdata = homogenize_mudata_obs_names(mdata=mdata, + alphabet=alphabet, + batch_key=batch_key, + id_length=id_length) + + # Add transcriptomic splicing data + main_info(f'adding splicing data ...', indent_level=2) + mdata = add_splicing_data(mdata=mdata, + multiome_base_path=multiome_base_path, + rna_splicing_loom=rna_splicing_loom, + cellranger_path_structure=cellranger_path_structure) + + temp_logger.finish_progress(progress_name='read_10x_multiome_h5') + + return mdata + + +def read_10x_multiome_h5( + multiome_base_path: Union[PathLike, str], + multiome_h5_fn: Union[PathLike, str] = 'filtered_feature_bc_matrix.h5', + rna_splicing_loom: Union[PathLike, str] = 'multiome.loom', + alphabet: Literal['[AGTCBDHKMNRSVWY]'] = '[AGTCBDHKMNRSVWY]', + batch_key: str = MDKM.INFERRED_BATCH_KEY, + cellranger_path_structure: bool = True, + id_length: int = 16, + gtf_path: Union[PathLike, str] = None, +): + import muon as mu + from muon import atac as ac + + main_info('Deserializing MATCHED scATAC-seq and scRNA-seq data ...') + temp_logger = LoggerManager.gen_logger('read_10x_multiome_h5') + temp_logger.log_time() + + # Assemble absolute path to multiomic data + full_multiome_path = os.path.join(multiome_base_path, + 'outs' if cellranger_path_structure else '', + multiome_h5_fn) + + # Read the multiome h5 file + main_info(f'reading the multiome h5 file ...', indent_level=2) + mdata = mu.read_10x_h5(Path(full_multiome_path), extended=True) + + # Flag the scATAC-seq data as matched to the scRNA-seq data + main_info(f' .uns[{MDKM.MATCHED_ATAC_RNA_DATA_KEY}] = True', indent_level=3) + mdata.mod['atac'].uns[MDKM.MATCHED_ATAC_RNA_DATA_KEY] = True + + # Add 'outs' paths - Note: for multiome they are identical + # ... atac + mdata.mod['atac'].uns['base_data_path'] = multiome_base_path + + # ... rna + mdata.mod['rna'].uns['base_data_path'] = multiome_base_path + + #Add path of fragments file if exist + fragments_path = os.path.join(multiome_base_path, + 'outs' if cellranger_path_structure else '', + 'fragments.tsv.gz') + if os.path.exists(fragments_path): + main_info(f" path to fragments file in .uns['files'] ...", indent_level=3) + mdata.mod['atac'].uns['files'] = {'fragments': fragments_path} + else: + main_info(f"fragments file not found in {fragments_path}", indent_level=3) + + # Add peak annotation file if exist + peak_annotation_path = os.path.join(multiome_base_path, + 'outs' if cellranger_path_structure else '', + 'peak_annotation.tsv') + if os.path.exists(peak_annotation_path): + main_info(f'adding peak annotation ...', indent_level=2) + ac.tl.add_peak_annotation(data=mdata, annotation=peak_annotation_path) + + elif gtf_path is not None: + main_info(f'adding peak annotation from gtf file ...', indent_level=2) + import Epiverse as ev + atac_anno=ev.utils.Annotation(gtf_path) + atac_anno.tss_init(upstream=1000, + downstream=100) + atac_anno.distal_init(upstream=[1000,200000], + downstream=[1000,200000]) + atac_anno.body_init() + + import pandas as pd + k=0 + for chr in mdata['atac'].var['seqnames'].unique(): + if k==0: + merge_pd=atac_anno.query_multi(query_list=mdata['atac'].var.loc[mdata['atac'].var['seqnames']==chr].index.tolist(), + chrom=chr,batch=4,ncpus=8) + else: + merge_pd1=atac_anno.query_multi(query_list=mdata['atac'].var.loc[mdata['atac'].var['seqnames']==chr].index.tolist(), + chrom=chr,batch=4,ncpus=8) + merge_pd=pd.concat([merge_pd,merge_pd1]) + k+=1 + merge_pd=atac_anno.merge_info(merge_pd) + atac_anno.add_gene_info(mdata['atac'],merge_pd, + columns=['peaktype','neargene','neargene_tss']) + else: + main_info(f"peak annotation file not found in {peak_annotation_path} and gtf file not provided", indent_level=3) + + # Homogenize cell indices across modalities + main_info(f'homogenizing cell indices ...', indent_level=2) + mdata = homogenize_mudata_obs_names(mdata=mdata, + alphabet=alphabet, + batch_key=batch_key, + id_length=id_length) + + # Add transcriptomic splicing data if exist + rna_splicing_loom_path = os.path.join(multiome_base_path, + 'velocyto' if cellranger_path_structure else '', + rna_splicing_loom) + if os.path.exists(rna_splicing_loom_path): + main_info(f'adding splicing data ...', indent_level=2) + mdata = add_splicing_data(mdata=mdata, + multiome_base_path=multiome_base_path, + rna_splicing_loom=rna_splicing_loom, + cellranger_path_structure=cellranger_path_structure) + else: + main_info(f"splicing data file not found in {rna_splicing_loom_path}", indent_level=3) + + # Aggregate_peaks_10x + main_info(f'aggregating peaks ...', indent_level=2) + feature_linkage_path=os.path.join(multiome_base_path, + 'outs' if cellranger_path_structure else '', + 'analysis/feature_linkage/feature_linkage.bedpe') + adata_aggr = aggregate_peaks_10x(mdata['atac'], + peak_annotation_path, + feature_linkage_path) + + mdata.mod['aggr']=adata_aggr + + + temp_logger.finish_progress(progress_name='read_10x_multiome_h5') + + return mdata \ No newline at end of file diff --git a/dynamo/multivelo/MultiPreprocessor.py b/dynamo/multivelo/MultiPreprocessor.py new file mode 100644 index 00000000..297ea91c --- /dev/null +++ b/dynamo/multivelo/MultiPreprocessor.py @@ -0,0 +1,718 @@ +# Imports from external modules +from anndata import AnnData +from .MultiConfiguration import MDKM +from mudata import MuData + +import numpy as np +import os +import pandas as pd +import scanpy as sc +from tqdm import tqdm +from scipy.sparse import coo_matrix, csr_matrix, diags + +from typing import Any, Callable, Dict, List, Literal, Optional, TypedDict + +# Imports from dynamo +from ..dynamo_logger import ( + LoggerManager, + main_debug, + main_exception, + main_info, + main_info_insert_adata, + main_warning, +) +from ..preprocessing.gene_selection import ( + select_genes_monocle +) +from ..preprocessing.normalization import ( + calc_sz_factor, + normalize +) +from ..preprocessing.pca import ( + pca +) +from ..preprocessing.Preprocessor import ( + Preprocessor +) +from ..preprocessing.QC import ( + filter_cells_by_highly_variable_genes, + filter_cells_by_outliers as monocle_filter_cells_by_outliers, + filter_genes_by_outliers as monocle_filter_genes_by_outliers +) +from ..preprocessing.transform import ( + log1p +) +from ..preprocessing.utils import ( + collapse_species_adata, + convert2symbol +) + +# Imports from MultiDynamo +from .ATACseqTools import ( + tfidf_normalize +) +from .MultiQC import ( + modality_basic_stats, + modality_filter_cells_by_outliers, + modality_filter_features_by_outliers +) + +# Define a custom type for the recipe dictionary using TypedDict +ATACType = Literal['archR', 'cicero', 'muon', 'signac'] +CITEType = Literal['seurat'] +HiCType = Literal['periwal'] +ModalityType = Literal['atac', 'cite', 'hic', 'rna'] +RNAType = Literal['monocle', 'seurat', 'sctransform', 'pearson_residuals', 'monocle_pearson_residuals'] + +class RecipeDataType(TypedDict, total=False): # total=False allows partial dictionary to be valid + atac: ATACType + cite: CITEType + hic: HiCType + rna: RNAType + + +# The Multiomic Preprocessor class, MultiPreprocessor +class MultiPreprocessor(Preprocessor): + def __init__( + self, + cell_cycle_score_enable: bool=False, + cell_cycle_score_kwargs: Dict[str, Any] = {}, + collapse_species_adata_function: Callable = collapse_species_adata, + convert_gene_name_function: Callable=convert2symbol, + filter_cells_by_highly_variable_genes_function: Callable = filter_cells_by_highly_variable_genes, + filter_cells_by_highly_variable_genes_kwargs: Dict[str, Any] = {}, + filter_cells_by_outliers_function: Callable=monocle_filter_cells_by_outliers, + filter_cells_by_outliers_kwargs: Dict[str, Any] = {}, + filter_genes_by_outliers_function: Callable=monocle_filter_genes_by_outliers, + filter_genes_by_outliers_kwargs: Dict[str, Any] = {}, + force_gene_list: Optional[List[str]]=None, + gene_append_list: List[str] = [], + gene_exclude_list: List[str] = {}, + norm_method: Callable=log1p, + norm_method_kwargs: Dict[str, Any] = {}, + normalize_by_cells_function: Callable=normalize, + normalize_by_cells_function_kwargs: Dict[str, Any] = {}, + normalize_selected_genes_function: Callable=None, + normalize_selected_genes_kwargs: Dict[str, Any] = {}, + pca_function: Callable=pca, + pca_kwargs: Dict[str, Any] = {}, + regress_out_kwargs: Dict[List[str], Any] = {}, + sctransform_kwargs: Dict[str, Any] = {}, + select_genes_function: Callable = select_genes_monocle, + select_genes_kwargs: Dict[str, Any] = {}, + size_factor_function: Callable=calc_sz_factor, + size_factor_kwargs: Dict[str, Any] = {}) -> None: + super().__init__( + collapse_species_adata_function = collapse_species_adata_function, + convert_gene_name_function = convert_gene_name_function, + filter_cells_by_outliers_function = filter_cells_by_outliers_function, + filter_cells_by_outliers_kwargs = filter_cells_by_outliers_kwargs, + filter_genes_by_outliers_function = filter_genes_by_outliers_function, + filter_genes_by_outliers_kwargs = filter_genes_by_outliers_kwargs, + filter_cells_by_highly_variable_genes_function = filter_cells_by_highly_variable_genes_function, + filter_cells_by_highly_variable_genes_kwargs = filter_cells_by_highly_variable_genes_kwargs, + normalize_by_cells_function = normalize_by_cells_function, + normalize_by_cells_function_kwargs = normalize_by_cells_function_kwargs, + size_factor_function = size_factor_function, + size_factor_kwargs = size_factor_kwargs, + select_genes_function = select_genes_function, + select_genes_kwargs = select_genes_kwargs, + normalize_selected_genes_function = normalize_selected_genes_function, + normalize_selected_genes_kwargs = normalize_selected_genes_kwargs, + norm_method = norm_method, + norm_method_kwargs = norm_method_kwargs, + pca_function = pca_function, + pca_kwargs = pca_kwargs, + gene_append_list = gene_append_list, + gene_exclude_list = gene_exclude_list, + force_gene_list = force_gene_list, + sctransform_kwargs = sctransform_kwargs, + regress_out_kwargs = regress_out_kwargs, + cell_cycle_score_enable = cell_cycle_score_enable, + cell_cycle_score_kwargs = cell_cycle_score_kwargs + ) + + def preprocess_atac( + self, + mdata: MuData, + recipe: ATACType = 'muon', + tkey: Optional[str] = None, + experiment_type: Optional[str] = None + ) -> None: + if recipe == 'archR': + self.preprocess_atac_archr(mdata, + tkey=tkey, + experiment_type=experiment_type) + elif recipe == 'cicero': + self.preprocess_atac_cicero(mdata, + tkey=tkey, + experiment_type=experiment_type) + elif recipe == 'muon': + self.preprocess_atac_muon(mdata, + tkey=tkey, + experiment_type=experiment_type) + elif recipe == 'signac': + self.preprocess_atac_signac(mdata, + tkey=tkey, + experiment_type=experiment_type) + else: + raise NotImplementedError("preprocess recipe chosen not implemented: %s" % recipe) + + def preprocess_atac_archr( + self, + mdata: MuData, + tkey: Optional[str] = None, + experiment_type: Optional[str] = None + ) -> None: + pass + + def preprocess_atac_cicero( + self, + mdata: MuData, + tkey: Optional[str] = None, + experiment_type: Optional[str] = None + ) -> None: + pass + + def preprocess_atac_muon( + self, + mdata: MuData, + tkey: Optional[str] = None, + experiment_type: Optional[str] = None + ) -> None: + from muon import atac as ac + main_info('Running muon preprocessing pipeline for scATAC-seq data ...') + preprocess_logger = LoggerManager.gen_logger('preprocess_atac_muon') + preprocess_logger.log_time() + + # Standardize MuData object + self.standardize_mdata(mdata, tkey, experiment_type) + + # Filter peaks + modality_filter_features_by_outliers(mdata, + modality='atac', + quantiles=[0.01, 0.99], + var_key='n_cells_by_counts') + + # Filter cells + modality_filter_cells_by_outliers(mdata, + modality='atac', + quantiles=[0.01, 0.99], + obs_key='n_genes_by_counts') + + modality_filter_cells_by_outliers(mdata, + modality='atac', + quantiles=[0.01, 0.99], + obs_key='total_counts') + + # Extract chromatin accessibility and transcriptome + atac_adata, rna_adata = mdata.mod['atac'], mdata.mod['rna'] + + # ... store counts layer used for SCVI's variational autoencoders + atac_adata.layers[MDKM.ATAC_COUNTS_LAYER] = atac_adata.X + rna_adata.layers[MDKM.RNA_COUNTS_LAYER] = rna_adata.X + + # ... compute TF-IDF + main_info(f'computing TF-IDF', indent_level=1) + atac_adata = tfidf_normalize(atac_adata=atac_adata, mv_algorithm=False) + + # Normalize + main_info(f'normalizing', indent_level=1) + sc.pp.normalize_total(atac_adata, target_sum=1e4) + sc.pp.log1p(atac_adata) + + # Feature selection + main_info(f'feature selection', indent_level=1) + sc.pp.highly_variable_genes(atac_adata, min_mean=0.05, max_mean=1.5, min_disp=0.5) + main_info(f'identified {np.sum(atac_adata.var.highly_variable)} highly variable features', indent_level=2) + + # Store current AnnData object in raw + atac_adata.raw = atac_adata + + # Latent sematic indexing + main_info(f'computing latent sematic indexing', indent_level=1) + ac.tl.lsi(atac_adata) + + # ... drop first component (size related) + main_info(f' X_lsi key in .obsm', indent_level=2) + atac_adata.obsm[MDKM.ATAC_OBSM_LSI_KEY] = atac_adata.obsm[MDKM.ATAC_OBSM_LSI_KEY][:, 1:] + main_info(f' LSI key in .varm', indent_level=2) + atac_adata.varm[MDKM.ATAC_VARM_LSI_KEY] = atac_adata.varm[MDKM.ATAC_VARM_LSI_KEY][:, 1:] + main_info(f' [lsi][stdev] key in .uns', indent_level=2) + atac_adata.uns['lsi']['stdev'] = atac_adata.uns['lsi']['stdev'][1:] + + # ... perhaps gratuitous deep copy + mdata.mod['atac'] = atac_adata.copy() + + preprocess_logger.finish_progress(progress_name='preprocess_atac_muon') + + def preprocess_atac_signac( + self, + mdata: MuData, + recipe: ATACType = 'muon', + tkey: Optional[str] = None, + experiment_type: Optional[str] = None + ) -> None: + pass + + def preprocess_cite( + self, + mdata: MuData, + recipe: CITEType + ) -> None: + pass + + def preprocess_hic( + self, + mdata: MuData, + recipe: HiCType + ) -> None: + pass + + def preprocess_mdata( + self, + mdata: MuData, + recipe_dict: RecipeDataType = None, + tkey: Optional[str] = None, + experiment_type: Optional[str] = None, + ) -> None: + """Preprocess the MuData object with the recipe specified. + + Args: + mdata: An AnnData object. + recipe_dict: The recipe used to preprocess the data. Current modalities are scATAC-seq, CITE-seq, scHi-C + and scRNA-seq + tkey: the key for time information (labeling time period for the cells) in .obs. Defaults to None. + experiment_type: the experiment type of the data. If not provided, would be inferred from the data. + + Raises: + NotImplementedError: the recipe is invalid. + """ + + if recipe_dict is None: + # Default recipe + recipe_dict = {'atac': 'signac', 'rna': 'seurat'} + + for mod, recipe in recipe_dict.items(): + if mod not in mdata.mod: + main_exception((f'Modality {mod} not found in MuData object')) + + if mod == 'atac': + self.preprocess_atac(mdata=mdata, + recipe=recipe, + tkey=tkey, + experiment_type=experiment_type) + + elif mod == 'cite': + self.preprocess_cite(mdata=mdata, + recipe=recipe, + tkey=tkey, + experiment_type=experiment_type) + elif mod == 'hic': + self.preprocess_hic(mdata=mdata, + recipe=recipe, + tkey=tkey, + experiment_type=experiment_type) + elif mod == 'rna': + rna_adata = mdata.mod.get('rna', None) + + self.preprocess_adata(adata=rna_adata, + recipe=recipe, + tkey=tkey, + experiment_type=experiment_type) + else: + raise NotImplementedError(f'Preprocess recipe not implemented for modality: {mod}') + + # Integrate modalities - at this point have filtered out poor quality cells for individual + # modalities. Next we need to + + def standardize_mdata( + self, + mdata: MuData, + tkey: str, + experiment_type: str + ) -> None: + """Process the scATAC-seq modality within MuData to make it meet the standards of dynamo. + + The index of the observations would be ensured to be unique. The layers with sparse matrix would be converted to + compressed csr_matrix. MDKM.allowed_layer_raw_names() will be used to define only_splicing, only_labeling and + splicing_labeling keys. + + Args: + mdata: an AnnData object. + tkey: the key for time information (labeling time period for the cells) in .obs. + experiment_type: the experiment type. + """ + + for modality, modality_adata in mdata.mod.items(): + if modality == 'rna': + # Handled by dynamo + continue + + # Compute basic QC metrics + modality_basic_stats(mdata=mdata, modality=modality) + + self.add_experiment_info(modality_adata, tkey, experiment_type) + main_info_insert_adata("tkey=%s" % tkey, "uns['pp']", indent_level=2) + main_info_insert_adata("experiment_type=%s" % modality_adata.uns["pp"]["experiment_type"], + "uns['pp']", + indent_level=2) + + self.convert_layers2csr(modality_adata) + + +def aggregate_peaks_10x(adata_atac, peak_annot_file, linkage_file, + peak_dist=10000, min_corr=0.5, gene_body=False, + return_dict=False, parallel=False, n_jobs=1): + + """Peak to gene aggregation. + + This function aggregates promoter and enhancer peaks to genes based on the + 10X linkage file. + + Parameters + ---------- + adata_atac: :class:`~anndata.AnnData` + ATAC anndata object which stores raw peak counts. + peak_annot_file: `str` + Peak annotation file from 10X CellRanger ARC. + linkage_file: `str` + Peak-gene linkage file from 10X CellRanger ARC. This file stores highly + correlated peak-peak and peak-gene pair information. + peak_dist: `int` (default: 10000) + Maximum distance for peaks to be included for a gene. + min_corr: `float` (default: 0.5) + Minimum correlation for a peak to be considered as enhancer. + gene_body: `bool` (default: `False`) + Whether to add gene body peaks to the associated promoters. + return_dict: `bool` (default: `False`) + Whether to return promoter and enhancer dictionaries. + + Returns + ------- + A new ATAC anndata object which stores gene aggreagted peak counts. + Additionally, if `return_dict==True`: + A dictionary which stores genes and promoter peaks. + And a dictionary which stores genes and enhancer peaks. + """ + promoter_dict = {} + distal_dict = {} + gene_body_dict = {} + corr_dict = {} + + # read annotations + with open(peak_annot_file) as f: + header = next(f) + tmp = header.split('\t') + if len(tmp) == 4: + cellranger_version = 1 + elif len(tmp) == 6: + cellranger_version = 2 + else: + raise ValueError('Peak annotation file should contain 4 columns ' + '(CellRanger ARC 1.0.0) or 6 columns (CellRanger ' + 'ARC 2.0.0)') + + main_info(f'CellRanger ARC identified as {cellranger_version}.0.0', + indent_level=1) + + if cellranger_version == 1: + for line in f: + tmp = line.rstrip().split('\t') + tmp1 = tmp[0].split('_') + peak = f'{tmp1[0]}:{tmp1[1]}-{tmp1[2]}' + if tmp[1] != '': + genes = tmp[1].split(';') + dists = tmp[2].split(';') + types = tmp[3].split(';') + for i, gene in enumerate(genes): + dist = dists[i] + annot = types[i] + if annot == 'promoter': + if gene not in promoter_dict: + promoter_dict[gene] = [peak] + else: + promoter_dict[gene].append(peak) + elif annot == 'distal': + if dist == '0': + if gene not in gene_body_dict: + gene_body_dict[gene] = [peak] + else: + gene_body_dict[gene].append(peak) + else: + if gene not in distal_dict: + distal_dict[gene] = [peak] + else: + distal_dict[gene].append(peak) + else: + for line in f: + tmp = line.rstrip().split('\t') + peak = f'{tmp[0]}:{tmp[1]}-{tmp[2]}' + gene = tmp[3] + dist = tmp[4] + annot = tmp[5] + if annot == 'promoter': + if gene not in promoter_dict: + promoter_dict[gene] = [peak] + else: + promoter_dict[gene].append(peak) + elif annot == 'distal': + if dist == '0': + if gene not in gene_body_dict: + gene_body_dict[gene] = [peak] + else: + gene_body_dict[gene].append(peak) + else: + if gene not in distal_dict: + distal_dict[gene] = [peak] + else: + distal_dict[gene].append(peak) + + # read linkages + with open(linkage_file) as f: + for line in f: + tmp = line.rstrip().split('\t') + if tmp[12] == "peak-peak": + peak1 = f'{tmp[0]}:{tmp[1]}-{tmp[2]}' + peak2 = f'{tmp[3]}:{tmp[4]}-{tmp[5]}' + tmp2 = tmp[6].split('><')[0][1:].split(';') + tmp3 = tmp[6].split('><')[1][:-1].split(';') + corr = float(tmp[7]) + for t2 in tmp2: + gene1 = t2.split('_') + for t3 in tmp3: + gene2 = t3.split('_') + # one of the peaks is in promoter, peaks belong to the + # same gene or are close in distance + if (((gene1[1] == "promoter") != + (gene2[1] == "promoter")) and + ((gene1[0] == gene2[0]) or + (float(tmp[11]) < peak_dist))): + + if gene1[1] == "promoter": + gene = gene1[0] + else: + gene = gene2[0] + if gene in corr_dict: + # peak 1 is in promoter, peak 2 is not in gene + # body -> peak 2 is added to gene 1 + if (peak2 not in corr_dict[gene] and + gene1[1] == "promoter" and + (gene2[0] not in gene_body_dict or + peak2 not in gene_body_dict[gene2[0]])): + + corr_dict[gene][0].append(peak2) + corr_dict[gene][1].append(corr) + # peak 2 is in promoter, peak 1 is not in gene + # body -> peak 1 is added to gene 2 + if (peak1 not in corr_dict[gene] and + gene2[1] == "promoter" and + (gene1[0] not in gene_body_dict or + peak1 not in gene_body_dict[gene1[0]])): + + corr_dict[gene][0].append(peak1) + corr_dict[gene][1].append(corr) + else: + # peak 1 is in promoter, peak 2 is not in gene + # body -> peak 2 is added to gene 1 + if (gene1[1] == "promoter" and + (gene2[0] not in + gene_body_dict + or peak2 not in + gene_body_dict[gene2[0]])): + + corr_dict[gene] = [[peak2], [corr]] + # peak 2 is in promoter, peak 1 is not in gene + # body -> peak 1 is added to gene 2 + if (gene2[1] == "promoter" and + (gene1[0] not in + gene_body_dict + or peak1 not in + gene_body_dict[gene1[0]])): + + corr_dict[gene] = [[peak1], [corr]] + elif tmp[12] == "peak-gene": + peak1 = f'{tmp[0]}:{tmp[1]}-{tmp[2]}' + tmp2 = tmp[6].split('><')[0][1:].split(';') + gene2 = tmp[6].split('><')[1][:-1] + corr = float(tmp[7]) + for t2 in tmp2: + gene1 = t2.split('_') + # peak 1 belongs to gene 2 or are close in distance + # -> peak 1 is added to gene 2 + if ((gene1[0] == gene2) or (float(tmp[11]) < peak_dist)): + gene = gene1[0] + if gene in corr_dict: + if (peak1 not in corr_dict[gene] and + gene1[1] != "promoter" and + (gene1[0] not in gene_body_dict or + peak1 not in gene_body_dict[gene1[0]])): + + corr_dict[gene][0].append(peak1) + corr_dict[gene][1].append(corr) + else: + if (gene1[1] != "promoter" and + (gene1[0] not in gene_body_dict or + peak1 not in gene_body_dict[gene1[0]])): + corr_dict[gene] = [[peak1], [corr]] + elif tmp[12] == "gene-peak": + peak2 = f'{tmp[3]}:{tmp[4]}-{tmp[5]}' + gene1 = tmp[6].split('><')[0][1:] + tmp3 = tmp[6].split('><')[1][:-1].split(';') + corr = float(tmp[7]) + for t3 in tmp3: + gene2 = t3.split('_') + # peak 2 belongs to gene 1 or are close in distance + # -> peak 2 is added to gene 1 + if ((gene1 == gene2[0]) or (float(tmp[11]) < peak_dist)): + gene = gene1 + if gene in corr_dict: + if (peak2 not in corr_dict[gene] and + gene2[1] != "promoter" and + (gene2[0] not in gene_body_dict or + peak2 not in gene_body_dict[gene2[0]])): + + corr_dict[gene][0].append(peak2) + corr_dict[gene][1].append(corr) + else: + if (gene2[1] != "promoter" and + (gene2[0] not in gene_body_dict or + peak2 not in gene_body_dict[gene2[0]])): + + corr_dict[gene] = [[peak2], [corr]] + + gene_dict = promoter_dict + enhancer_dict = {} + promoter_genes = list(promoter_dict.keys()) + main_info(f'Found {len(promoter_genes)} genes with promoter peaks', indent_level=1) + for gene in promoter_genes: + if gene_body: # add gene-body peaks + if gene in gene_body_dict: + for peak in gene_body_dict[gene]: + if peak not in gene_dict[gene]: + gene_dict[gene].append(peak) + enhancer_dict[gene] = [] + if gene in corr_dict: # add enhancer peaks + for j, peak in enumerate(corr_dict[gene][0]): + corr = corr_dict[gene][1][j] + if corr > min_corr: + if peak not in gene_dict[gene]: + gene_dict[gene].append(peak) + enhancer_dict[gene].append(peak) + + # aggregate to genes + adata_atac_X_copy = adata_atac.X.A + gene_mat = np.zeros((adata_atac.shape[0], len(promoter_genes))) + var_names = adata_atac.var_names.to_numpy() + var_dict = {} + + for i, name in enumerate(var_names): + var_dict.update({name: i}) + + # if we only want to run one job at a time, then no parallelization + # is necessary + if n_jobs == 1: + parallel = False + + if parallel: + from joblib import Parallel, delayed + # if we want to run in parallel, modify the gene_mat variable with + # multiple cores, calling prepare_gene_mat with joblib.Parallel() + Parallel(n_jobs=n_jobs, + require='sharedmem')( + delayed(prepare_gene_mat)(var_dict, + gene_dict[promoter_genes[i]], + gene_mat, + adata_atac_X_copy, + i)for i in tqdm(range( + len(promoter_genes)))) + + else: + # if we aren't running in parallel, just call prepare_gene_mat + # from a for loop + for i, gene in tqdm(enumerate(promoter_genes), + total=len(promoter_genes)): + prepare_gene_mat(var_dict, + gene_dict[promoter_genes[i]], + gene_mat, + adata_atac_X_copy, + i) + + gene_mat[gene_mat < 0] = 0 + gene_mat = AnnData(X=csr_matrix(gene_mat)) + gene_mat.obs_names = pd.Index(list(adata_atac.obs_names)) + gene_mat.var_names = pd.Index(promoter_genes) + gene_mat = gene_mat[:, gene_mat.X.sum(0) > 0] + if return_dict: + return gene_mat, promoter_dict, enhancer_dict + else: + return gene_mat + +def prepare_gene_mat(var_dict, peaks, gene_mat, adata_atac_X_copy, i): + + for peak in peaks: + if peak in var_dict: + peak_index = var_dict[peak] + + gene_mat[:, i] += adata_atac_X_copy[:, peak_index] + + + +def knn_smooth_chrom(adata_atac, nn_idx=None, nn_dist=None, conn=None, + n_neighbors=None): + """KNN smoothing. + + This function smooth (impute) the count matrix with k nearest neighbors. + The inputs can be either KNN index and distance matrices or a pre-computed + connectivities matrix (for example in adata_rna object). + + Parameters + ---------- + adata_atac: :class:`~anndata.AnnData` + ATAC anndata object. + nn_idx: `np.darray` (default: `None`) + KNN index matrix of size (cells, k). + nn_dist: `np.darray` (default: `None`) + KNN distance matrix of size (cells, k). + conn: `csr_matrix` (default: `None`) + Pre-computed connectivities matrix. + n_neighbors: `int` (default: `None`) + Top N neighbors to extract for each cell in the connectivities matrix. + + Returns + ------- + `.layers['Mc']` stores imputed values. + """ + if nn_idx is not None and nn_dist is not None: + if nn_idx.shape[0] != adata_atac.shape[0]: + raise ValueError('Number of rows of KNN indices does not equal to ' + 'number of observations.') + if nn_dist.shape[0] != adata_atac.shape[0]: + raise ValueError('Number of rows of KNN distances does not equal ' + 'to number of observations.') + X = coo_matrix(([], ([], [])), shape=(nn_idx.shape[0], 1)) + from umap.umap_ import fuzzy_simplicial_set + conn, sigma, rho, dists = fuzzy_simplicial_set(X, nn_idx.shape[1], + None, None, + knn_indices=nn_idx-1, + knn_dists=nn_dist, + return_dists=True) + elif conn is not None: + pass + else: + raise ValueError('Please input nearest neighbor indices and distances,' + ' or a connectivities matrix of size n x n, with ' + 'columns being neighbors.' + ' For example, RNA connectivities can usually be ' + 'found in adata.obsp.') + + conn = conn.tocsr().copy() + n_counts = (conn > 0).sum(1).A1 + if n_neighbors is not None and n_neighbors < n_counts.min(): + from .sparse_matrix_utils import top_n_sparse + conn = top_n_sparse(conn, n_neighbors) + conn.setdiag(1) + conn_norm = conn.multiply(1.0 / conn.sum(1)).tocsr() + adata_atac.layers['Mc'] = csr_matrix.dot(conn_norm, adata_atac.X) + adata_atac.obsp['connectivities'] = conn + diff --git a/dynamo/multivelo/MultiQC.py b/dynamo/multivelo/MultiQC.py new file mode 100644 index 00000000..8604af14 --- /dev/null +++ b/dynamo/multivelo/MultiQC.py @@ -0,0 +1,141 @@ +import anndata as ad +from anndata import AnnData +from .MultiConfiguration import MDKM +from mudata import MuData + + + +import numpy as np +import pandas as pd +import scanpy as sc +from scipy.sparse import ( + issparse +) +from typing import ( + List, + Literal, + Optional, + Union, +) + +# Define several Literals - might move to MDKM +ModalityType = Literal['atac', 'cite', 'hic', 'rna'] +ObsKeyType = Literal['n_genes_by_counts', 'total_counts'] +VarKeyType = Literal['n_cells_by_counts'] + +# Imports from dynamo +from ..dynamo_logger import ( + LoggerManager, + main_debug, + main_exception, + main_finish_progress, + main_info, + main_info_insert_adata, + main_warning, +) + +def modality_basic_stats( + mdata: MuData, + modality: ModalityType = None +) -> None: + """Generate basic stats of the adata, including number of genes, number of cells, and number of mitochondria genes. + + Args: + adata: an AnnData object. + + Returns: + An updated AnnData object with a number of QC metrics computed: 'n_cells_by_counts', 'n_features_by_counts', and + 'total_counts'. (Note: since most modalities do not have direct information about related genes, fractions of + mitochondrial genes cannot be computed.) + """ + from muon import atac as ac + modality_adata = mdata.mod.get(modality, None) + if modality_adata is None: + raise ValueError(f'Modality {modality} not found in MuData object.') + + # Compute QC metrics via functionality in scanpy + sc.pp.calculate_qc_metrics(modality_adata, percent_top=None, log1p=False, inplace=True) + + # Compute modality specific QC metrics + if modality == 'atac': + ac.tl.nucleosome_signal(mdata, n=1e6) + + +def modality_filter_cells_by_outliers( + mdata: MuData, + modality: ModalityType = 'atac', + obs_key: VarKeyType = 'n_cells_by_counts', + quantiles: Optional[Union[List[float], float]] = [0.01, 0.99], + thresholds: Optional[Union[List[float], float]] = None +) -> None: + import muon as mu + modality_adata = mdata.mod.get(modality, None) + if modality_adata is None: + raise ValueError(f'Modality {modality} not found in MuData object.') + + if quantiles is not None: + # Thresholds were specified as quantiles + qc_parameter_series = modality_adata.obs[obs_key] + + if isinstance(quantiles, list): + if len(quantiles) > 2: + raise ValueError(f'More than 2 quantiles were specified {len(quantiles)}.') + + min_feature_thresh, max_feature_thresh = qc_parameter_series.quantile(quantiles).tolist() + else: + min_feature_thresh, max_feature_thresh = qc_parameter_series.quantile(quantiles), np.inf + else: + # Thresholds were specified as absolute thresholds + if isinstance(thresholds, list): + if len(thresholds) > 2: + raise ValueError(f'More than 2 thresholds were specified {len(thresholds)}.') + + min_feature_thresh, max_feature_thresh = thresholds + else: + min_feature_thresh, max_feature_thresh = thresholds, np.inf + + # Carry out the actual filtering + pre_filter_n_cells = modality_adata.n_obs + mu.pp.filter_obs(modality_adata, obs_key, lambda x: (x >= min_feature_thresh) & (x <= max_feature_thresh)) + post_filter_n_cells = modality_adata.n_obs + main_info(f'filtered out {pre_filter_n_cells - post_filter_n_cells} outlier cells', indent_level=2) + + +def modality_filter_features_by_outliers( + mdata: MuData, + modality: ModalityType = 'atac', + quantiles: Optional[Union[List[float], float]] = [0.01, 0.99], + thresholds: Optional[Union[List[float], float]] = None, + var_key: ObsKeyType = 'n_cells_by_counts' +) -> None: + import muon as mu + modality_adata = mdata.mod.get(modality, None) + if modality_adata is None: + raise ValueError(f'Modality {modality} not found in MuData object.') + + if quantiles is not None: + # Thresholds were specified as quantiles + qc_parameter_series = modality_adata.var[var_key] + + if isinstance(quantiles, list): + if len(quantiles) > 2: + raise ValueError(f'More than 2 quantiles were specified {len(quantiles)}.') + + min_feature_thresh, max_feature_thresh = qc_parameter_series.quantile(quantiles).tolist() + else: + min_feature_thresh, max_feature_thresh = qc_parameter_series.quantile(quantiles), np.inf + else: + # Thresholds were specified as absolute thresholds + if isinstance(thresholds, list): + if len(thresholds) > 2: + raise ValueError(f'More than 2 thresholds were specified {len(thresholds)}.') + + min_feature_thresh, max_feature_thresh = thresholds + else: + min_feature_thresh, max_feature_thresh = thresholds, np.inf + + # Carry out the actual filtering + pre_filter_n_cells = modality_adata.n_obs + mu.pp.filter_var(modality_adata, var_key, lambda x: (x >= min_feature_thresh) & (x <= max_feature_thresh)) + post_filter_n_cells = modality_adata.n_obs + main_info(f'filtered out {pre_filter_n_cells - post_filter_n_cells} outlier features', indent_level=2) diff --git a/dynamo/multivelo/MultiVelo.py b/dynamo/multivelo/MultiVelo.py new file mode 100644 index 00000000..7e1405d2 --- /dev/null +++ b/dynamo/multivelo/MultiVelo.py @@ -0,0 +1,115 @@ +import pandas as pd +import numpy as np +from anndata import AnnData +from mudata import MuData +from typing import Dict + +from ..tl import dynamics,reduceDimension,cell_velocities +from .MultiPreprocessor import knn_smooth_chrom +from .dynamical_chrom_func import recover_dynamics_chrom + +def multi_velocities( + mdata: MuData, + model: str='stochastic', + method: str='pearson', + other_kernels_dict: Dict={'transform': 'sqrt'}, + core: int=3, + device: str='cpu', + extra_color_key: str=None, + max_iter: int=5, + velo_arg: Dict ={}, + vkey: str='velo_s', + **kwargs +)->AnnData: + """ + Calculate the velocites using the scRNA-seq and scATAC-seq data. + + Args: + mdata: MuData object containing the RNA and ATAC data. + model: The model used to calculate the dynamics. Default is 'stochastic'. + method: The method used to calculate the velocity. Default is 'pearson'. + other_kernels_dict: The dictionary containing the parameters for the other kernels. Default is {'transform': 'sqrt'}. + core: The number of cores used for the calculation. Default is 3. + device: The device used for the calculation. Default is 'cpu'. + extra_color_key: The extra color key used for the calculation. Default is None. + max_iter: The maximum number of iterations used for the calculation. Default is 5. + velo_arg: The dictionary containing the parameters for the velocity calculation. Default is {}. + vkey: The key used for the velocity calculation. Default is 'velo_s'. + **kwargs: The other parameters used for the calculation. + + Returns: + An updated AnnData object with the velocities calculated. + + """ + # We need to calculate the dynamics of the RNA data first and reduce the dimensionality + dynamics(mdata['rna'], model=model, cores=core) + reduceDimension(mdata['rna']) + cell_velocities(mdata['rna'], method=method, + other_kernels_dict=other_kernels_dict, + **velo_arg + ) + + # And we use the connectivity matrix from the RNA data to smooth the ATAC data and calculate the Mc + knn_smooth_chrom(mdata['aggr'], conn= mdata['rna'].obsp['connectivities']) + + # We then select the genes that are present in both datasets + shared_cells = pd.Index(np.intersect1d(mdata['rna'].obs_names, mdata['aggr'].obs_names)) + shared_genes = pd.Index(np.intersect1d( + [i.split('rna:')[-1] for i in mdata['rna'][:,mdata['rna'].var['use_for_dynamics']].var_names], + [i.split('aggr:')[-1] for i in mdata['aggr'].var_names] + )) + + # We then create the AnnData objects for the RNA and ATAC data + adata_rna = mdata['rna'][shared_cells, [f'rna:{i}' for i in shared_genes]].copy() + adata_atac = mdata['aggr'][shared_cells, [f'aggr:{i}' for i in shared_genes]].copy() + adata_rna.var.index=[i.split('rna:')[-1] for i in adata_rna.var.index] + adata_atac.var.index=[i.split('aggr:')[-1] for i in adata_atac.var.index] + + adata_rna.layers['Ms']=adata_rna.layers['M_s'] + adata_rna.layers['Mu']=adata_rna.layers['M_u'] + + # Now we use MultiVelo's recover_dynamics_chrom function to calculate the dynamics of the RNA and ATAC data + adata_result = recover_dynamics_chrom(adata_rna, + adata_atac, + max_iter=max_iter, + init_mode="invert", + parallel=True, + n_jobs = core, + save_plot=False, + rna_only=False, + fit=True, + n_anchors=500, + extra_color_key=extra_color_key, + device=device, + **kwargs + ) + + # We need to add some information of new RNA velocity to the ATAC data + if vkey not in adata_result.layers.keys(): + raise ValueError('Velocity matrix is not found. Please run multivelo' + '.recover_dynamics_chrom function first.') + if vkey+'_norm' not in adata_result.layers.keys(): + adata_result.layers[vkey+'_norm'] = adata_result.layers[vkey] / np.sum( + np.abs(adata_result.layers[vkey]), 0) + adata_result.layers[vkey+'_norm'] /= np.mean(adata_result.layers[vkey+'_norm']) + adata_result.uns[vkey+'_norm_params'] = adata_result.uns[vkey+'_params'] + if vkey+'_norm_genes' not in adata_result.var.columns: + adata_result.var[vkey+'_norm_genes'] = adata_result.var[vkey+'_genes'] + + # Transition genes identification and velocity calculation + transition_genes=adata_result.var.loc[adata_result.var['velo_s_norm_genes']==True].index.tolist() + if 'pearson_transition_matrix' in adata_result.obsp.keys(): + del adata_result.obsp['pearson_transition_matrix'] + if 'velocity_umap' in adata_result.obsm.keys(): + del adata_result.obsm['velocity_umap'] + cell_velocities(adata_result, vkey='velo_s',#layer='Ms', + X=adata_result[:,transition_genes].layers['Ms'], + V=adata_result[:,transition_genes].layers['velo_s'], + transition_genes=adata_result.var.loc[adata_result.var['velo_s_norm_genes']==True].index.tolist(), + method=method, + other_kernels_dict=other_kernels_dict, + **velo_arg + ) + return adata_result + + diff --git a/dynamo/multivelo/__init__.py b/dynamo/multivelo/__init__.py new file mode 100644 index 00000000..3cc49c2d --- /dev/null +++ b/dynamo/multivelo/__init__.py @@ -0,0 +1,10 @@ +from .ATACseqTools import * +from .ChromatinVelocity import * +from .MultiConfiguration import * +from .MultiIO import * +from .MultiQC import * +from .old_MultiomicVectorField import * +from .MultiPreprocessor import * +from .old_MultiVelocity import * +from .pyWNN import * +from .MultiVelo import * diff --git a/dynamo/multivelo/dynamical_chrom_func.py b/dynamo/multivelo/dynamical_chrom_func.py new file mode 100644 index 00000000..3adc096f --- /dev/null +++ b/dynamo/multivelo/dynamical_chrom_func.py @@ -0,0 +1,6386 @@ +from dynamo.multivelo import settings + +import os +import sys +import numpy as np +from numpy.linalg import norm +import matplotlib.pyplot as plt +from scipy import sparse +from scipy.sparse import coo_matrix +from scipy.optimize import minimize +from scipy.spatial import KDTree +from sklearn.metrics import pairwise_distances +from sklearn.mixture import GaussianMixture + +import scvelo as scv +import pandas as pd +import seaborn as sns +from numba import njit +import numba +from numba.typed import List +from tqdm.auto import tqdm + +import math +import torch +from torch import nn + +current_path = os.path.dirname(__file__) +src_path = os.path.join(current_path, "..") +sys.path.append(src_path) + +from ..dynamo_logger import ( + LoggerManager, + main_exception, + main_info, +) + + + +# a funciton to check for invalid values of different parameters +def check_params(alpha_c, + alpha, + beta, + gamma, + c0=None, + u0=None, + s0=None): + + new_alpha_c = alpha_c + new_alpha = alpha + new_beta = beta + new_gamma = gamma + + new_c0 = c0 + new_u0 = u0 + new_s0 = s0 + + inf_fix = 1e10 + zero_fix = 1e-10 + + # check if any of our parameters are infinite + if c0 is not None and math.isinf(c0): + main_info("c0 is infinite.", indent_level=1) + new_c0 = inf_fix + if u0 is not None and math.isinf(u0): + main_info("u0 is infinite.", indent_level=1) + new_u0 = inf_fix + if s0 is not None and math.isinf(s0): + main_info("s0 is infinite.", indent_level=1) + new_s0 = inf_fix + if math.isinf(alpha_c): + new_alpha_c = inf_fix + main_info("alpha_c is infinite.", indent_level=1) + if math.isinf(alpha): + new_alpha = inf_fix + main_info("alpha is infinite.", indent_level=1) + if math.isinf(beta): + new_beta = inf_fix + main_info("beta is infinite.", indent_level=1) + if math.isinf(gamma): + new_gamma = inf_fix + main_info("gamma is infinite.", indent_level=1) + + # check if any of our parameters are nan + if c0 is not None and math.isnan(c0): + main_info("c0 is Nan.", indent_level=1) + new_c0 = zero_fix + if u0 is not None and math.isnan(u0): + main_info("u0 is Nan.", indent_level=1) + new_u0 = zero_fix + if s0 is not None and math.isnan(s0): + main_info("s0 is Nan.", indent_level=1) + new_s0 = zero_fix + if math.isnan(alpha_c): + new_alpha_c = zero_fix + main_info("alpha_c is Nan.", indent_level=1) + if math.isnan(alpha): + new_alpha = zero_fix + main_info("alpha is Nan.", indent_level=1) + if math.isnan(beta): + new_beta = zero_fix + main_info("beta is Nan.", indent_level=1) + if math.isnan(gamma): + new_gamma = zero_fix + main_info("gamma is Nan.", indent_level=1) + + # check if any of our rate parameters are 0 + if alpha_c < 1e-7: + new_alpha_c = zero_fix + main_info("alpha_c is zero.", indent_level=1) + if alpha < 1e-7: + new_alpha = zero_fix + main_info("alpha is zero.", indent_level=1) + if beta < 1e-7: + new_beta = zero_fix + main_info("beta is zero.", indent_level=1) + if gamma < 1e-7: + new_gamma = zero_fix + main_info("gamma is zero.", indent_level=1) + + if beta == alpha_c: + new_beta += zero_fix + main_info("alpha_c and beta are equal, leading to divide by zero", + indent_level=1) + if beta == gamma: + new_gamma += zero_fix + main_info("gamma and beta are equal, leading to divide by zero", + indent_level=1) + if alpha_c == gamma: + new_gamma += zero_fix + main_info("gamma and alpha_c are equal, leading to divide by zero", + indent_level=1) + + if c0 is not None and u0 is not None and s0 is not None: + return new_alpha_c, new_alpha, new_beta, new_gamma, new_c0, new_u0, \ + new_s0 + + return new_alpha_c, new_alpha, new_beta, new_gamma + + +@njit( + locals={ + "res": numba.types.float64[:, ::1], + "eat": numba.types.float64[::1], + "ebt": numba.types.float64[::1], + "egt": numba.types.float64[::1], + }, + fastmath=True) +def predict_exp(tau, + c0, + u0, + s0, + alpha_c, + alpha, + beta, + gamma, + scale_cc=1, + pred_r=True, + chrom_open=True, + backward=False, + rna_only=False): + + if len(tau) == 0: + return np.empty((0, 3)) + if backward: + tau = -tau + res = np.empty((len(tau), 3)) + eat = np.exp(-alpha_c * tau) + ebt = np.exp(-beta * tau) + egt = np.exp(-gamma * tau) + if rna_only: + kc = 1 + c0 = 1 + else: + if chrom_open: + kc = 1 + else: + kc = 0 + alpha_c *= scale_cc + + const = (kc - c0) * alpha / (beta - alpha_c) + + res[:, 0] = kc - (kc - c0) * eat + + if pred_r: + + res[:, 1] = u0 * ebt + (alpha * kc / beta) * (1 - ebt) + res[:, 1] += const * (ebt - eat) + + res[:, 2] = s0 * egt + (alpha * kc / gamma) * (1 - egt) + res[:, 2] += ((beta / (gamma - beta)) * + ((alpha * kc / beta) - u0 - const) * (egt - ebt)) + res[:, 2] += (beta / (gamma - alpha_c)) * const * (egt - eat) + + else: + res[:, 1] = np.zeros(len(tau)) + res[:, 2] = np.zeros(len(tau)) + return res + + +@njit(locals={ + "exp_sw1": numba.types.float64[:, ::1], + "exp_sw2": numba.types.float64[:, ::1], + "exp_sw3": numba.types.float64[:, ::1], + "exp1": numba.types.float64[:, ::1], + "exp2": numba.types.float64[:, ::1], + "exp3": numba.types.float64[:, ::1], + "exp4": numba.types.float64[:, ::1], + "tau_sw1": numba.types.float64[::1], + "tau_sw2": numba.types.float64[::1], + "tau_sw3": numba.types.float64[::1], + "tau1": numba.types.float64[::1], + "tau2": numba.types.float64[::1], + "tau3": numba.types.float64[::1], + "tau4": numba.types.float64[::1] + }, + fastmath=True) +def generate_exp(tau_list, + t_sw_array, + alpha_c, + alpha, + beta, + gamma, + scale_cc=1, + model=1, + rna_only=False): + + if beta == alpha_c: + beta += 1e-3 + if gamma == beta or gamma == alpha_c: + gamma += 1e-3 + switch = len(t_sw_array) + if switch >= 1: + tau_sw1 = np.array([t_sw_array[0]]) + if switch >= 2: + tau_sw2 = np.array([t_sw_array[1] - t_sw_array[0]]) + if switch == 3: + tau_sw3 = np.array([t_sw_array[2] - t_sw_array[1]]) + exp_sw1, exp_sw2, exp_sw3 = (np.empty((0, 3)), + np.empty((0, 3)), + np.empty((0, 3))) + if tau_list is None: + if model == 0: + if switch >= 1: + exp_sw1 = predict_exp(tau_sw1, 0, 0, 0, alpha_c, alpha, beta, + gamma, pred_r=False, scale_cc=scale_cc, + rna_only=rna_only) + if switch >= 2: + exp_sw2 = predict_exp(tau_sw2, exp_sw1[0, 0], + exp_sw1[0, 1], exp_sw1[0, 2], + alpha_c, alpha, beta, gamma, + pred_r=False, chrom_open=False, + scale_cc=scale_cc, rna_only=rna_only) + if switch >= 3: + exp_sw3 = predict_exp(tau_sw3, exp_sw2[0, 0], + exp_sw2[0, 1], exp_sw2[0, 2], + alpha_c, alpha, beta, gamma, + chrom_open=False, + scale_cc=scale_cc, + rna_only=rna_only) + elif model == 1: + if switch >= 1: + exp_sw1 = predict_exp(tau_sw1, 0, 0, 0, alpha_c, alpha, beta, + gamma, pred_r=False, scale_cc=scale_cc, + rna_only=rna_only) + if switch >= 2: + exp_sw2 = predict_exp(tau_sw2, exp_sw1[0, 0], + exp_sw1[0, 1], exp_sw1[0, 2], + alpha_c, alpha, beta, gamma, + scale_cc=scale_cc, rna_only=rna_only) + if switch >= 3: + exp_sw3 = predict_exp(tau_sw3, exp_sw2[0, 0], + exp_sw2[0, 1], exp_sw2[0, 2], + alpha_c, alpha, beta, gamma, + chrom_open=False, + scale_cc=scale_cc, + rna_only=rna_only) + elif model == 2: + if switch >= 1: + exp_sw1 = predict_exp(tau_sw1, 0, 0, 0, alpha_c, alpha, beta, + gamma, pred_r=False, scale_cc=scale_cc, + rna_only=rna_only) + if switch >= 2: + exp_sw2 = predict_exp(tau_sw2, exp_sw1[0, 0], + exp_sw1[0, 1], exp_sw1[0, 2], + alpha_c, alpha, beta, gamma, + scale_cc=scale_cc, rna_only=rna_only) + if switch >= 3: + exp_sw3 = predict_exp(tau_sw3, exp_sw2[0, 0], + exp_sw2[0, 1], exp_sw2[0, 2], + alpha_c, 0, beta, gamma, + scale_cc=scale_cc, + rna_only=rna_only) + + return (np.empty((0, 3)), np.empty((0, 3)), np.empty((0, 3)), + np.empty((0, 3))), (exp_sw1, exp_sw2, exp_sw3) + + tau1 = tau_list[0] + if switch >= 1: + tau2 = tau_list[1] + if switch >= 2: + tau3 = tau_list[2] + if switch == 3: + tau4 = tau_list[3] + exp1, exp2, exp3, exp4 = (np.empty((0, 3)), np.empty((0, 3)), + np.empty((0, 3)), np.empty((0, 3))) + if model == 0: + exp1 = predict_exp(tau1, 0, 0, 0, alpha_c, alpha, beta, gamma, + pred_r=False, scale_cc=scale_cc, rna_only=rna_only) + if switch >= 1: + exp_sw1 = predict_exp(tau_sw1, 0, 0, 0, alpha_c, alpha, beta, + gamma, pred_r=False, scale_cc=scale_cc, + rna_only=rna_only) + exp2 = predict_exp(tau2, exp_sw1[0, 0], exp_sw1[0, 1], + exp_sw1[0, 2], alpha_c, alpha, beta, gamma, + pred_r=False, chrom_open=False, + scale_cc=scale_cc, rna_only=rna_only) + if switch >= 2: + exp_sw2 = predict_exp(tau_sw2, exp_sw1[0, 0], exp_sw1[0, 1], + exp_sw1[0, 2], alpha_c, alpha, beta, + gamma, pred_r=False, chrom_open=False, + scale_cc=scale_cc, rna_only=rna_only) + exp3 = predict_exp(tau3, exp_sw2[0, 0], exp_sw2[0, 1], + exp_sw2[0, 2], alpha_c, alpha, beta, gamma, + chrom_open=False, scale_cc=scale_cc, + rna_only=rna_only) + if switch == 3: + exp_sw3 = predict_exp(tau_sw3, exp_sw2[0, 0], + exp_sw2[0, 1], exp_sw2[0, 2], + alpha_c, alpha, beta, gamma, + chrom_open=False, scale_cc=scale_cc, + rna_only=rna_only) + exp4 = predict_exp(tau4, exp_sw3[0, 0], exp_sw3[0, 1], + exp_sw3[0, 2], alpha_c, 0, beta, gamma, + chrom_open=False, scale_cc=scale_cc, + rna_only=rna_only) + elif model == 1: + exp1 = predict_exp(tau1, 0, 0, 0, alpha_c, alpha, beta, gamma, + pred_r=False, scale_cc=scale_cc, rna_only=rna_only) + if switch >= 1: + exp_sw1 = predict_exp(tau_sw1, 0, 0, 0, alpha_c, alpha, beta, + gamma, pred_r=False, scale_cc=scale_cc, + rna_only=rna_only) + exp2 = predict_exp(tau2, exp_sw1[0, 0], exp_sw1[0, 1], + exp_sw1[0, 2], alpha_c, alpha, beta, gamma, + scale_cc=scale_cc, rna_only=rna_only) + if switch >= 2: + exp_sw2 = predict_exp(tau_sw2, exp_sw1[0, 0], exp_sw1[0, 1], + exp_sw1[0, 2], alpha_c, alpha, beta, + gamma, scale_cc=scale_cc, + rna_only=rna_only) + exp3 = predict_exp(tau3, exp_sw2[0, 0], exp_sw2[0, 1], + exp_sw2[0, 2], alpha_c, alpha, beta, gamma, + chrom_open=False, scale_cc=scale_cc, + rna_only=rna_only) + if switch == 3: + exp_sw3 = predict_exp(tau_sw3, exp_sw2[0, 0], + exp_sw2[0, 1], exp_sw2[0, 2], + alpha_c, alpha, beta, gamma, + chrom_open=False, scale_cc=scale_cc, + rna_only=rna_only) + exp4 = predict_exp(tau4, exp_sw3[0, 0], exp_sw3[0, 1], + exp_sw3[0, 2], alpha_c, 0, beta, gamma, + chrom_open=False, scale_cc=scale_cc, + rna_only=rna_only) + elif model == 2: + exp1 = predict_exp(tau1, 0, 0, 0, alpha_c, alpha, beta, gamma, + pred_r=False, scale_cc=scale_cc, rna_only=rna_only) + if switch >= 1: + exp_sw1 = predict_exp(tau_sw1, 0, 0, 0, alpha_c, alpha, beta, + gamma, pred_r=False, scale_cc=scale_cc, + rna_only=rna_only) + exp2 = predict_exp(tau2, exp_sw1[0, 0], exp_sw1[0, 1], + exp_sw1[0, 2], alpha_c, alpha, beta, gamma, + scale_cc=scale_cc, rna_only=rna_only) + if switch >= 2: + exp_sw2 = predict_exp(tau_sw2, exp_sw1[0, 0], exp_sw1[0, 1], + exp_sw1[0, 2], alpha_c, alpha, beta, + gamma, scale_cc=scale_cc, + rna_only=rna_only) + exp3 = predict_exp(tau3, exp_sw2[0, 0], exp_sw2[0, 1], + exp_sw2[0, 2], alpha_c, 0, beta, gamma, + scale_cc=scale_cc, rna_only=rna_only) + if switch == 3: + exp_sw3 = predict_exp(tau_sw3, exp_sw2[0, 0], + exp_sw2[0, 1], exp_sw2[0, 2], + alpha_c, 0, beta, gamma, + scale_cc=scale_cc, rna_only=rna_only) + exp4 = predict_exp(tau4, exp_sw3[0, 0], exp_sw3[0, 1], + exp_sw3[0, 2], alpha_c, 0, beta, gamma, + chrom_open=False, scale_cc=scale_cc, + rna_only=rna_only) + return (exp1, exp2, exp3, exp4), (exp_sw1, exp_sw2, exp_sw3) + + +@njit(locals={ + "exp_sw1": numba.types.float64[:, ::1], + "exp_sw2": numba.types.float64[:, ::1], + "exp_sw3": numba.types.float64[:, ::1], + "exp1": numba.types.float64[:, ::1], + "exp2": numba.types.float64[:, ::1], + "exp3": numba.types.float64[:, ::1], + "exp4": numba.types.float64[:, ::1], + "tau_sw1": numba.types.float64[::1], + "tau_sw2": numba.types.float64[::1], + "tau_sw3": numba.types.float64[::1], + "tau1": numba.types.float64[::1], + "tau2": numba.types.float64[::1], + "tau3": numba.types.float64[::1], + "tau4": numba.types.float64[::1] + }, + fastmath=True) +def generate_exp_backward(tau_list, t_sw_array, alpha_c, alpha, beta, gamma, + scale_cc=1, model=1): + if beta == alpha_c: + beta += 1e-3 + if gamma == beta or gamma == alpha_c: + gamma += 1e-3 + switch = len(t_sw_array) + if switch >= 1: + tau_sw1 = np.array([t_sw_array[0]]) + if switch >= 2: + tau_sw2 = np.array([t_sw_array[1] - t_sw_array[0]]) + if t is None: + if model == 0: + exp_sw1 = predict_exp(tau_sw1, 1e-3, 1e-3, 1e-3, alpha_c, 0, beta, + gamma, scale_cc=scale_cc, chrom_open=False, + backward=True) + exp_sw2 = predict_exp(tau_sw2, exp_sw1[0, 0], exp_sw1[0, 1], + exp_sw1[0, 2], alpha_c, alpha, beta, gamma, + scale_cc=scale_cc, chrom_open=False, + backward=True) + elif model == 1: + exp_sw1 = predict_exp(tau_sw1, 1e-3, 1e-3, 1e-3, alpha_c, 0, beta, + gamma, scale_cc=scale_cc, chrom_open=False, + backward=True) + exp_sw2 = predict_exp(tau_sw2, exp_sw1[0, 0], exp_sw1[0, 1], + exp_sw1[0, 2], alpha_c, alpha, beta, gamma, + scale_cc=scale_cc, chrom_open=False, + backward=True) + elif model == 2: + exp_sw1 = predict_exp(tau_sw1, 1e-3, 1e-3, 1e-3, alpha_c, 0, beta, + gamma, scale_cc=scale_cc, chrom_open=False, + backward=True) + exp_sw2 = predict_exp(tau_sw2, exp_sw1[0, 0], exp_sw1[0, 1], + exp_sw1[0, 2], alpha_c, 0, beta, gamma, + scale_cc=scale_cc, backward=True) + return (np.empty((0, 0)), + np.empty((0, 0)), + np.empty((0, 0))), (exp_sw1, exp_sw2) + + tau1 = tau_list[0] + if switch >= 1: + tau2 = tau_list[1] + if switch >= 2: + tau3 = tau_list[2] + + exp1, exp2, exp3 = np.empty((0, 3)), np.empty((0, 3)), np.empty((0, 3)) + if model == 0: + exp1 = predict_exp(tau1, 1e-3, 1e-3, 1e-3, alpha_c, 0, beta, gamma, + scale_cc=scale_cc, chrom_open=False, backward=True) + if switch >= 1: + exp_sw1 = predict_exp(tau_sw1, 1e-3, 1e-3, 1e-3, alpha_c, 0, beta, + gamma, scale_cc=scale_cc, chrom_open=False, + backward=True) + exp2 = predict_exp(tau2, exp_sw1[0, 0], exp_sw1[0, 1], + exp_sw1[0, 2], alpha_c, alpha, beta, gamma, + scale_cc=scale_cc, chrom_open=False, + backward=True) + if switch >= 2: + exp_sw2 = predict_exp(tau_sw2, exp_sw1[0, 0], exp_sw1[0, 1], + exp_sw1[0, 2], alpha_c, alpha, beta, + gamma, scale_cc=scale_cc, + chrom_open=False, backward=True) + exp3 = predict_exp(tau_sw2, exp_sw1[0, 0], exp_sw1[0, 1], + exp_sw1[0, 2], alpha_c, alpha, beta, gamma, + scale_cc=scale_cc, chrom_open=False, + backward=True) + elif model == 1: + exp1 = predict_exp(tau1, 1e-3, 1e-3, 1e-3, alpha_c, 0, beta, gamma, + scale_cc=scale_cc, chrom_open=False, backward=True) + if switch >= 1: + exp_sw1 = predict_exp(tau_sw1, 1e-3, 1e-3, 1e-3, alpha_c, 0, beta, + gamma, scale_cc=scale_cc, chrom_open=False, + backward=True) + exp2 = predict_exp(tau2, exp_sw1[0, 0], exp_sw1[0, 1], + exp_sw1[0, 2], alpha_c, alpha, beta, gamma, + scale_cc=scale_cc, chrom_open=False, + backward=True) + if switch >= 2: + exp_sw2 = predict_exp(tau_sw2, exp_sw1[0, 0], exp_sw1[0, 1], + exp_sw1[0, 2], alpha_c, alpha, beta, + gamma, scale_cc=scale_cc, + chrom_open=False, backward=True) + exp3 = predict_exp(tau3, exp_sw2[0, 0], exp_sw2[0, 1], + exp_sw2[0, 2], alpha_c, alpha, beta, gamma, + scale_cc=scale_cc, backward=True) + elif model == 2: + exp1 = predict_exp(tau1, 1e-3, 1e-3, 1e-3, alpha_c, 0, beta, gamma, + scale_cc=scale_cc, chrom_open=False, backward=True) + if switch >= 1: + exp_sw1 = predict_exp(tau_sw1, 1e-3, 1e-3, 1e-3, alpha_c, alpha, + beta, gamma, scale_cc=scale_cc, + chrom_open=False, backward=True) + exp2 = predict_exp(tau2, exp_sw1[0, 0], exp_sw1[0, 1], + exp_sw1[0, 2], alpha_c, 0, beta, gamma, + scale_cc=scale_cc, backward=True) + if switch >= 2: + exp_sw2 = predict_exp(tau_sw2, exp_sw1[0, 0], exp_sw1[0, 1], + exp_sw1[0, 2], alpha_c, 0, beta, gamma, + scale_cc=scale_cc, backward=True) + exp3 = predict_exp(tau3, exp_sw2[0, 0], exp_sw2[0, 1], + exp_sw2[0, 2], alpha_c, alpha, beta, gamma, + scale_cc=scale_cc, backward=True) + return (exp1, exp2, exp3), (exp_sw1, exp_sw2) + + +@njit(locals={ + "res": numba.types.float64[:, ::1], + }, + fastmath=True) +def ss_exp(alpha_c, alpha, beta, gamma, pred_r=True, chrom_open=True): + res = np.empty((1, 3)) + if not chrom_open: + res[0, 0] = 0 + res[0, 1] = 0 + res[0, 2] = 0 + else: + res[0, 0] = 1 + if pred_r: + res[0, 1] = alpha / beta + res[0, 2] = alpha / gamma + else: + res[0, 1] = 0 + res[0, 2] = 0 + return res + + +@njit(locals={ + "ss1": numba.types.float64[:, ::1], + "ss2": numba.types.float64[:, ::1], + "ss3": numba.types.float64[:, ::1], + "ss4": numba.types.float64[:, ::1] + }, + fastmath=True) +def compute_ss_exp(alpha_c, alpha, beta, gamma, model=0): + if model == 0: + ss1 = ss_exp(alpha_c, alpha, beta, gamma, pred_r=False) + ss2 = ss_exp(alpha_c, alpha, beta, gamma, pred_r=False, + chrom_open=False) + ss3 = ss_exp(alpha_c, alpha, beta, gamma, chrom_open=False) + ss4 = ss_exp(alpha_c, 0, beta, gamma, chrom_open=False) + elif model == 1: + ss1 = ss_exp(alpha_c, alpha, beta, gamma, pred_r=False) + ss2 = ss_exp(alpha_c, alpha, beta, gamma) + ss3 = ss_exp(alpha_c, alpha, beta, gamma, chrom_open=False) + ss4 = ss_exp(alpha_c, 0, beta, gamma, chrom_open=False) + elif model == 2: + ss1 = ss_exp(alpha_c, alpha, beta, gamma, pred_r=False) + ss2 = ss_exp(alpha_c, alpha, beta, gamma) + ss3 = ss_exp(alpha_c, 0, beta, gamma) + ss4 = ss_exp(alpha_c, 0, beta, gamma, chrom_open=False) + return np.vstack((ss1, ss2, ss3, ss4)) + + +@njit(fastmath=True) +def velocity_equations(c, u, s, alpha_c, alpha, beta, gamma, scale_cc=1, + pred_r=True, chrom_open=True, rna_only=False): + if rna_only: + c = np.full(len(u), 1.0) + if not chrom_open: + alpha_c *= scale_cc + if pred_r: + return -alpha_c * c, alpha * c - beta * u, beta * u - gamma * s + else: + return -alpha_c * c, np.zeros(len(u)), np.zeros(len(u)) + else: + if pred_r: + return (alpha_c - alpha_c * c), (alpha * c - beta * u), (beta * u + - gamma + * s) + else: + return alpha_c - alpha_c * c, np.zeros(len(u)), np.zeros(len(u)) + + +@njit(locals={ + "state0": numba.types.boolean[::1], + "state1": numba.types.boolean[::1], + "state2": numba.types.boolean[::1], + "state3": numba.types.boolean[::1], + "tau1": numba.types.float64[::1], + "tau2": numba.types.float64[::1], + "tau3": numba.types.float64[::1], + "tau4": numba.types.float64[::1], + "exp_list": numba.types.Tuple((numba.types.float64[:, ::1], + numba.types.float64[:, ::1], + numba.types.float64[:, ::1], + numba.types.float64[:, ::1])), + "exp_sw_list": numba.types.Tuple((numba.types.float64[:, ::1], + numba.types.float64[:, ::1], + numba.types.float64[:, ::1])), + "c": numba.types.float64[::1], + "u": numba.types.float64[::1], + "s": numba.types.float64[::1], + "vc_vec": numba.types.float64[::1], + "vu_vec": numba.types.float64[::1], + "vs_vec": numba.types.float64[::1] + }, + fastmath=True) +def compute_velocity(t, + t_sw_array, + state, + alpha_c, + alpha, + beta, + gamma, + rescale_c, + rescale_u, + scale_cc=1, + model=1, + total_h=20, + rna_only=False): + + if state is None: + state0 = t <= t_sw_array[0] + state1 = (t_sw_array[0] < t) & (t <= t_sw_array[1]) + state2 = (t_sw_array[1] < t) & (t <= t_sw_array[2]) + state3 = t_sw_array[2] < t + else: + state0 = np.equal(state, 0) + state1 = np.equal(state, 1) + state2 = np.equal(state, 2) + state3 = np.equal(state, 3) + + tau1 = t[state0] + tau2 = t[state1] - t_sw_array[0] + tau3 = t[state2] - t_sw_array[1] + tau4 = t[state3] - t_sw_array[2] + tau_list = [tau1, tau2, tau3, tau4] + switch = np.sum(t_sw_array < total_h) + typed_tau_list = List() + [typed_tau_list.append(x) for x in tau_list] + exp_list, exp_sw_list = generate_exp(typed_tau_list, + t_sw_array[:switch], + alpha_c, + alpha, + beta, + gamma, + model=model, + scale_cc=scale_cc, + rna_only=rna_only) + + c = np.empty(len(t)) + u = np.empty(len(t)) + s = np.empty(len(t)) + for i, ii in enumerate([state0, state1, state2, state3]): + if np.any(ii): + c[ii] = exp_list[i][:, 0] + u[ii] = exp_list[i][:, 1] + s[ii] = exp_list[i][:, 2] + + vc_vec = np.zeros(len(u)) + vu_vec = np.zeros(len(u)) + vs_vec = np.zeros(len(u)) + + if model == 0: + if np.any(state0): + vc_vec[state0], vu_vec[state0], vs_vec[state0] = \ + velocity_equations(c[state0], u[state0], s[state0], alpha_c, + alpha, beta, gamma, pred_r=False, + scale_cc=scale_cc, rna_only=rna_only) + if np.any(state1): + vc_vec[state1], vu_vec[state1], vs_vec[state1] = \ + velocity_equations(c[state1], u[state1], s[state1], alpha_c, + alpha, beta, gamma, pred_r=False, + chrom_open=False, scale_cc=scale_cc, + rna_only=rna_only) + if np.any(state2): + vc_vec[state2], vu_vec[state2], vs_vec[state2] = \ + velocity_equations(c[state2], u[state2], s[state2], alpha_c, + alpha, beta, gamma, chrom_open=False, + scale_cc=scale_cc, rna_only=rna_only) + if np.any(state3): + vc_vec[state3], vu_vec[state3], vs_vec[state3] = \ + velocity_equations(c[state3], u[state3], s[state3], alpha_c, 0, + beta, gamma, chrom_open=False, + scale_cc=scale_cc, rna_only=rna_only) + elif model == 1: + if np.any(state0): + vc_vec[state0], vu_vec[state0], vs_vec[state0] = \ + velocity_equations(c[state0], u[state0], s[state0], alpha_c, + alpha, beta, gamma, pred_r=False, + scale_cc=scale_cc, rna_only=rna_only) + if np.any(state1): + vc_vec[state1], vu_vec[state1], vs_vec[state1] = \ + velocity_equations(c[state1], u[state1], s[state1], alpha_c, + alpha, beta, gamma, scale_cc=scale_cc, + rna_only=rna_only) + if np.any(state2): + vc_vec[state2], vu_vec[state2], vs_vec[state2] = \ + velocity_equations(c[state2], u[state2], s[state2], alpha_c, + alpha, beta, gamma, chrom_open=False, + scale_cc=scale_cc, rna_only=rna_only) + if np.any(state3): + vc_vec[state3], vu_vec[state3], vs_vec[state3] = \ + velocity_equations(c[state3], u[state3], s[state3], alpha_c, 0, + beta, gamma, chrom_open=False, + scale_cc=scale_cc, rna_only=rna_only) + elif model == 2: + if np.any(state0): + vc_vec[state0], vu_vec[state0], vs_vec[state0] = \ + velocity_equations(c[state0], u[state0], s[state0], alpha_c, + alpha, beta, gamma, pred_r=False, + scale_cc=scale_cc, rna_only=rna_only) + if np.any(state1): + vc_vec[state1], vu_vec[state1], vs_vec[state1] = \ + velocity_equations(c[state1], u[state1], s[state1], alpha_c, + alpha, beta, gamma, scale_cc=scale_cc, + rna_only=rna_only) + if np.any(state2): + vc_vec[state2], vu_vec[state2], vs_vec[state2] = \ + velocity_equations(c[state2], u[state2], s[state2], alpha_c, + 0, beta, gamma, scale_cc=scale_cc, + rna_only=rna_only) + if np.any(state3): + vc_vec[state3], vu_vec[state3], vs_vec[state3] = \ + velocity_equations(c[state3], u[state3], s[state3], alpha_c, 0, + beta, gamma, chrom_open=False, + scale_cc=scale_cc, rna_only=rna_only) + return vc_vec * rescale_c, vu_vec * rescale_u, vs_vec + + +def log_valid(x): + return np.log(np.clip(x, 1e-3, 1 - 1e-3)) + + +def approx_tau(u, s, u0, s0, alpha, beta, gamma): + if gamma == beta: + gamma -= 1e-3 + u_inf = alpha / beta + if beta > gamma: + b_new = beta / (gamma - beta) + s_inf = alpha / gamma + s_inf_new = s_inf - b_new * u_inf + s_new = s - b_new * u + s0_new = s0 - b_new * u0 + tau = -1.0 / gamma * log_valid((s_new - s_inf_new) / + (s0_new - s_inf_new)) + else: + tau = -1.0 / beta * log_valid((u - u_inf) / (u0 - u_inf)) + return tau + + +def anchor_points(t_sw_array, total_h=20, t=1000, mode='uniform', + return_time=False): + t_ = np.linspace(0, total_h, t) + tau1 = t_[t_ <= t_sw_array[0]] + tau2 = t_[(t_sw_array[0] < t_) & (t_ <= t_sw_array[1])] - t_sw_array[0] + tau3 = t_[(t_sw_array[1] < t_) & (t_ <= t_sw_array[2])] - t_sw_array[1] + tau4 = t_[t_sw_array[2] < t_] - t_sw_array[2] + + if mode == 'log': + if len(tau1) > 0: + tau1 = np.expm1(tau1) + tau1 = tau1 / np.max(tau1) * (t_sw_array[0]) + if len(tau2) > 0: + tau2 = np.expm1(tau2) + tau2 = tau2 / np.max(tau2) * (t_sw_array[1] - t_sw_array[0]) + if len(tau3) > 0: + tau3 = np.expm1(tau3) + tau3 = tau3 / np.max(tau3) * (t_sw_array[2] - t_sw_array[1]) + if len(tau4) > 0: + tau4 = np.expm1(tau4) + tau4 = tau4 / np.max(tau4) * (total_h - t_sw_array[2]) + + tau_list = [tau1, tau2, tau3, tau4] + if return_time: + return t_, tau_list + else: + return tau_list + + +# @jit(nopython=True, fastmath=True, debug=True) +def pairwise_distance_square(X, Y): + res = np.empty((X.shape[0], Y.shape[0]), dtype=X.dtype) + for a in range(X.shape[0]): + for b in range(Y.shape[0]): + val = 0.0 + for i in range(X.shape[1]): + tmp = X[a, i] - Y[b, i] + val += tmp**2 + res[a, b] = val + return res + + +def calculate_dist_and_time(c, u, s, + t_sw_array, + alpha_c, alpha, beta, gamma, + rescale_c, rescale_u, + scale_cc=1, + scale_factor=None, + model=1, + conn=None, + t=1000, k=1, + direction='complete', + total_h=20, + rna_only=False, + penalize_gap=True, + all_cells=True): + + n = len(u) + if scale_factor is None: + scale_factor = np.array([np.std(c), np.std(u), np.std(s)]) + tau_list = anchor_points(t_sw_array, total_h, t) + switch = np.sum(t_sw_array < total_h) + typed_tau_list = List() + [typed_tau_list.append(x) for x in tau_list] + alpha_c, alpha, beta, gamma = check_params(alpha_c, alpha, beta, gamma) + exp_list, exp_sw_list = generate_exp(typed_tau_list, + t_sw_array[:switch], + alpha_c, + alpha, + beta, + gamma, + model=model, + scale_cc=scale_cc, + rna_only=rna_only) + rescale_factor = np.array([rescale_c, rescale_u, 1.0]) + exp_list = [x*rescale_factor for x in exp_list] + exp_sw_list = [x*rescale_factor for x in exp_sw_list] + max_c = 0 + max_u = 0 + max_s = 0 + if rna_only: + exp_mat = (np.hstack((np.reshape(u, (-1, 1)), np.reshape(s, (-1, 1)))) + / scale_factor[1:]) + else: + exp_mat = np.hstack((np.reshape(c, (-1, 1)), np.reshape(u, (-1, 1)), + np.reshape(s, (-1, 1)))) / scale_factor + + dists = np.full((n, 4), np.inf) + taus = np.zeros((n, 4), dtype=u.dtype) + ts = np.zeros((n, 4), dtype=u.dtype) + anchor_exp, anchor_t = None, None + + for i in range(switch+1): + if not all_cells: + max_ci = (np.max(exp_list[i][:, 0]) if exp_list[i].shape[0] > 0 + else 0) + max_c = max_ci if max_ci > max_c else max_c + max_ui = np.max(exp_list[i][:, 1]) if exp_list[i].shape[0] > 0 else 0 + max_u = max_ui if max_ui > max_u else max_u + max_si = np.max(exp_list[i][:, 2]) if exp_list[i].shape[0] > 0 else 0 + max_s = max_si if max_si > max_s else max_s + + skip_phase = False + if direction == 'off': + if (model in [1, 2]) and (i < 2): + skip_phase = True + elif direction == 'on': + if (model in [1, 2]) and (i >= 2): + skip_phase = True + if rna_only and i == 0: + skip_phase = True + + if not skip_phase: + if rna_only: + tmp = exp_list[i][:, 1:] / scale_factor[1:] + else: + tmp = exp_list[i] / scale_factor + if anchor_exp is None: + anchor_exp = exp_list[i] + anchor_t = (tau_list[i] + t_sw_array[i-1] if i >= 1 + else tau_list[i]) + else: + anchor_exp = np.vstack((anchor_exp, exp_list[i])) + anchor_t = np.hstack((anchor_t, tau_list[i] + t_sw_array[i-1] + if i >= 1 else tau_list[i])) + + if not all_cells: + anchor_dist = np.diff(tmp, axis=0, prepend=np.zeros((1, 2)) + if rna_only else np.zeros((1, 3))) + anchor_dist = np.sqrt((anchor_dist**2).sum(axis=1)) + remove_cand = anchor_dist < (0.01*np.max(exp_mat[1]) + if rna_only + else 0.01*np.max(exp_mat[2])) + step_idx = np.arange(0, len(anchor_dist), 1) % 3 > 0 + remove_cand &= step_idx + keep_idx = np.where(~remove_cand)[0] + tmp = tmp[keep_idx, :] + + tree = KDTree(tmp) + dd, ii = tree.query(exp_mat, k=k) + dd = dd**2 + if k > 1: + dd = np.mean(dd, axis=1) + if conn is not None: + dd = conn.dot(dd) + dists[:, i] = dd + + if not all_cells: + ii = keep_idx[ii] + if k == 1: + taus[:, i] = tau_list[i][ii] + else: + for j in range(n): + taus[j, i] = tau_list[i][ii[j, :]] + ts[:, i] = taus[:, i] + t_sw_array[i-1] if i >= 1 else taus[:, i] + + min_dist = np.min(dists, axis=1) + state_pred = np.argmin(dists, axis=1) + t_pred = ts[np.arange(n), state_pred] + + anchor_t1_list = [] + anchor_t2_list = [] + t_sw_adjust = np.zeros(3, dtype=u.dtype) + + if direction == 'complete': + t_sorted = np.sort(t_pred) + dt = np.diff(t_sorted, prepend=0) + gap_thresh = 3*np.percentile(dt, 99) + idx = np.where(dt > gap_thresh)[0] + for i in idx: + t1 = t_sorted[i-1] if i > 0 else 0 + t2 = t_sorted[i] + anchor_t1 = anchor_exp[np.argmin(np.abs(anchor_t - t1)), :] + anchor_t2 = anchor_exp[np.argmin(np.abs(anchor_t - t2)), :] + if all_cells: + anchor_t1_list.append(np.ravel(anchor_t1)) + anchor_t2_list.append(np.ravel(anchor_t2)) + if not all_cells: + for j in range(1, switch): + crit1 = ((t1 > t_sw_array[j-1]) and (t2 > t_sw_array[j-1]) + and (t1 <= t_sw_array[j]) + and (t2 <= t_sw_array[j])) + crit2 = ((np.abs(anchor_t1[2] - exp_sw_list[j][0, 2]) + < 0.02 * max_s) and + (np.abs(anchor_t2[2] - exp_sw_list[j][0, 2]) + < 0.01 * max_s)) + crit3 = ((np.abs(anchor_t1[1] - exp_sw_list[j][0, 1]) + < 0.02 * max_u) and + (np.abs(anchor_t2[1] - exp_sw_list[j][0, 1]) + < 0.01 * max_u)) + crit4 = ((np.abs(anchor_t1[0] - exp_sw_list[j][0, 0]) + < 0.02 * max_c) and + (np.abs(anchor_t2[0] - exp_sw_list[j][0, 0]) + < 0.01 * max_c)) + if crit1 and crit2 and crit3 and crit4: + t_sw_adjust[j] += t2 - t1 + if penalize_gap: + dist_gap = np.sum(((anchor_t1[1:] - anchor_t2[1:]) / + scale_factor[1:])**2) + idx_to_adjust = t_pred >= t2 + t_sw_array_ = np.append(t_sw_array, total_h) + state_to_adjust = np.where(t_sw_array_ > t2)[0] + dists[np.ix_(idx_to_adjust, state_to_adjust)] += dist_gap + min_dist = np.min(dists, axis=1) + state_pred = np.argmin(dists, axis=1) + if all_cells: + t_pred = ts[np.arange(n), state_pred] + + if all_cells: + exp_ss_mat = compute_ss_exp(alpha_c, alpha, beta, gamma, model=model) + if rna_only: + exp_ss_mat[:, 0] = 1 + dists_ss = pairwise_distance_square(exp_mat, exp_ss_mat * + rescale_factor / scale_factor) + + reach_ss = np.full((n, 4), False) + for i in range(n): + for j in range(4): + if min_dist[i] > dists_ss[i, j]: + reach_ss[i, j] = True + late_phase = np.full(n, -1) + for i in range(3): + late_phase[np.abs(t_pred - t_sw_array[i]) < 0.1] = i + return min_dist, t_pred, state_pred, reach_ss, late_phase, max_u, \ + max_s, anchor_t1_list, anchor_t2_list + else: + return min_dist, state_pred, max_u, max_s, t_sw_adjust + + +def t_of_c(alpha_c, k_c, c_o, c, rescale_factor, sw_t): + + coef = -float(1)/alpha_c + + c_val = np.clip(c / rescale_factor, a_min=0, a_max=1) + + in_log = (float(k_c) - c_val) / float((k_c) - (c_o)) + + epsilon = 1e-9 + + return_val = coef * np.log(in_log + epsilon) + + if k_c == 0: + return_val += sw_t + + return return_val + + +def make_X(c, u, s, + max_u, + max_s, + alpha_c, alpha, beta, gamma, + gene_sw_t, + c0, c_sw1, c_sw2, c_sw3, + u0, u_sw1, u_sw2, u_sw3, + s0, s_sw1, s_sw2, s_sw3, + model, direction, state): + + if direction == "complete": + dire = 0 + elif direction == "on": + dire = 1 + elif direction == "off": + dire = 2 + + n = c.shape[0] + + epsilon = 1e-5 + + if dire == 0: + x = np.concatenate((np.array([c, + np.log(u + epsilon), + np.log(s + epsilon)]), + np.full((n, 17), [np.log(alpha_c + epsilon), + np.log(alpha + epsilon), + np.log(beta + epsilon), + np.log(gamma + epsilon), + c_sw1, c_sw2, c_sw3, + np.log(u_sw2 + epsilon), + np.log(u_sw3 + epsilon), + np.log(s_sw2 + epsilon), + np.log(s_sw3 + epsilon), + np.log(max_u), + np.log(max_s), + gene_sw_t[0], + gene_sw_t[1], + gene_sw_t[2], + model]).T, + np.full((n, 1), state).T + )).T.astype(np.float32) + + elif dire == 1: + x = np.concatenate((np.array([c, + np.log(u + epsilon), + np.log(s + epsilon)]), + np.full((n, 12), [np.log(alpha_c + epsilon), + np.log(alpha + epsilon), + np.log(beta + epsilon), + np.log(gamma + epsilon), + c_sw1, c_sw2, + np.log(u_sw1 + epsilon), + np.log(u_sw2 + epsilon), + np.log(s_sw1 + epsilon), + np.log(s_sw2 + epsilon), + gene_sw_t[0], + model]).T, + np.full((n, 1), state).T + )).T.astype(np.float32) + + elif dire == 2: + if model == 1: + + max_u_t = -(float(1)/alpha_c)*np.log((max_u*beta) + / (alpha*c0[2])) + + x = np.concatenate((np.array([np.log(c + epsilon), + np.log(u + epsilon), + np.log(s + epsilon)]), + np.full((n, 14), [np.log(alpha_c + epsilon), + np.log(alpha + epsilon), + np.log(beta + epsilon), + np.log(gamma + epsilon), + c_sw2, c_sw3, + np.log(u_sw2 + epsilon), + np.log(u_sw3 + epsilon), + np.log(s_sw2 + epsilon), + np.log(s_sw3 + epsilon), + max_u_t, + np.log(max_u), + np.log(max_s), + gene_sw_t[2]]).T, + np.full((n, 1), state).T + )).T.astype(np.float32) + elif model == 2: + x = np.concatenate((np.array([c, + np.log(u + epsilon), + np.log(s + epsilon)]), + np.full((n, 12), [np.log(alpha_c + epsilon), + np.log(alpha + epsilon), + np.log(beta + epsilon), + np.log(gamma + epsilon), + c_sw2, c_sw3, + np.log(u_sw2 + epsilon), + np.log(u_sw3 + epsilon), + np.log(s_sw2 + epsilon), + np.log(s_sw3 + epsilon), + np.log(max_u), + gene_sw_t[2]]).T, + np.full((n, 1), state).T + )).T.astype(np.float32) + + return x + + +def calculate_dist_and_time_nn(c, u, s, + max_u, max_s, + t_sw_array, + alpha_c, alpha, beta, gamma, + rescale_c, rescale_u, + ode_model_0, ode_model_1, + ode_model_2_m1, ode_model_2_m2, + device, + scale_cc=1, + scale_factor=None, + model=1, + conn=None, + t=1000, k=1, + direction='complete', + total_h=20, + rna_only=False, + penalize_gap=True, + all_cells=True): + + rescale_factor = np.array([rescale_c, rescale_u, 1.0]) + + exp_list_net, exp_sw_list_net = generate_exp(None, + t_sw_array, + alpha_c, + alpha, + beta, + gamma, + model=model, + scale_cc=scale_cc, + rna_only=rna_only) + + N = len(c) + N_list = np.arange(N) + + if scale_factor is None: + cur_scale_factor = np.array([np.std(c), + np.std(u), + np.std(s)]) + else: + cur_scale_factor = scale_factor + + t_pred_per_state = [] + dists_per_state = [] + + dire = 0 + + if direction == "on": + states = [0, 1] + dire = 1 + + elif direction == "off": + states = [2, 3] + dire = 2 + + else: + states = [0, 1, 2, 3] + dire = 0 + + dists_per_state = np.zeros((N, len(states))) + t_pred_per_state = np.zeros((N, len(states))) + u_pred_per_state = np.zeros((N, len(states))) + s_pred_per_state = np.zeros((N, len(states))) + + increment = 0 + + # determine when we can consider u and s close to zero + zero_us = np.logical_and((u < 0.1 * max_u), (s < 0.1 * max_s)) + + t_pred = np.zeros(N) + dists = None + + # pass all the data through the neural net as each valid state + for state in states: + + # when u and s = 0, it's better to use the inverse c equation + # instead of the neural network, which happens for part of + # state 3 and all of state 0 + inverse_c = np.logical_or(state == 0, + np.logical_and(state == 3, zero_us)) + + not_inverse_c = np.logical_not(inverse_c) + + # if we want to use the inverse c equation... + if np.any(inverse_c): + + # find out at what switch time chromatin closes + c_sw_t = t_sw_array[int(model)] + + # figure out whether chromatin is opening/closing and what + # the initial c value is + if state <= model: + k_c = 1 + c_0_for_t_guess = 0 + elif state > model: + k_c = 0 + c_0_for_t_guess = exp_sw_list_net[int(model)][0, 0] + + # calculate predicted time from the inverse c equation + t_pred[inverse_c] = t_of_c(alpha_c, + k_c, c_0_for_t_guess, + c[inverse_c], + rescale_factor[0], + c_sw_t) + + # if there are points where we want to use the neural network... + if np.any(not_inverse_c): + + # create an input matrix from the data + x = make_X(c[not_inverse_c] / rescale_factor[0], + u[not_inverse_c] / rescale_factor[1], + s[not_inverse_c] / rescale_factor[2], + max_u, + max_s, + alpha_c*(scale_cc if state > model else 1), + alpha, beta, gamma, + t_sw_array, + 0, + exp_sw_list_net[0][0, 0], + exp_sw_list_net[1][0, 0], + exp_sw_list_net[2][0, 0], + 0, + exp_sw_list_net[0][0, 1], + exp_sw_list_net[1][0, 1], + exp_sw_list_net[2][0, 1], + 0, + exp_sw_list_net[0][0, 2], + exp_sw_list_net[1][0, 2], + exp_sw_list_net[2][0, 2], + model, direction, state) + + # do a forward pass + if dire == 0: + t_pred_ten = ode_model_0(torch.tensor(x, + dtype=torch.float, + device=device) + .reshape(-1, x.shape[1])) + + elif dire == 1: + t_pred_ten = ode_model_1(torch.tensor(x, + dtype=torch.float, + device=device) + .reshape(-1, x.shape[1])) + + elif dire == 2: + if model == 1: + t_pred_ten = ode_model_2_m1(torch.tensor(x, + dtype=torch.float, + device=device) + .reshape(-1, x.shape[1])) + elif model == 2: + t_pred_ten = ode_model_2_m2(torch.tensor(x, + dtype=torch.float, + device=device) + .reshape(-1, x.shape[1])) + + # make a numpy array out of our tensor of predicted time points + t_pred[not_inverse_c] = (t_pred_ten.cpu().detach().numpy() + .flatten()*21) - 1 + + # calculate tau values from our predicted time points + if state == 0: + t_pred = np.clip(t_pred, a_min=0, a_max=t_sw_array[0]) + tau1 = t_pred + tau2 = [] + tau3 = [] + tau4 = [] + elif state == 1: + tau1 = [] + t_pred = np.clip(t_pred, a_min=t_sw_array[0], a_max=t_sw_array[1]) + tau2 = t_pred - t_sw_array[0] + tau3 = [] + tau4 = [] + elif state == 2: + tau1 = [] + tau2 = [] + t_pred = np.clip(t_pred, a_min=t_sw_array[1], a_max=t_sw_array[2]) + tau3 = t_pred - t_sw_array[1] + tau4 = [] + elif state == 3: + tau1 = [] + tau2 = [] + tau3 = [] + t_pred = np.clip(t_pred, a_min=t_sw_array[2], a_max=20) + tau4 = t_pred - t_sw_array[2] + + tau_list = [tau1, tau2, tau3, tau4] + + valid_vals = [] + + for i in range(len(tau_list)): + if len(tau_list[i]) == 0: + tau_list[i] = np.array([0.0]) + else: + valid_vals.append(i) + + # take the time points and get predicted c/u/s values from them + exp_list, exp_sw_list_2 = generate_exp(tau_list, + t_sw_array, + alpha_c, + alpha, + beta, + gamma, + model=model, + scale_cc=scale_cc, + rna_only=rna_only) + + pred_c = np.concatenate([exp_list[x][:, 0] * rescale_factor[0] + for x in valid_vals]) + pred_u = np.concatenate([exp_list[x][:, 1] * rescale_factor[1] + for x in valid_vals]) + pred_s = np.concatenate([exp_list[x][:, 2] * rescale_factor[2] + for x in valid_vals]) + + # calculate distance between predicted and real values + c_diff = (c - pred_c) / cur_scale_factor[0] + u_diff = (u - pred_u) / cur_scale_factor[1] + s_diff = (s - pred_s) / cur_scale_factor[2] + + dists = (c_diff*c_diff) + (u_diff*u_diff) + (s_diff*s_diff) + + if conn is not None: + dists = conn.dot(dists) + + # store the distances, times, and predicted u and s values for + # each state + dists_per_state[:, increment] = dists + t_pred_per_state[:, increment] = t_pred + u_pred_per_state[:, increment] = pred_u + s_pred_per_state[:, increment] = pred_s + + increment += 1 + + # whichever state has the smallest distance for a given data point + # is our predicted state + state_pred = np.argmin(dists_per_state, axis=1) + + # slice dists and predicted time over the correct state + dists = dists_per_state[N_list, state_pred] + t_pred = t_pred_per_state[N_list, state_pred] + + max_t = t_pred.max() + min_t = t_pred.min() + + penalty = 0 + + # for induction and complete genes, add a penalty to ensure that not + # all points are in state 0 + if direction == "on" or direction == "complete": + + if t_sw_array[0] >= max_t: + penalty += (t_sw_array[0] - max_t) + 10 + + # for induction genes, add a penalty to ensure that predicted time + # points are not "out of bounds" by being greater than the + # second switch time + if direction == "on": + + if min_t > t_sw_array[1]: + penalty += (min_t - t_sw_array[1]) + 10 + + # for repression genes, add a penalty to ensure that predicted time + # points are not "out of bounds" by being smaller than the + # second switch time + if direction == "off": + + if t_sw_array[1] >= max_t: + penalty += (t_sw_array[1] - max_t) + 10 + + # add penalty to ensure that the time points aren't concentrated to + # one spot + if np.abs(max_t - min_t) <= 1e-2: + penalty += np.abs(max_t - min_t) + 10 + + # because the indices chosen by np.argmin are just indices, + # we need to increment by two to get the true state number for + # our "off" genes (e.g. so that they're in the domain of [2,3] instead + # of [0,1]) + if direction == "off": + state_pred += 2 + + if all_cells: + return dists, t_pred, state_pred, max_u, max_s, penalty + else: + return dists, state_pred, max_u, max_s, penalty + + +# @jit(nopython=True, fastmath=True) +def compute_likelihood(c, u, s, + t_sw_array, + alpha_c, alpha, beta, gamma, + rescale_c, rescale_u, + t_pred, + state_pred, + scale_cc=1, + scale_factor=None, + model=1, + weight=None, + total_h=20, + rna_only=False): + + if weight is None: + weight = np.full(c.shape, True) + c_ = c[weight] + u_ = u[weight] + s_ = s[weight] + t_pred_ = t_pred[weight] + state_pred_ = state_pred[weight] + + n = len(u_) + if scale_factor is None: + scale_factor = np.ones(3) + tau1 = t_pred_[state_pred_ == 0] + tau2 = t_pred_[state_pred_ == 1] - t_sw_array[0] + tau3 = t_pred_[state_pred_ == 2] - t_sw_array[1] + tau4 = t_pred_[state_pred_ == 3] - t_sw_array[2] + tau_list = [tau1, tau2, tau3, tau4] + switch = np.sum(t_sw_array < total_h) + typed_tau_list = List() + [typed_tau_list.append(x) for x in tau_list] + alpha_c, alpha, beta, gamma = check_params(alpha_c, alpha, beta, gamma) + exp_list, _ = generate_exp(typed_tau_list, + t_sw_array[:switch], + alpha_c, + alpha, + beta, + gamma, + model=model, + scale_cc=scale_cc, + rna_only=rna_only) + rescale_factor = np.array([rescale_c, rescale_u, 1.0]) + exp_list = [x*rescale_factor*scale_factor for x in exp_list] + exp_mat = np.hstack((np.reshape(c_, (-1, 1)), np.reshape(u_, (-1, 1)), + np.reshape(s_, (-1, 1)))) * scale_factor + diffs = np.empty((n, 3), dtype=u.dtype) + likelihood_c = 0 + likelihood_u = 0 + likelihood_s = 0 + ssd_c, var_c = 0, 0 + for i in range(switch+1): + index = state_pred_ == i + if np.sum(index) > 0: + diff = exp_mat[index, :] - exp_list[i] + diffs[index, :] = diff + if rna_only: + diff_u = np.ravel(diffs[:, 0]) + diff_s = np.ravel(diffs[:, 1]) + dist_us = diff_u ** 2 + diff_s ** 2 + var_us = np.var(np.sign(diff_s) * np.sqrt(dist_us)) + nll = (0.5 * np.log(2 * np.pi * var_us) + 0.5 / n / + var_us * np.sum(dist_us)) + else: + diff_c = np.ravel(diffs[:, 0]) + diff_u = np.ravel(diffs[:, 1]) + diff_s = np.ravel(diffs[:, 2]) + dist_c = diff_c ** 2 + dist_u = diff_u ** 2 + dist_s = diff_s ** 2 + var_c = np.var(diff_c) + var_u = np.var(diff_u) + var_s = np.var(diff_s) + ssd_c = np.sum(dist_c) + nll_c = (0.5 * np.log(2 * np.pi * var_c) + 0.5 / n / + var_c * np.sum(dist_c)) + nll_u = (0.5 * np.log(2 * np.pi * var_u) + 0.5 / n / + var_u * np.sum(dist_u)) + nll_s = (0.5 * np.log(2 * np.pi * var_s) + 0.5 / n / + var_s * np.sum(dist_s)) + nll = nll_c + nll_u + nll_s + likelihood_c = np.exp(-nll_c) + likelihood_u = np.exp(-nll_u) + likelihood_s = np.exp(-nll_s) + likelihood = np.exp(-nll) + return likelihood, likelihood_c, ssd_c, var_c, likelihood_u, likelihood_s + + +class ChromatinDynamical: + def __init__(self, c, u, s, + gene=None, + model=None, + max_iter=10, + init_mode="grid", + device="cpu", + neural_net=False, + adam=False, + adam_lr=None, + adam_beta1=None, + adam_beta2=None, + batch_size=None, + local_std=None, + embed_coord=None, + connectivities=None, + plot=False, + save_plot=False, + plot_dir=None, + fit_args=None, + partial=None, + direction=None, + rna_only=False, + fit_decoupling=True, + extra_color=None, + rescale_u=None, + alpha=None, + beta=None, + gamma=None, + t_=None + ): + + self.device = device + self.gene = gene + self.local_std = local_std + self.conn = connectivities + + self.neural_net = neural_net + self.adam = adam + self.adam_lr = adam_lr + self.adam_beta1 = adam_beta1 + self.adam_beta2 = adam_beta2 + self.batch_size = batch_size + + self.torch_type = type(u[0].item()) + + # fitting arguments + self.init_mode = init_mode + self.rna_only = rna_only + self.fit_decoupling = fit_decoupling + self.max_iter = max_iter + self.n_anchors = np.clip(int(fit_args['t']), 201, 2000) + self.k_dist = np.clip(int(fit_args['k']), 1, 20) + self.tm = np.clip(fit_args['thresh_multiplier'], 0.4, 2) + self.weight_c = np.clip(fit_args['weight_c'], 0.1, 5) + self.outlier = np.clip(fit_args['outlier'], 80, 100) + self.model = int(model) if isinstance(model, float) else model + self.model_ = None + if self.model == 0 and self.init_mode == 'invert': + self.init_mode = 'grid' + + # plot parameters + self.plot = plot + self.save_plot = save_plot + self.extra_color = extra_color + self.fig_size = fit_args['fig_size'] + self.point_size = fit_args['point_size'] + if plot_dir is None: + self.plot_path = 'rna_plots' if self.rna_only else 'plots' + else: + self.plot_path = plot_dir + self.color = ['tab:red', 'tab:orange', 'tab:green', 'tab:blue'] + self.fig = None + self.ax = None + + # input + self.total_n = len(u) + if sparse.issparse(c): + c = c.A + if sparse.issparse(u): + u = u.A + if sparse.issparse(s): + s = s.A + self.c_all = np.ravel(np.array(c, dtype=np.float64)) + self.u_all = np.ravel(np.array(u, dtype=np.float64)) + self.s_all = np.ravel(np.array(s, dtype=np.float64)) + + # adjust offset + self.offset_c, self.offset_u, self.offset_s = np.min(self.c_all), \ + np.min(self.u_all), np.min(self.s_all) + self.offset_c = 0 if self.rna_only else self.offset_c + self.c_all -= self.offset_c + self.u_all -= self.offset_u + self.s_all -= self.offset_s + # remove zero counts + self.non_zero = (np.ravel(self.c_all > 0) | np.ravel(self.u_all > 0) | + np.ravel(self.s_all > 0)) + # remove outliers + self.non_outlier = np.ravel(self.c_all <= np.percentile(self.c_all, + self.outlier)) + self.non_outlier &= np.ravel(self.u_all <= np.percentile(self.u_all, + self.outlier)) + self.non_outlier &= np.ravel(self.s_all <= np.percentile(self.s_all, + self.outlier)) + self.c = self.c_all[self.non_zero & self.non_outlier] + self.u = self.u_all[self.non_zero & self.non_outlier] + self.s = self.s_all[self.non_zero & self.non_outlier] + self.low_quality = len(self.u) < 10 + # scale modalities + self.std_c, self.std_u, self.std_s = (np.std(self.c_all) + if not self.rna_only + else 1.0, np.std(self.u_all), + np.std(self.s_all)) + if self.std_u == 0 or self.std_s == 0: + self.low_quality = True + self.scale_c, self.scale_u, self.scale_s = np.max(self.c_all) \ + if not self.rna_only else 1.0, self.std_u/self.std_s, 1.0 + + # if we're on neural net mode, check to see if c is way bigger than + # u or s, which would be very hard for the neural net to fit + if not self.low_quality and neural_net: + max_c_orig = np.max(self.c) + if max_c_orig / np.max(self.u) > 500: + self.low_quality = True + + if not self.low_quality: + if max_c_orig / np.max(self.s) > 500: + self.low_quality = True + + self.c_all /= self.scale_c + self.u_all /= self.scale_u + self.s_all /= self.scale_s + self.c /= self.scale_c + self.u /= self.scale_u + self.s /= self.scale_s + self.scale_factor = np.array([np.std(self.c_all) / self.std_s / + self.weight_c, 1.0, 1.0]) + self.scale_factor[0] = 1 if self.rna_only else self.scale_factor[0] + self.max_u, self.max_s = np.max(self.u), np.max(self.s) + self.max_u_all, self.max_s_all = np.max(self.u_all), np.max(self.s_all) + if self.conn is not None: + self.conn_sub = self.conn[np.ix_(self.non_zero & self.non_outlier, + self.non_zero & self.non_outlier)] + else: + self.conn_sub = None + + main_info(f'{len(self.u)} cells passed filter and will be used to ' + 'compute trajectories.', indent_level=2) + self.known_pars = (True + if None not in [rescale_u, alpha, beta, gamma, t_] + else False) + if self.known_pars: + main_info(f'known parameters for gene {self.gene} are ' + f'scaling={rescale_u}, alpha={alpha}, beta={beta},' + f' gamma={gamma}, t_={t_}.', indent_level=1) + + # define neural networks + self.ode_model_0 = nn.Sequential( + nn.Linear(21, 150), + nn.ReLU(), + nn.Linear(150, 112), + nn.ReLU(), + nn.Linear(112, 75), + nn.ReLU(), + nn.Linear(75, 1), + nn.Sigmoid() + ) + + self.ode_model_1 = nn.Sequential( + nn.Linear(16, 64), + nn.ReLU(), + nn.Linear(64, 48), + nn.ReLU(), + nn.Linear(48, 32), + nn.ReLU(), + nn.Linear(32, 1), + nn.Sigmoid() + ) + + self.ode_model_2_m1 = nn.Sequential( + nn.Linear(18, 220), + nn.ReLU(), + nn.Linear(220, 165), + nn.ReLU(), + nn.Linear(165, 110), + nn.ReLU(), + nn.Linear(110, 1), + nn.Sigmoid() + ) + + self.ode_model_2_m2 = nn.Sequential( + nn.Linear(16, 150), + nn.ReLU(), + nn.Linear(150, 112), + nn.ReLU(), + nn.Linear(112, 75), + nn.ReLU(), + nn.Linear(75, 1), + nn.Sigmoid() + ) + + self.ode_model_0.to(torch.device(self.device)) + self.ode_model_1.to(torch.device(self.device)) + self.ode_model_2_m1.to(torch.device(self.device)) + self.ode_model_2_m2.to(torch.device(self.device)) + + # load in neural network + net_path = os.path.dirname(os.path.abspath(__file__)) + \ + "/neural_nets/" + + self.ode_model_0.load_state_dict(torch.load(net_path+"dir0.pt")) + self.ode_model_1.load_state_dict(torch.load(net_path+"dir1.pt")) + self.ode_model_2_m1.load_state_dict(torch.load(net_path+"dir2_m1.pt")) + self.ode_model_2_m2.load_state_dict(torch.load(net_path+"dir2_m2.pt")) + + # 4 rate parameters + self.alpha_c = 0.1 + self.alpha = alpha if alpha is not None else 0.0 + self.beta = beta if beta is not None else 0.0 + self.gamma = gamma if gamma is not None else 0.0 + # 3 possible switch time points + self.t_sw_1 = 0.1 if t_ is not None else 0.0 + self.t_sw_2 = t_+0.1 if t_ is not None else 0.0 + self.t_sw_3 = 20.0 if t_ is not None else 0.0 + # 2 rescale factors + self.rescale_c = 1.0 + self.rescale_u = rescale_u if rescale_u is not None else 1.0 + self.rates = None + self.t_sw_array = None + self.fit_rescale = True if rescale_u is None else False + self.params = None + + # other parameters or results + self.t = None + self.state = None + self.loss = [np.inf] + self.likelihood = -1.0 + self.l_c = 0 + self.ssd_c, self.var_c = 0, 0 + self.scale_cc = 1.0 + self.fitting_flag_ = 0 + self.velocity = None + self.anchor_t1_list, self.anchor_t2_list = None, None + self.anchor_exp = None + self.anchor_exp_sw = None + self.anchor_min_idx, self.anchor_max_idx, self.anchor_velo_min_idx, \ + self.anchor_velo_max_idx = None, None, None, None + self.anchor_velo = None + self.c0 = self.u0 = self.s0 = 0.0 + self.realign_ratio = 1.0 + self.partial = False + self.direction = 'complete' + self.steady_state_func = None + + # for fit and update + self.cur_iter = 0 + self.cur_loss = None + self.cur_state_pred = None + self.cur_t_sw_adjust = None + + # partial checking and model examination + determine_model = model is None + if partial is None and direction is None: + if embed_coord is not None: + self.embed_coord = embed_coord[self.non_zero & + self.non_outlier] + else: + self.embed_coord = None + self.check_partial_trajectory(determine_model=determine_model) + elif direction is not None: + self.direction = direction + if direction in ['on', 'off']: + self.partial = True + else: + self.partial = False + self.check_partial_trajectory(fit_gmm=False, fit_slope=False, + determine_model=determine_model) + elif partial is not None: + self.partial = partial + self.check_partial_trajectory(fit_gmm=False, + determine_model=determine_model) + else: + self.check_partial_trajectory(fit_gmm=False, fit_slope=False, + determine_model=determine_model) + + # intialize steady state parameters + if not self.known_pars and not self.low_quality: + self.initialize_steady_state_params(model_mismatch=self.model + != self.model_) + if self.known_pars: + self.params = np.array([self.t_sw_1, + self.t_sw_2-self.t_sw_1, + self.t_sw_3-self.t_sw_2, + self.alpha_c, + self.alpha, + self.beta, + self.gamma, + self.scale_cc, + self.rescale_c, + self.rescale_u]) + + # the torch tensor version of the anchor points function + def anchor_points_ten(self, t_sw_array, total_h=20, t=1000, mode='uniform', + return_time=False): + + t_ = torch.linspace(0, total_h, t, device=self.device, + dtype=self.torch_type) + tau1 = t_[t_ <= t_sw_array[0]] + tau2 = t_[(t_sw_array[0] < t_) & (t_ <= t_sw_array[1])] - t_sw_array[0] + tau3 = t_[(t_sw_array[1] < t_) & (t_ <= t_sw_array[2])] - t_sw_array[1] + tau4 = t_[t_sw_array[2] < t_] - t_sw_array[2] + + if mode == 'log': + if len(tau1) > 0: + tau1 = torch.expm1(tau1) + tau1 = tau1 / torch.max(tau1) * (t_sw_array[0]) + if len(tau2) > 0: + tau2 = torch.expm1(tau2) + tau2 = tau2 / torch.max(tau2) * (t_sw_array[1] - t_sw_array[0]) + if len(tau3) > 0: + tau3 = torch.expm1(tau3) + tau3 = tau3 / torch.max(tau3) * (t_sw_array[2] - t_sw_array[1]) + if len(tau4) > 0: + tau4 = torch.expm1(tau4) + tau4 = tau4 / torch.max(tau4) * (total_h - t_sw_array[2]) + + tau_list = [tau1, tau2, tau3, tau4] + if return_time: + return t_, tau_list + else: + return tau_list + + # the torch version of the predict_exp function + def predict_exp_ten(self, + tau, + c0, + u0, + s0, + alpha_c, + alpha, + beta, + gamma, + scale_cc=None, + pred_r=True, + chrom_open=True, + backward=False, + rna_only=False): + + if scale_cc is None: + scale_cc = torch.tensor(1.0, requires_grad=True, + device=self.device, + dtype=self.torch_type) + + if len(tau) == 0: + return torch.empty((0, 3), + requires_grad=True, + device=self.device, + dtype=self.torch_type) + if backward: + tau = -tau + + eat = torch.exp(-alpha_c * tau) + ebt = torch.exp(-beta * tau) + egt = torch.exp(-gamma * tau) + if rna_only: + kc = 1 + c0 = 1 + else: + if chrom_open: + kc = 1 + else: + kc = 0 + alpha_c = alpha_c * scale_cc + + const = (kc - c0) * alpha / (beta - alpha_c) + + res0 = kc - (kc - c0) * eat + + if pred_r: + + res1 = u0 * ebt + (alpha * kc / beta) * (1 - ebt) + res1 += const * (ebt - eat) + + res2 = s0 * egt + (alpha * kc / gamma) * (1 - egt) + res2 += ((beta / (gamma - beta)) * + ((alpha * kc / beta) - u0 - const) * (egt - ebt)) + res2 += (beta / (gamma - alpha_c)) * const * (egt - eat) + + else: + res1 = torch.zeros(len(tau), device=self.device, + requires_grad=True, + dtype=self.torch_type) + res2 = torch.zeros(len(tau), device=self.device, + requires_grad=True, + dtype=self.torch_type) + + res = torch.stack((res0, res1, res2), 1) + + return res + + # the torch tensor version of the generate_exp function + def generate_exp_tens(self, + tau_list, + t_sw_array, + alpha_c, + alpha, + beta, + gamma, + scale_cc=None, + model=1, + rna_only=False): + + if scale_cc is None: + scale_cc = torch.tensor(1.0, requires_grad=True, + device=self.device, + dtype=self.torch_type) + + if beta == alpha_c: + beta += 1e-3 + if gamma == beta or gamma == alpha_c: + gamma += 1e-3 + switch = int(t_sw_array.size(dim=0)) + if switch >= 1: + tau_sw1 = torch.tensor([t_sw_array[0]], requires_grad=True, + device=self.device, + dtype=self.torch_type) + if switch >= 2: + tau_sw2 = torch.tensor([t_sw_array[1] - t_sw_array[0]], + requires_grad=True, + device=self.device, + dtype=self.torch_type) + if switch == 3: + tau_sw3 = torch.tensor([t_sw_array[2] - t_sw_array[1]], + requires_grad=True, + device=self.device, + dtype=self.torch_type) + exp_sw1, exp_sw2, exp_sw3 = (torch.empty((0, 3), + requires_grad=True, + device=self.device, + dtype=self.torch_type), + torch.empty((0, 3), + requires_grad=True, + device=self.device, + dtype=self.torch_type), + torch.empty((0, 3), + requires_grad=True, + device=self.device, + dtype=self.torch_type)) + if tau_list is None: + if model == 0: + if switch >= 1: + exp_sw1 = self.predict_exp_ten(tau_sw1, 0, 0, 0, alpha_c, + alpha, beta, gamma, + pred_r=False, + scale_cc=scale_cc, + rna_only=rna_only) + if switch >= 2: + exp_sw2 = self.predict_exp_ten(tau_sw2, exp_sw1[0, 0], + exp_sw1[0, 1], + exp_sw1[0, 2], + alpha_c, alpha, beta, + gamma, pred_r=False, + chrom_open=False, + scale_cc=scale_cc, + rna_only=rna_only) + if switch >= 3: + exp_sw3 = self.predict_exp_ten(tau_sw3, + exp_sw2[0, 0], + exp_sw2[0, 1], + exp_sw2[0, 2], + alpha_c, alpha, + beta, gamma, + chrom_open=False, + scale_cc=scale_cc, + rna_only=rna_only) + elif model == 1: + if switch >= 1: + exp_sw1 = self.predict_exp_ten(tau_sw1, 0, 0, 0, alpha_c, + alpha, beta, gamma, + pred_r=False, + scale_cc=scale_cc, + rna_only=rna_only) + if switch >= 2: + exp_sw2 = self.predict_exp_ten(tau_sw2, exp_sw1[0, 0], + exp_sw1[0, 1], + exp_sw1[0, 2], + alpha_c, alpha, + beta, gamma, + scale_cc=scale_cc, + rna_only=rna_only) + if switch >= 3: + exp_sw3 = self.predict_exp_ten(tau_sw3, + exp_sw2[0, 0], + exp_sw2[0, 1], + exp_sw2[0, 2], + alpha_c, alpha, + beta, gamma, + chrom_open=False, + scale_cc=scale_cc, + rna_only=rna_only) + elif model == 2: + if switch >= 1: + exp_sw1 = self.predict_exp_ten(tau_sw1, 0, 0, 0, alpha_c, + alpha, beta, gamma, + pred_r=False, + scale_cc=scale_cc, + rna_only=rna_only) + if switch >= 2: + exp_sw2 = self.predict_exp_ten(tau_sw2, exp_sw1[0, 0], + exp_sw1[0, 1], + exp_sw1[0, 2], alpha_c, + alpha, beta, gamma, + scale_cc=scale_cc, + rna_only=rna_only) + if switch >= 3: + exp_sw3 = self.predict_exp_ten(tau_sw3, + exp_sw2[0, 0], + exp_sw2[0, 1], + exp_sw2[0, 2], + alpha_c, 0, beta, + gamma, + scale_cc=scale_cc, + rna_only=rna_only) + + return [torch.empty((0, 3), requires_grad=True, + device=self.device, + dtype=self.torch_type), + torch.empty((0, 3), requires_grad=True, + device=self.device, + dtype=self.torch_type), + torch.empty((0, 3), requires_grad=True, + device=self.device, + dtype=self.torch_type), + torch.empty((0, 3), requires_grad=True, + device=self.device, + dtype=self.torch_type)], \ + [exp_sw1, exp_sw2, exp_sw3] + + tau1 = tau_list[0] + if switch >= 1: + tau2 = tau_list[1] + if switch >= 2: + tau3 = tau_list[2] + if switch == 3: + tau4 = tau_list[3] + exp1, exp2, exp3, exp4 = (torch.empty((0, 3), requires_grad=True, + device=self.device, + dtype=self.torch_type), + torch.empty((0, 3), requires_grad=True, + device=self.device, + dtype=self.torch_type), + torch.empty((0, 3), requires_grad=True, + device=self.device, + dtype=self.torch_type), + torch.empty((0, 3), requires_grad=True, + device=self.device, + dtype=self.torch_type)) + if model == 0: + exp1 = self.predict_exp_ten(tau1, 0, 0, 0, alpha_c, alpha, beta, + gamma, pred_r=False, scale_cc=scale_cc, + rna_only=rna_only) + if switch >= 1: + exp_sw1 = self.predict_exp_ten(tau_sw1, 0, 0, 0, alpha_c, + alpha, beta, gamma, + pred_r=False, scale_cc=scale_cc, + rna_only=rna_only) + exp2 = self.predict_exp_ten(tau2, exp_sw1[0, 0], exp_sw1[0, 1], + exp_sw1[0, 2], alpha_c, alpha, + beta, gamma, pred_r=False, + chrom_open=False, + scale_cc=scale_cc, + rna_only=rna_only) + if switch >= 2: + exp_sw2 = self.predict_exp_ten(tau_sw2, exp_sw1[0, 0], + exp_sw1[0, 1], + exp_sw1[0, 2], + alpha_c, alpha, beta, gamma, + pred_r=False, + chrom_open=False, + scale_cc=scale_cc, + rna_only=rna_only) + exp3 = self.predict_exp_ten(tau3, exp_sw2[0, 0], + exp_sw2[0, 1], exp_sw2[0, 2], + alpha_c, alpha, beta, gamma, + chrom_open=False, + scale_cc=scale_cc, + rna_only=rna_only) + if switch == 3: + exp_sw3 = self.predict_exp_ten(tau_sw3, exp_sw2[0, 0], + exp_sw2[0, 1], + exp_sw2[0, 2], + alpha_c, alpha, beta, + gamma, + chrom_open=False, + scale_cc=scale_cc, + rna_only=rna_only) + exp4 = self.predict_exp_ten(tau4, exp_sw3[0, 0], + exp_sw3[0, 1], + exp_sw3[0, 2], + alpha_c, 0, beta, gamma, + chrom_open=False, + scale_cc=scale_cc, + rna_only=rna_only) + elif model == 1: + exp1 = self.predict_exp_ten(tau1, 0, 0, 0, alpha_c, alpha, beta, + gamma, pred_r=False, scale_cc=scale_cc, + rna_only=rna_only) + if switch >= 1: + exp_sw1 = self.predict_exp_ten(tau_sw1, 0, 0, 0, alpha_c, + alpha, beta, gamma, + pred_r=False, scale_cc=scale_cc, + rna_only=rna_only) + exp2 = self.predict_exp_ten(tau2, exp_sw1[0, 0], exp_sw1[0, 1], + exp_sw1[0, 2], alpha_c, alpha, + beta, gamma, scale_cc=scale_cc, + rna_only=rna_only) + if switch >= 2: + exp_sw2 = self.predict_exp_ten(tau_sw2, exp_sw1[0, 0], + exp_sw1[0, 1], + exp_sw1[0, 2], alpha_c, + alpha, beta, gamma, + scale_cc=scale_cc, + rna_only=rna_only) + exp3 = self.predict_exp_ten(tau3, exp_sw2[0, 0], + exp_sw2[0, 1], exp_sw2[0, 2], + alpha_c, alpha, beta, gamma, + chrom_open=False, + scale_cc=scale_cc, + rna_only=rna_only) + if switch == 3: + exp_sw3 = self.predict_exp_ten(tau_sw3, exp_sw2[0, 0], + exp_sw2[0, 1], + exp_sw2[0, 2], + alpha_c, alpha, beta, + gamma, + chrom_open=False, + scale_cc=scale_cc, + rna_only=rna_only) + exp4 = self.predict_exp_ten(tau4, exp_sw3[0, 0], + exp_sw3[0, 1], + exp_sw3[0, 2], alpha_c, 0, + beta, gamma, + chrom_open=False, + scale_cc=scale_cc, + rna_only=rna_only) + elif model == 2: + exp1 = self.predict_exp_ten(tau1, 0, 0, 0, alpha_c, alpha, beta, + gamma, pred_r=False, scale_cc=scale_cc, + rna_only=rna_only) + if switch >= 1: + exp_sw1 = self.predict_exp_ten(tau_sw1, 0, 0, 0, alpha_c, + alpha, beta, gamma, + pred_r=False, scale_cc=scale_cc, + rna_only=rna_only) + exp2 = self.predict_exp_ten(tau2, exp_sw1[0, 0], exp_sw1[0, 1], + exp_sw1[0, 2], alpha_c, alpha, + beta, gamma, scale_cc=scale_cc, + rna_only=rna_only) + if switch >= 2: + exp_sw2 = self.predict_exp_ten(tau_sw2, exp_sw1[0, 0], + exp_sw1[0, 1], + exp_sw1[0, 2], alpha_c, + alpha, beta, gamma, + scale_cc=scale_cc, + rna_only=rna_only) + exp3 = self.predict_exp_ten(tau3, exp_sw2[0, 0], + exp_sw2[0, 1], + exp_sw2[0, 2], alpha_c, 0, + beta, gamma, scale_cc=scale_cc, + rna_only=rna_only) + if switch == 3: + exp_sw3 = self.predict_exp_ten(tau_sw3, exp_sw2[0, 0], + exp_sw2[0, 1], + exp_sw2[0, 2], + alpha_c, 0, beta, gamma, + scale_cc=scale_cc, + rna_only=rna_only) + exp4 = self.predict_exp_ten(tau4, exp_sw3[0, 0], + exp_sw3[0, 1], + exp_sw3[0, 2], + alpha_c, 0, beta, gamma, + chrom_open=False, + scale_cc=scale_cc, + rna_only=rna_only) + return [exp1, exp2, exp3, exp4], [exp_sw1, exp_sw2, exp_sw3] + + def check_partial_trajectory(self, fit_gmm=True, fit_slope=True, + determine_model=True): + w_non_zero = ((self.c >= 0.1 * np.max(self.c)) & + (self.u >= 0.1 * np.max(self.u)) & + (self.s >= 0.1 * np.max(self.s))) + u_non_zero = self.u[w_non_zero] + s_non_zero = self.s[w_non_zero] + if len(u_non_zero) < 10: + self.low_quality = True + return + + # GMM + w_low = ((np.percentile(s_non_zero, 30) <= s_non_zero) & + (s_non_zero <= np.percentile(s_non_zero, 40))) + if np.sum(w_low) < 10: + fit_gmm = False + self.partial = True + if self.local_std is None: + main_info('local standard deviation not provided. ' + 'Skipping GMM..', indent_level=2) + if self.embed_coord is None: + main_info('Warning: embedded coordinates not provided. ' + 'Skipping GMM..') + if (fit_gmm and self.local_std is not None and self.embed_coord + is not None): + + pdist = pairwise_distances( + self.embed_coord[w_non_zero, :][w_low, :]) + dists = (np.ravel(pdist[np.triu_indices_from(pdist, k=1)]) + .reshape(-1, 1)) + model = GaussianMixture(n_components=2, covariance_type='tied', + random_state=2021).fit(dists) + mean_diff = np.abs(model.means_[1][0] - model.means_[0][0]) + criterion1 = mean_diff > self.local_std / self.tm + main_info(f'GMM: difference between means = {mean_diff}, ' + f'threshold = {self.local_std / self.tm}.', indent_level=2) + criterion2 = np.all(model.weights_[1] > 0.2 / self.tm) + main_info('GMM: weight of the second Gaussian =' + f' {model.weights_[1]}.', indent_level=2) + if criterion1 and criterion2: + self.partial = False + else: + self.partial = True + main_info(f'GMM decides {"" if self.partial else "not "}' + 'partial.', indent_level=2) + + # steady-state slope + wu = self.u >= np.percentile(u_non_zero, 95) + ws = self.s >= np.percentile(s_non_zero, 95) + ss_u = self.u[wu | ws] + ss_s = self.s[wu | ws] + if np.all(ss_u == 0) or np.all(ss_s == 0): + self.low_quality = True + return + gamma = np.dot(ss_u, ss_s) / np.dot(ss_s, ss_s) + self.steady_state_func = lambda x: gamma*x + + # thickness of phase portrait + u_norm = u_non_zero / np.max(self.u) + s_norm = s_non_zero / np.max(self.s) + exp = np.hstack((np.reshape(u_norm, (-1, 1)), + np.reshape(s_norm, (-1, 1)))) + U, S, Vh = np.linalg.svd(exp) + self.thickness = S[1] + + # slope-based direction decision + with np.errstate(divide='ignore', invalid='ignore'): + slope = self.u / self.s + non_nan = ~np.isnan(slope) + slope = slope[non_nan] + on = slope >= gamma + off = slope < gamma + if len(ss_u) < 10 or len(u_non_zero) < 10: + fit_slope = False + self.direction = 'complete' + if fit_slope: + slope_ = u_non_zero / s_non_zero + on_ = slope_ >= gamma + off_ = slope_ < gamma + on_dist = np.sum((u_non_zero[on_] - gamma * s_non_zero[on_])**2) + off_dist = np.sum((gamma * s_non_zero[off_] - u_non_zero[off_])**2) + main_info(f'Slope: SSE on induction phase = {on_dist},' + f' SSE on repression phase = {off_dist}.', indent_level=2) + if self.thickness < 1.5 / np.sqrt(self.tm): + narrow = True + else: + narrow = False + main_info(f'Thickness of trajectory = {self.thickness}. ' + f'Trajectory is {"narrow" if narrow else "normal"}.', + indent_level=2) + if on_dist > 10 * self.tm**2 * off_dist: + self.direction = 'on' + self.partial = True + elif off_dist > 10 * self.tm**2 * on_dist: + self.direction = 'off' + self.partial = True + else: + if self.partial is True: + if on_dist > 3 * self.tm * off_dist: + self.direction = 'on' + elif off_dist > 3 * self.tm * on_dist: + self.direction = 'off' + else: + if narrow: + self.direction = 'on' + else: + self.direction = 'complete' + self.partial = False + else: + if narrow: + self.direction = ('off' + if off_dist > 2 * self.tm * on_dist + else 'on') + self.partial = True + else: + self.direction = 'complete' + + # model pre-determination + if self.direction == 'on': + self.model_ = 1 + elif self.direction == 'off': + self.model_ = 2 + else: + c_high = self.c >= np.mean(self.c) + 2 * np.std(self.c) + c_high = c_high[non_nan] + if np.sum(c_high) < 10: + c_high = self.c >= np.mean(self.c) + np.std(self.c) + c_high = c_high[non_nan] + if np.sum(c_high) < 10: + c_high = self.c >= np.percentile(self.c, 90) + c_high = c_high[non_nan] + if np.sum(self.c[non_nan][c_high] == 0) > 0.5*np.sum(c_high): + self.low_quality = True + return + c_high_on = np.sum(c_high & on) + c_high_off = np.sum(c_high & off) + if c_high_on > c_high_off: + self.model_ = 1 + else: + self.model_ = 2 + if determine_model: + self.model = self.model_ + + if not self.known_pars: + if fit_gmm or fit_slope: + main_info(f'predicted partial trajectory: {self.partial}', + indent_level=1) + main_info('predicted trajectory direction:' + f'{self.direction}', indent_level=1) + if determine_model: + main_info(f'predicted model: {self.model}', indent_level=1) + + def initialize_steady_state_params(self, model_mismatch=False): + self.scale_cc = 1.0 + self.rescale_c = 1.0 + # estimate rescale factor for u + s_norm = self.s / self.max_s + u_mid = (self.u >= 0.4 * self.max_u) & (self.u <= 0.6 * self.max_u) + if np.sum(u_mid) < 10: + self.rescale_u = self.thickness / 5 + else: + s_low, s_high = np.percentile(s_norm[u_mid], [2, 98]) + s_dist = s_high - s_low + self.rescale_u = s_dist + if self.rescale_u == 0: + self.low_quality = True + return + + c = self.c / self.rescale_c + u = self.u / self.rescale_u + s = self.s + + # some extreme values + wu = u >= np.percentile(u, 97) + ws = s >= np.percentile(s, 97) + ss_u = u[wu | ws] + ss_s = s[wu | ws] + c_upper = np.mean(c[wu | ws]) + + c_high = c >= np.mean(c) + # _r stands for repressed state + c0_r = np.mean(c[c_high]) + u0_r = np.mean(ss_u) + s0_r = np.mean(ss_s) + if c0_r < c_upper: + c0_r = c_upper + 0.1 + + # adjust chromatin level for reasonable initialization + if model_mismatch or not self.fit_decoupling: + c_indu = np.mean(c[self.u > self.steady_state_func(self.s)]) + c_repr = np.mean(c[self.u < self.steady_state_func(self.s)]) + if c_indu == np.nan or c_repr == np.nan: + self.low_quality = True + return + c0_r = np.mean(c[c >= np.min([c_indu, c_repr])]) + + # initialize rates + self.alpha_c = 0.1 + self.beta = 1.0 + self.gamma = np.dot(ss_u, ss_s) / np.dot(ss_s, ss_s) + alpha = u0_r + self.alpha = u0_r + self.rates = np.array([self.alpha_c, self.alpha, self.beta, + self.gamma]) + + # RNA-only + if self.rna_only: + t_sw_1 = 0.1 + t_sw_3 = 20.0 + if self.init_mode == 'grid': + # arange returns sequence [2,6,10,14,18] + for t_sw_2 in np.arange(2, 20, 4, dtype=np.float64): + self.update(params, initialize=True, adjust_time=False, + plot=False) + + elif self.init_mode == 'simple': + t_sw_2 = 10 + self.params = np.array([t_sw_1, + t_sw_2-t_sw_1, + t_sw_3-t_sw_2, + self.alpha_c, + self.alpha, + self.beta, + self.gamma, + self.scale_cc, + self.rescale_c, + self.rescale_u]) + + elif self.init_mode == 'invert': + t_sw_2 = approx_tau(u0_r, s0_r, 0, 0, alpha, self.beta, + self.gamma) + if t_sw_2 <= 0.2: + t_sw_2 = 1.0 + elif t_sw_2 >= 19.9: + t_sw_2 = 19.0 + self.params = np.array([t_sw_1, + t_sw_2-t_sw_1, + t_sw_3-t_sw_2, + self.alpha_c, + self.alpha, + self.beta, + self.gamma, + self.scale_cc, + self.rescale_c, + self.rescale_u]) + + # chromatin-RNA + else: + if self.init_mode == 'grid': + # arange returns sequence [1,5,9,13,17] + for t_sw_1 in np.arange(1, 18, 4, dtype=np.float64): + # arange returns sequence 2,6,10,14,18 + for t_sw_2 in np.arange(t_sw_1+1, 19, 4, dtype=np.float64): + # arange returns sequence [3,7,11,15,19] + for t_sw_3 in np.arange(t_sw_2+1, 20, 4, + dtype=np.float64): + if not self.fit_decoupling: + t_sw_3 = t_sw_2 + 30 / self.n_anchors + params = np.array([t_sw_1, + t_sw_2-t_sw_1, + t_sw_3-t_sw_2, + self.alpha_c, + self.alpha, + self.beta, + self.gamma, + self.scale_cc, + self.rescale_c, + self.rescale_u]) + self.update(params, initialize=True, + adjust_time=False, plot=False) + if not self.fit_decoupling: + break + + elif self.init_mode == 'simple': + t_sw_1, t_sw_2, t_sw_3 = 5, 10, 15 \ + if not self.fit_decoupling \ + else 10.1 + self.params = np.array([t_sw_1, + t_sw_2-t_sw_1, + t_sw_3-t_sw_2, + self.alpha_c, + self.alpha, + self.beta, + self.gamma, + self.scale_cc, + self.rescale_c, + self.rescale_u]) + + elif self.init_mode == 'invert': + self.alpha = u0_r / c_upper + if model_mismatch or not self.fit_decoupling: + self.alpha = u0_r / c0_r + rna_interval = approx_tau(u0_r, s0_r, 0, 0, alpha, self.beta, + self.gamma) + rna_interval = np.clip(rna_interval, 3, 12) + if self.model == 1: + for t_sw_1 in np.arange(1, rna_interval-1, 2, + dtype=np.float64): + t_sw_3 = rna_interval + t_sw_1 + for t_sw_2 in np.arange(t_sw_1+1, rna_interval, 2, + dtype=np.float64): + if not self.fit_decoupling: + t_sw_2 = t_sw_3 - 30 / self.n_anchors + + alpha_c = -np.log(1 - c0_r) / t_sw_2 + params = np.array([t_sw_1, + t_sw_2-t_sw_1, + t_sw_3-t_sw_2, + alpha_c, + self.alpha, + self.beta, + self.gamma, + self.scale_cc, + self.rescale_c, + self.rescale_u]) + self.update(params, initialize=True, + adjust_time=False, plot=False) + if not self.fit_decoupling: + break + + elif self.model == 2: + for t_sw_1 in np.arange(1, rna_interval, 2, + dtype=np.float64): + t_sw_2 = rna_interval + t_sw_1 + for t_sw_3 in np.arange(t_sw_2+1, t_sw_2+6, 2, + dtype=np.float64): + if not self.fit_decoupling: + t_sw_3 = t_sw_2 + 30 / self.n_anchors + + alpha_c = -np.log(1 - c0_r) / t_sw_3 + params = np.array([t_sw_1, + t_sw_2-t_sw_1, + t_sw_3-t_sw_2, + alpha_c, + self.alpha, + self.beta, + self.gamma, + self.scale_cc, + self.rescale_c, + self.rescale_u]) + self.update(params, initialize=True, + adjust_time=False, plot=False) + if not self.fit_decoupling: + break + + self.loss = [self.mse(self.params)] + self.t_sw_array = np.array([self.params[0], + self.params[0]+self.params[1], + self.params[0]+self.params[1] + + self.params[2]]) + self.t_sw_1, self.t_sw_2, self.t_sw_3 = self.t_sw_array + + main_info(f'initial params:\nswitch time array = {self.t_sw_array},' + '\n' + f'rates = {self.rates},\ncc scale = {self.scale_cc},\n' + f'c rescale factor = {self.rescale_c},\n' + f'u rescale factor = {self.rescale_u}', indent_level=1) + main_info(f'initial loss: {self.loss[-1]}', indent_level=1) + + def fit(self): + if self.low_quality: + return self.loss + + if self.plot: + plt.ion() + self.fig = plt.figure(figsize=self.fig_size) + if self.rna_only: + self.ax = self.fig.add_subplot(111) + else: + self.ax = self.fig.add_subplot(111, projection='3d') + + if not self.known_pars: + self.fit_dyn() + + self.update(self.params, perform_update=True, fit_outlier=True, + plot=True) + + # remove long gaps in the last observed state + t_sorted = np.sort(self.t) + dt = np.diff(t_sorted, prepend=0) + mean_dt = np.mean(dt) + std_dt = np.std(dt) + gap_thresh = np.clip(mean_dt+3*std_dt, 3*20/self.n_anchors, None) + if gap_thresh > 0: + idx = np.where(dt > gap_thresh)[0] + gap_sum = 0 + last_t_sw = np.max(self.t_sw_array[self.t_sw_array < 20]) + for i in idx: + t1 = t_sorted[i-1] if i > 0 else 0 + t2 = t_sorted[i] + if t1 > last_t_sw and t2 <= 20: + gap_sum += np.clip(t2 - t1 - mean_dt, 0, None) + if last_t_sw > np.max(self.t): + gap_sum += 20 - last_t_sw + realign_ratio = np.clip(20/(20 - gap_sum), None, 20/last_t_sw) + main_info(f'removing gaps and realigning by {realign_ratio}..', + indent_level=1) + self.rates /= realign_ratio + self.alpha_c, self.alpha, self.beta, self.gamma = self.rates + self.params[:3] *= realign_ratio + self.params[3:7] = self.rates + self.t_sw_array = np.array([self.params[0], + self.params[0]+self.params[1], + self.params[0]+self.params[1] + + self.params[2]]) + self.t_sw_1, self.t_sw_2, self.t_sw_3 = self.t_sw_array + self.update(self.params, perform_update=True, fit_outlier=True, + plot=True) + + if self.plot: + plt.ioff() + plt.show(block=True) + + # likelihood + main_info('computing likelihood..', indent_level=1) + keep = self.non_zero & self.non_outlier & \ + (self.u_all > 0.2 * np.percentile(self.u_all, 99.5)) & \ + (self.s_all > 0.2 * np.percentile(self.s_all, 99.5)) + scale_factor = np.array([self.scale_c / self.std_c, + self.scale_u / self.std_u, + self.scale_s / self.std_s]) + if np.sum(keep) >= 10: + self.likelihood, self.l_c, self.ssd_c, self.var_c, l_u, l_s = \ + compute_likelihood(self.c_all, + self.u_all, + self.s_all, + self.t_sw_array, + self.alpha_c, + self.alpha, + self.beta, + self.gamma, + self.rescale_c, + self.rescale_u, + self.t, + self.state, + scale_cc=self.scale_cc, + scale_factor=scale_factor, + model=self.model, + weight=keep, + rna_only=self.rna_only) + else: + self.likelihood, self.l_c, self.ssd_c, self.var_c, l_u = \ + 0, 0, 0, 0, 0 + # TODO: Keep? Remove?? + l_s = 0 + + if not self.rna_only: + main_info(f'likelihood of c: {self.l_c}, likelihood of u: {l_u},' + f' likelihood of s: {l_s}', indent_level=1) + + # velocity + main_info('computing velocities..', indent_level=1) + self.velocity = np.empty((len(self.u_all), 3)) + if self.conn is not None: + new_time = self.conn.dot(self.t) + new_time[new_time > 20] = 20 + new_state = self.state.copy() + new_state[new_time <= self.t_sw_1] = 0 + new_state[(self.t_sw_1 < new_time) & (new_time <= self.t_sw_2)] = 1 + new_state[(self.t_sw_2 < new_time) & (new_time <= self.t_sw_3)] = 2 + new_state[self.t_sw_3 < new_time] = 3 + + else: + new_time = self.t + new_state = self.state + + self.alpha_c, self.alpha, self.beta, self.gamma = \ + check_params(self.alpha_c, self.alpha, self.beta, self.gamma) + vc, vu, vs = compute_velocity(new_time, + self.t_sw_array, + new_state, + self.alpha_c, + self.alpha, + self.beta, + self.gamma, + self.rescale_c, + self.rescale_u, + scale_cc=self.scale_cc, + model=self.model, + rna_only=self.rna_only) + + self.velocity[:, 0] = vc * self.scale_c + self.velocity[:, 1] = vu * self.scale_u + self.velocity[:, 2] = vs * self.scale_s + + # anchor expression and velocity + anchor_time, tau_list = anchor_points(self.t_sw_array, 20, + self.n_anchors, return_time=True) + switch = np.sum(self.t_sw_array < 20) + typed_tau_list = List() + [typed_tau_list.append(x) for x in tau_list] + self.alpha_c, self.alpha, self.beta, self.gamma, \ + self.c0, self.u0, self.s0 = \ + check_params(self.alpha_c, self.alpha, self.beta, self.gamma, + c0=self.c0, u0=self.u0, s0=self.s0) + exp_list, exp_sw_list = generate_exp(typed_tau_list, + self.t_sw_array[:switch], + self.alpha_c, + self.alpha, + self.beta, + self.gamma, + scale_cc=self.scale_cc, + model=self.model, + rna_only=self.rna_only) + rescale_factor = np.array([self.rescale_c, self.rescale_u, 1.0]) + exp_list = [x*rescale_factor for x in exp_list] + exp_sw_list = [x*rescale_factor for x in exp_sw_list] + c = np.ravel(np.concatenate([exp_list[x][:, 0] + for x in range(switch+1)])) + u = np.ravel(np.concatenate([exp_list[x][:, 1] + for x in range(switch+1)])) + s = np.ravel(np.concatenate([exp_list[x][:, 2] + for x in range(switch+1)])) + c_sw = np.ravel(np.concatenate([exp_sw_list[x][:, 0] + for x in range(switch)])) + u_sw = np.ravel(np.concatenate([exp_sw_list[x][:, 1] + for x in range(switch)])) + s_sw = np.ravel(np.concatenate([exp_sw_list[x][:, 2] + for x in range(switch)])) + self.alpha_c, self.alpha, self.beta, self.gamma = \ + check_params(self.alpha_c, self.alpha, self.beta, self.gamma) + vc, vu, vs = compute_velocity(anchor_time, + self.t_sw_array, + None, + self.alpha_c, + self.alpha, + self.beta, + self.gamma, + self.rescale_c, + self.rescale_u, + scale_cc=self.scale_cc, + model=self.model, + rna_only=self.rna_only) + + # scale and shift back to original scale + c_ = c * self.scale_c + self.offset_c + u_ = u * self.scale_u + self.offset_u + s_ = s * self.scale_s + self.offset_s + c_sw_ = c_sw * self.scale_c + self.offset_c + u_sw_ = u_sw * self.scale_u + self.offset_u + s_sw_ = s_sw * self.scale_s + self.offset_s + vc = vc * self.scale_c + vu = vu * self.scale_u + vs = vs * self.scale_s + + self.anchor_exp = np.empty((len(u_), 3)) + self.anchor_exp[:, 0], self.anchor_exp[:, 1], self.anchor_exp[:, 2] = \ + c_, u_, s_ + self.anchor_exp_sw = np.empty((len(u_sw_), 3)) + self.anchor_exp_sw[:, 0], self.anchor_exp_sw[:, 1], \ + self.anchor_exp_sw[:, 2] = c_sw_, u_sw_, s_sw_ + self.anchor_velo = np.empty((len(u_), 3)) + self.anchor_velo[:, 0] = vc + self.anchor_velo[:, 1] = vu + self.anchor_velo[:, 2] = vs + self.anchor_velo_min_idx = np.sum(anchor_time < np.min(new_time)) + self.anchor_velo_max_idx = np.sum(anchor_time < np.max(new_time)) - 1 + + if self.save_plot: + main_info('saving plots..', indent_level=1) + self.save_dyn_plot(c_, u_, s_, c_sw_, u_sw_, s_sw_, tau_list) + + self.realign_time_and_velocity(c, u, s, anchor_time) + + main_info(f'final params:\nswitch time array = {self.t_sw_array},\n' + f'rates = {self.rates},\ncc scale = {self.scale_cc},\n' + f'c rescale factor = {self.rescale_c},\n' + f'u rescale factor = {self.rescale_u}', + indent_level=1) + main_info(f'final loss: {self.loss[-1]}', indent_level=1) + main_info(f'final likelihood: {self.likelihood}', indent_level=1) + + return self.loss + + # the adam algorithm + # NOTE: The starting point for this function was an excample on the + # GeeksForGeeks website. The particular article is linked below: + # www.geeksforgeeks.org/how-to-implement-adam-gradient-descent-from-scratch-using-python/ + def AdamMin(self, x, n_iter, tol, eps=1e-8): + + n = len(x) + + x_ten = torch.tensor(x, requires_grad=True, device=self.device, + dtype=self.torch_type) + + # record lowest loss as a benchmark + # (right now the lowest loss is the current loss) + lowest_loss = torch.tensor(np.array(self.loss[-1], dtype=self.u.dtype), + device=self.device, + dtype=self.torch_type) + + # record the tensor of the parameters that cause the lowest loss + lowest_x_ten = x_ten + + # the m and v variables used in the adam calculations + m = torch.zeros(n, device=self.device, requires_grad=True, + dtype=self.torch_type) + v = torch.zeros(n, device=self.device, requires_grad=True, + dtype=self.torch_type) + + # the update amount to add to the x tensor after the appropriate + # calculations are made + u = torch.ones(n, device=self.device, requires_grad=True, + dtype=self.torch_type) * float("inf") + + # how many times the new loss is lower than the lowest loss + update_count = 0 + + iterations = 0 + + # run the gradient descent updates + for t in range(n_iter): + + iterations += 1 + + # calculate the loss + loss = self.mse_ten(x_ten) + + # if the loss is lower than the lowest loss... + if loss < lowest_loss: + + # record the new best tensor + lowest_x_ten = x_ten + update_count += 1 + + # if the percentage difference in x tensors and loss values + # is less than the tolerance parameter and we've update the + # loss 3 times by now... + if torch.all((torch.abs(u) / lowest_x_ten) < tol) and \ + (torch.abs(loss - lowest_loss) / lowest_loss) < tol and \ + update_count >= 3: + + # ...we've updated enough. Break! + break + + # record the new lowest loss + lowest_loss = loss + + # take the gradient of mse w/r/t our current parameter values + loss.backward(inputs=x_ten) + g = x_ten.grad + + # calculate the new update value using the Adam formula + m = (self.adam_beta1 * m) + ((1.0 - self.adam_beta1) * g) + v = (self.adam_beta2 * v) + ((1.0 - self.adam_beta2) * g * g) + + mhat = m / (1.0 - (self.adam_beta1**(t+1))) + vhat = v / (1.0 - (self.adam_beta2**(t+1))) + + u = -(self.adam_lr * mhat) / (torch.sqrt(vhat) + eps) + + # update the x tensor + x_ten = x_ten + u + + # as long as we've found at least one better x tensor... + if update_count > 1: + + # record the final lowest loss + if loss < lowest_loss: + lowest_loss = loss + + # set the new loss for the gene to the new lowest loss + self.cur_loss = lowest_loss.item() + + # use the update() function so the gene's parameters + # are the new best one we found + updated = self.update(lowest_x_ten.cpu().detach().numpy()) + + # if we never found a better x tensor, then the return value should + # state that we did not update it + else: + updated = False + + # return whether we updated the x tensor or not + return updated + + def fit_dyn(self): + + while self.cur_iter < self.max_iter: + self.cur_iter += 1 + + # RNA-only + if self.rna_only: + main_info('Nelder Mead on t_sw_2 and alpha..', indent_level=2) + self.fitting_flag_ = 0 + if self.cur_iter == 1: + var_test = (self.alpha + + np.array([-2, -1, -0.5, 0.5, 1, 2]) * 0.1 + * self.alpha) + new_params = self.params.copy() + for var in var_test: + new_params[4] = var + self.update(new_params, adjust_time=False, + penalize_gap=False) + res = minimize(self.mse, x0=[self.params[1], self.params[4]], + method='Nelder-Mead', tol=1e-2, + callback=self.update, options={'maxiter': 3}) + + if self.fit_rescale: + main_info('Nelder Mead on t_sw_2, beta, and rescale u..', + indent_level=2) + res = minimize(self.mse, x0=[self.params[1], + self.params[5], + self.params[9]], + method='Nelder-Mead', tol=1e-2, + callback=self.update, + options={'maxiter': 5}) + + main_info('Nelder Mead on alpha and gamma..', indent_level=2) + self.fitting_flag_ = 1 + res = minimize(self.mse, x0=[self.params[4], self.params[6]], + method='Nelder-Mead', tol=1e-2, + callback=self.update, options={'maxiter': 3}) + + main_info('Nelder Mead on t_sw_2..', indent_level=2) + res = minimize(self.mse, x0=[self.params[1]], + method='Nelder-Mead', tol=1e-2, + callback=self.update, options={'maxiter': 2}) + + main_info('Full Nelder Mead..', indent_level=2) + res = minimize(self.mse, x0=[self.params[1], self.params[4], + self.params[5], self.params[6]], + method='Nelder-Mead', tol=1e-2, + callback=self.update, options={'maxiter': 5}) + + # chromatin-RNA + else: + + if not self.adam: + main_info('Nelder Mead on t_sw_1, chromatin switch time,' + 'and alpha_c..', indent_level=2) + self.fitting_flag_ = 1 + if self.cur_iter == 1: + var_test = (self.gamma + np.array([-1, -0.5, 0.5, 1]) + * 0.1 * self.gamma) + new_params = self.params.copy() + for var in var_test: + new_params[6] = var + self.update(new_params, adjust_time=False) + if self.model == 0 or self.model == 1: + res = minimize(self.mse, x0=[self.params[0], + self.params[1], + self.params[3]], + method='Nelder-Mead', tol=1e-2, + callback=self.update, + options={'maxiter': 20}) + elif self.model == 2: + res = minimize(self.mse, x0=[self.params[0], + self.params[2], + self.params[3]], + method='Nelder-Mead', tol=1e-2, + callback=self.update, + options={'maxiter': 20}) + + main_info('Nelder Mead on chromatin switch time,' + 'chromatin closing rate scaling, and rescale' + 'c..', indent_level=2) + self.fitting_flag_ = 2 + if self.model == 0 or self.model == 1: + res = minimize(self.mse, x0=[self.params[1], + self.params[7], + self.params[8]], + method='Nelder-Mead', tol=1e-2, + callback=self.update, + options={'maxiter': 20}) + elif self.model == 2: + res = minimize(self.mse, x0=[self.params[2], + self.params[7], + self.params[8]], + method='Nelder-Mead', tol=1e-2, + callback=self.update, + options={'maxiter': 20}) + + main_info('Nelder Mead on rna switch time and alpha..', + indent_level=2) + self.fitting_flag_ = 1 + if self.model == 0 or self.model == 1: + res = minimize(self.mse, x0=[self.params[2], + self.params[4]], + method='Nelder-Mead', tol=1e-2, + callback=self.update, + options={'maxiter': 10}) + elif self.model == 2: + res = minimize(self.mse, x0=[self.params[1], + self.params[4]], + method='Nelder-Mead', tol=1e-2, + callback=self.update, + options={'maxiter': 10}) + + main_info('Nelder Mead on rna switch time, beta, and ' + 'rescale u..', indent_level=2) + self.fitting_flag_ = 3 + if self.model == 0 or self.model == 1: + res = minimize(self.mse, x0=[self.params[2], + self.params[5], + self.params[9]], + method='Nelder-Mead', tol=1e-2, + callback=self.update, + options={'maxiter': 20}) + elif self.model == 2: + res = minimize(self.mse, x0=[self.params[1], + self.params[5], + self.params[9]], + method='Nelder-Mead', tol=1e-2, + callback=self.update, + options={'maxiter': 20}) + + main_info('Nelder Mead on alpha and gamma..', indent_level=2) + self.fitting_flag_ = 2 + res = minimize(self.mse, x0=[self.params[4], + self.params[6]], + method='Nelder-Mead', tol=1e-2, + callback=self.update, + options={'maxiter': 10}) + + main_info('Nelder Mead on t_sw..', indent_level=2) + self.fitting_flag_ = 4 + res = minimize(self.mse, x0=self.params[:3], + method='Nelder-Mead', tol=1e-2, + callback=self.update, + options={'maxiter': 20}) + + else: + + main_info('Adam on all parameters', indent_level=2) + self.AdamMin(np.array(self.params, dtype=self.u.dtype), 20, + tol=1e-2) + + main_info('Nelder Mead on t_sw..', indent_level=2) + self.fitting_flag_ = 4 + res = minimize(self.mse, x0=self.params[:3], + method='Nelder-Mead', tol=1e-2, + callback=self.update, + options={'maxiter': 15}) + + main_info(f'iteration {self.cur_iter} finished', indent_level=2) + + def _variables(self, x): + scale_cc = self.scale_cc + rescale_c = self.rescale_c + rescale_u = self.rescale_u + + # RNA-only + if self.rna_only: + if len(x) == 1: # fit t_sw_2 + t3 = np.array([self.t_sw_1, x[0], + self.t_sw_3 - self.t_sw_1 - x[0]]) + r4 = self.rates + + elif len(x) == 2: + if self.fitting_flag_: # fit alpha and gamma + t3 = self.params[:3] + r4 = np.array([self.alpha_c, x[0], self.beta, x[1]]) + else: # fit t_sw_2 and alpha + t3 = np.array([self.t_sw_1, x[0], + self.t_sw_3 - self.t_sw_1 - x[0]]) + r4 = np.array([self.alpha_c, x[1], self.beta, self.gamma]) + + elif len(x) == 3: # fit t_sw_2, beta, and rescale u + t3 = np.array([self.t_sw_1, + x[0], self.t_sw_3 - self.t_sw_1 - x[0]]) + r4 = np.array([self.alpha_c, self.alpha, x[1], self.gamma]) + rescale_u = x[2] + + elif len(x) == 4: # fit all + t3 = np.array([self.t_sw_1, x[0], self.t_sw_3 - self.t_sw_1 + - x[0]]) + r4 = np.array([self.alpha_c, x[1], x[2], x[3]]) + + elif len(x) == 10: # all available + t3 = x[:3] + r4 = x[3:7] + scale_cc = x[7] + rescale_c = x[8] + rescale_u = x[9] + + else: + return + + # chromatin-RNA + else: + + if len(x) == 2: + if self.fitting_flag_ == 1: # fit rna switch time and alpha + if self.model == 0 or self.model == 1: + t3 = np.array([self.t_sw_1, self.params[1], x[0]]) + elif self.model == 2: + t3 = np.array([self.t_sw_1, x[0], + self.t_sw_3 - self.t_sw_1 - x[0]]) + r4 = np.array([self.alpha_c, x[1], self.beta, self.gamma]) + elif self.fitting_flag_ == 2: # fit alpha and gamma + t3 = self.params[:3] + r4 = np.array([self.alpha_c, x[0], self.beta, x[1]]) + + elif len(x) == 3: + # fit t_sw_1, chromatin switch time, and alpha_c + if self.fitting_flag_ == 1: + if self.model == 0 or self.model == 1: + t3 = np.array([x[0], x[1], self.t_sw_3 - x[0] - x[1]]) + elif self.model == 2: + t3 = np.array([x[0], self.t_sw_2 - x[0], x[1]]) + r4 = np.array([x[2], self.alpha, self.beta, self.gamma]) + # fit chromatin switch time, chromatin closing rate scaling, + # and rescale c + elif self.fitting_flag_ == 2: + if self.model == 0 or self.model == 1: + t3 = np.array([self.t_sw_1, x[0], + self.t_sw_3 - self.t_sw_1 - x[0]]) + elif self.model == 2: + t3 = np.array([self.t_sw_1, self.params[1], x[0]]) + r4 = self.rates + scale_cc = x[1] + rescale_c = x[2] + # fit rna switch time, beta, and rescale u + elif self.fitting_flag_ == 3: + if self.model == 0 or self.model == 1: + t3 = np.array([self.t_sw_1, self.params[1], x[0]]) + elif self.model == 2: + t3 = np.array([self.t_sw_1, x[0], + self.t_sw_3 - self.t_sw_1 - x[0]]) + r4 = np.array([self.alpha_c, self.alpha, x[1], self.gamma]) + rescale_u = x[2] + # fit three switch times + elif self.fitting_flag_ == 4: + t3 = x + r4 = self.rates + + elif len(x) == 7: + t3 = x[:3] + r4 = x[3:] + + elif len(x) == 10: + t3 = x[:3] + r4 = x[3:7] + scale_cc = x[7] + rescale_c = x[8] + rescale_u = x[9] + + else: + return + + # clip to meaningful values + if self.fitting_flag_ and not self.adam: + scale_cc = np.clip(scale_cc, + np.max([0.5*self.scale_cc, 0.25]), + np.min([2*self.scale_cc, 4])) + + if not self.known_pars: + if self.fit_decoupling: + t3 = np.clip(t3, 0.1, None) + else: + t3[2] = 30 / self.n_anchors + t3[:2] = np.clip(t3[:2], 0.1, None) + r4 = np.clip(r4, 0.001, 1000) + rescale_c = np.clip(rescale_c, 0.75, 1.5) + rescale_u = np.clip(rescale_u, 0.2, 3) + + return t3, r4, scale_cc, rescale_c, rescale_u + + # the tensor version of the calculate_dist_and_time function + def calculate_dist_and_time_ten(self, + c, u, s, + t_sw_array, + alpha_c, alpha, beta, gamma, + rescale_c, rescale_u, + scale_cc=1, + scale_factor=None, + model=1, + conn=None, + t=1000, k=1, + direction='complete', + total_h=20, + rna_only=False, + penalize_gap=True, + all_cells=True): + + conn = torch.tensor(conn.todense(), + device=self.device, + dtype=self.torch_type) + + c_ten = torch.tensor(c, device=self.device, dtype=self.torch_type) + u_ten = torch.tensor(u, device=self.device, dtype=self.torch_type) + s_ten = torch.tensor(s, device=self.device, dtype=self.torch_type) + + n = len(u) + if scale_factor is None: + scale_factor_ten = torch.stack((torch.std(c_ten), torch.std(u_ten), + torch.std(s_ten))) + else: + scale_factor_ten = torch.tensor(scale_factor, device=self.device, + dtype=self.torch_type) + + tau_list = self.anchor_points_ten(t_sw_array, total_h, t) + + switch = torch.sum(t_sw_array < total_h) + + exp_list, exp_sw_list = self.generate_exp_tens(tau_list, + t_sw_array[:switch], + alpha_c, + alpha, + beta, + gamma, + model=model, + scale_cc=scale_cc, + rna_only=rna_only) + + rescale_factor = torch.stack((rescale_c, rescale_u, + torch.tensor(1.0, device=self.device, + requires_grad=True, + dtype=self.torch_type))) + + for i in range(len(exp_list)): + exp_list[i] = exp_list[i]*rescale_factor + + if i < len(exp_list)-1: + exp_sw_list[i] = exp_sw_list[i]*rescale_factor + + max_c = 0 + max_u = 0 + max_s = 0 + + if rna_only: + exp_mat = (torch.hstack((torch.reshape(u_ten, (-1, 1)), + torch.reshape(s_ten, (-1, 1)))) + / scale_factor_ten[1:]) + else: + exp_mat = torch.hstack((torch.reshape(c_ten, (-1, 1)), + torch.reshape(u_ten, (-1, 1)), + torch.reshape(s_ten, (-1, 1))))\ + / scale_factor_ten + + taus = torch.zeros((1, n), device=self.device, + requires_grad=True, + dtype=self.torch_type) + anchor_exp, anchor_t = None, None + + dists0 = torch.full((1, n), 0.0 if direction == "on" + or direction == "complete" else np.inf, + device=self.device, + requires_grad=True, + dtype=self.torch_type) + dists1 = torch.full((1, n), 0.0 if direction == "on" + or direction == "complete" else np.inf, + device=self.device, + requires_grad=True, + dtype=self.torch_type) + dists2 = torch.full((1, n), 0.0 if direction == "off" + or direction == "complete" else np.inf, + device=self.device, + requires_grad=True, + dtype=self.torch_type) + dists3 = torch.full((1, n), 0.0 if direction == "off" + or direction == "complete" else np.inf, + device=self.device, + requires_grad=True, + dtype=self.torch_type) + + ts0 = torch.zeros((1, n), device=self.device, + requires_grad=True, + dtype=self.torch_type) + ts1 = torch.zeros((1, n), device=self.device, + requires_grad=True, + dtype=self.torch_type) + ts2 = torch.zeros((1, n), device=self.device, + requires_grad=True, + dtype=self.torch_type) + ts3 = torch.zeros((1, n), device=self.device, + requires_grad=True, + dtype=self.torch_type) + + for i in range(switch+1): + + if not all_cells: + max_ci = (torch.max(exp_list[i][:, 0]) + if exp_list[i].shape[0] > 0 + else 0) + max_c = max_ci if max_ci > max_c else max_c + max_ui = torch.max(exp_list[i][:, 1]) if exp_list[i].shape[0] > 0 \ + else 0 + max_u = max_ui if max_ui > max_u else max_u + max_si = torch.max(exp_list[i][:, 2]) if exp_list[i].shape[0] > 0 \ + else 0 + max_s = max_si if max_si > max_s else max_s + + skip_phase = False + if direction == 'off': + if (model in [1, 2]) and (i < 2): + skip_phase = True + elif direction == 'on': + if (model in [1, 2]) and (i >= 2): + skip_phase = True + if rna_only and i == 0: + skip_phase = True + + if not skip_phase: + if rna_only: + tmp = exp_list[i][:, 1:] / scale_factor_ten[1:] + else: + tmp = exp_list[i] / scale_factor_ten + if anchor_exp is None: + anchor_exp = exp_list[i] + anchor_t = (tau_list[i] + t_sw_array[i-1] if i >= 1 + else tau_list[i]) + else: + anchor_exp = torch.vstack((anchor_exp, exp_list[i])) + anchor_t = torch.hstack((anchor_t, + tau_list[i] + t_sw_array[i-1] + if i >= 1 else tau_list[i])) + + if not all_cells: + anchor_prepend_rna = torch.zeros((1, 2), + device=self.device, + dtype=self.torch_type) + anchor_prepend_chrom = torch.zeros((1, 3), + device=self.device, + dtype=self.torch_type) + anchor_dist = torch.diff(tmp, dim=0, + prepend=anchor_prepend_rna + if rna_only + else anchor_prepend_chrom) + + anchor_dist = torch.sqrt((anchor_dist*anchor_dist) + .sum(axis=1)) + remove_cand = anchor_dist < (0.01*torch.max(exp_mat[1]) + if rna_only + else + 0.01*torch.max(exp_mat[2])) + step_idx = torch.arange(0, anchor_dist.size()[0], 1, + device=self.device, + dtype=self.torch_type) % 3 > 0 + remove_cand &= step_idx + keep_idx = torch.where(~remove_cand)[0] + + tmp = tmp[keep_idx, :] + + model = NearestNeighbors(n_neighbors=k, output_type="numpy") + model.fit(tmp.detach()) + dd, ii = model.kneighbors(exp_mat.detach()) + ii = ii.T[0] + + new_dd = ((exp_mat[:, 0] - tmp[ii, 0]) + * (exp_mat[:, 0] - tmp[ii, 0]) + + (exp_mat[:, 1] - tmp[ii, 1]) + * (exp_mat[:, 1] - tmp[ii, 1]) + + (exp_mat[:, 2] - tmp[ii, 2]) + * (exp_mat[:, 2] - tmp[ii, 2])) + + if k > 1: + new_dd = torch.mean(new_dd, dim=1) + if conn is not None: + new_dd = torch.matmul(conn, new_dd) + + if i == 0: + dists0 = dists0 + new_dd + elif i == 1: + dists1 = dists1 + new_dd + elif i == 2: + dists2 = dists2 + new_dd + elif i == 3: + dists3 = dists3 + new_dd + + if not all_cells: + ii = keep_idx[ii] + if k == 1: + taus = tau_list[i][ii] + else: + for j in range(n): + taus[j] = tau_list[i][ii[j, :]] + + if i == 0: + ts0 = ts0 + taus + elif i == 1: + ts1 = ts1 + taus + t_sw_array[0] + elif i == 2: + ts2 = ts2 + taus + t_sw_array[1] + elif i == 3: + ts3 = ts3 + taus + t_sw_array[2] + + dists = torch.cat((dists0, dists1, dists2, dists3), 0) + + ts = torch.cat((ts0, ts1, ts2, ts3), 0) + + state_pred = torch.argmin(dists, axis=0) + + t_pred = ts[state_pred, torch.arange(n, device=self.device)] + + anchor_t1_list = [] + anchor_t2_list = [] + + t_sw_adjust = torch.zeros(3, device=self.device, dtype=self.torch_type) + + if direction == 'complete': + + dist_gap_add = torch.zeros((1, n), device=self.device, + dtype=self.torch_type) + + t_sorted = torch.clone(t_pred) + t_sorted, t_sorted_indices = torch.sort(t_sorted) + + dt = torch.diff(t_sorted, dim=0, + prepend=torch.zeros(1, device=self.device, + dtype=self.torch_type)) + + gap_thresh = 3*torch.quantile(dt, 0.99) + + idx = torch.where(dt > gap_thresh)[0] + + if len(idx) > 0 and penalize_gap: + h_tens = torch.tensor([total_h], device=self.device, + dtype=self.torch_type) + + for i in idx: + + t1 = t_sorted[i-1] if i > 0 else 0 + t2 = t_sorted[i] + anchor_t1 = anchor_exp[torch.argmin(torch.abs(anchor_t - t1)), + :] + anchor_t2 = anchor_exp[torch.argmin(torch.abs(anchor_t - t2)), + :] + if all_cells: + anchor_t1_list.append(torch.ravel(anchor_t1)) + anchor_t2_list.append(torch.ravel(anchor_t2)) + if not all_cells: + for j in range(1, switch): + crit1 = ((t1 > t_sw_array[j-1]) + and (t2 > t_sw_array[j-1]) + and (t1 <= t_sw_array[j]) + and (t2 <= t_sw_array[j])) + crit2 = ((torch.abs(anchor_t1[2] + - exp_sw_list[j][0, 2]) + < 0.02 * max_s) and + (torch.abs(anchor_t2[2] + - exp_sw_list[j][0, 2]) + < 0.01 * max_s)) + crit3 = ((torch.abs(anchor_t1[1] + - exp_sw_list[j][0, 1]) + < 0.02 * max_u) and + (torch.abs(anchor_t2[1] + - exp_sw_list[j][0, 1]) + < 0.01 * max_u)) + crit4 = ((torch.abs(anchor_t1[0] + - exp_sw_list[j][0, 0]) + < 0.02 * max_c) and + (torch.abs(anchor_t2[0] + - exp_sw_list[j][0, 0]) + < 0.01 * max_c)) + if crit1 and crit2 and crit3 and crit4: + t_sw_adjust[j] += t2 - t1 + if penalize_gap: + dist_gap = torch.sum(((anchor_t1[1:] - anchor_t2[1:]) / + scale_factor_ten[1:])**2) + + idx_to_adjust = torch.tensor(t_pred >= t2, + device=self.device) + + idx_to_adjust = torch.reshape(idx_to_adjust, + (1, idx_to_adjust.size()[0])) + + true_tensor = torch.tensor([True], device=self.device) + false_tensor = torch.tensor([False], device=self.device) + + t_sw_array_ = torch.cat((t_sw_array, h_tens), dim=0) + state_to_adjust = torch.where(t_sw_array_ > t2, + true_tensor, false_tensor) + + dist_gap_add[idx_to_adjust] += dist_gap + + if state_to_adjust[0].item(): + dists0 += dist_gap_add + if state_to_adjust[1].item(): + dists1 += dist_gap_add + if state_to_adjust[2].item(): + dists2 += dist_gap_add + if state_to_adjust[3].item(): + dists3 += dist_gap_add + + dist_gap_add[idx_to_adjust] -= dist_gap + + dists = torch.cat((dists0, dists1, dists2, dists3), 0) + + state_pred = torch.argmin(dists, dim=0) + + if all_cells: + t_pred = ts[torch.arange(n, device=self.device), state_pred] + + min_dist = torch.min(dists, dim=0).values + + if all_cells: + exp_ss_mat = compute_ss_exp(alpha_c, alpha, beta, gamma, + model=model) + if rna_only: + exp_ss_mat[:, 0] = 1 + dists_ss = pairwise_distance_square(exp_mat, exp_ss_mat * + rescale_factor / scale_factor) + + reach_ss = np.full((n, 4), False) + for i in range(n): + for j in range(4): + if min_dist[i] > dists_ss[i, j]: + reach_ss[i, j] = True + late_phase = np.full(n, -1) + for i in range(3): + late_phase[torch.abs(t_pred - t_sw_array[i]) < 0.1] = i + + return min_dist, t_pred, state_pred.cpu().detach().numpy(), \ + reach_ss, late_phase, max_u, max_s, anchor_t1_list, \ + anchor_t2_list + + else: + return min_dist, state_pred.cpu().detach().numpy(), max_u, max_s, \ + t_sw_adjust.cpu().detach().numpy() + + # the torch tensor version of the mse function + def mse_ten(self, x, fit_outlier=False, + penalize_gap=True): + + t3 = x[:3] + r4 = x[3:7] + scale_cc = x[7] + rescale_c = x[8] + rescale_u = x[9] + + if not self.known_pars: + if self.fit_decoupling: + t3 = torch.clip(t3, 0.1, None) + else: + t3[2] = 30 / self.n_anchors + t3[:2] = torch.clip(t3[:2], 0.1, None) + r4 = torch.clip(r4, 0.001, 1000) + rescale_c = torch.clip(rescale_c, 0.75, 1.5) + rescale_u = torch.clip(rescale_u, 0.2, 3) + + t_sw_array = torch.cumsum(t3, dim=0) + + if self.rna_only: + t_sw_array[2] = 20 + + # conditions for minimum switch time and rate params + penalty = 0 + if any(t3 < 0.2) or any(r4 < 0.005): + penalty = (torch.sum(0.2 - t3[t3 < 0.2]) if self.fit_decoupling + else torch.sum(0.2 - t3[:2][t3[:2] < 0.2])) + penalty += torch.sum(0.005 - r4[r4 < 0.005]) * 1e2 + + # condition for all params + if any(x > 500): + penalty = torch.sum(x[x > 500] - 500) * 1e-2 + + c_array = self.c_all if fit_outlier else self.c + u_array = self.u_all if fit_outlier else self.u + s_array = self.s_all if fit_outlier else self.s + + if self.batch_size is not None and self.batch_size < len(c_array): + + subset_choice = np.random.choice(len(c_array), self.batch_size, + replace=False) + + c_array = c_array[subset_choice] + u_array = u_array[subset_choice] + s_array = s_array[subset_choice] + + if fit_outlier: + conn_for_calc = self.conn[subset_choice] + if not fit_outlier: + conn_for_calc = self.conn_sub[subset_choice] + + conn_for_calc = ((conn_for_calc.T)[subset_choice]).T + + else: + + if fit_outlier: + conn_for_calc = self.conn + if not fit_outlier: + conn_for_calc = self.conn_sub + + scale_factor_func = np.array(self.scale_factor, dtype=self.u.dtype) + + # distances and time assignments + res = self.calculate_dist_and_time_ten(c_array, + u_array, + s_array, + t_sw_array, + r4[0], + r4[1], + r4[2], + r4[3], + rescale_c, + rescale_u, + scale_cc=scale_cc, + scale_factor=scale_factor_func, + model=self.model, + direction=self.direction, + conn=conn_for_calc, + k=self.k_dist, + t=self.n_anchors, + rna_only=self.rna_only, + penalize_gap=penalize_gap, + all_cells=fit_outlier) + + if fit_outlier: + min_dist, t_pred, state_pred, reach_ss, late_phase, max_u, max_s, \ + self.anchor_t1_list, self.anchor_t2_list = res + else: + min_dist, state_pred, max_u, max_s, t_sw_adjust = res + + loss = torch.mean(min_dist) + + # avoid exceeding maximum expressions + reg = torch.max(torch.tensor([0, max_s - torch.tensor(self.max_s)], + requires_grad=True, + dtype=self.torch_type))\ + + torch.max(torch.tensor([0, max_u - torch.tensor(self.max_u)], + requires_grad=True, + dtype=self.torch_type)) + + loss += reg + + loss += 1e-1 * penalty + + self.cur_loss = loss.item() + self.cur_state_pred = state_pred + + if fit_outlier: + return loss, t_pred + else: + self.cur_t_sw_adjust = t_sw_adjust + + return loss + + def mse(self, x, fit_outlier=False, penalize_gap=True): + x = np.array(x) + + t3, r4, scale_cc, rescale_c, rescale_u = self._variables(x) + + t_sw_array = np.array([t3[0], t3[0]+t3[1], t3[0]+t3[1]+t3[2]]) + if self.rna_only: + t_sw_array[2] = 20 + + # conditions for minimum switch time and rate params + penalty = 0 + if any(t3 < 0.2) or any(r4 < 0.005): + penalty = (np.sum(0.2 - t3[t3 < 0.2]) if self.fit_decoupling + else np.sum(0.2 - t3[:2][t3[:2] < 0.2])) + penalty += np.sum(0.005 - r4[r4 < 0.005]) * 1e2 + + # condition for all params + if any(x > 500): + penalty = np.sum(x[x > 500] - 500) * 1e-2 + + c_array = self.c_all if fit_outlier else self.c + u_array = self.u_all if fit_outlier else self.u + s_array = self.s_all if fit_outlier else self.s + + if self.neural_net: + + res = calculate_dist_and_time_nn(c_array, + u_array, + s_array, + self.max_u_all if fit_outlier + else self.max_u, + self.max_s_all if fit_outlier + else self.max_s, + t_sw_array, + r4[0], + r4[1], + r4[2], + r4[3], + rescale_c, + rescale_u, + self.ode_model_0, + self.ode_model_1, + self.ode_model_2_m1, + self.ode_model_2_m2, + self.device, + scale_cc=scale_cc, + scale_factor=self.scale_factor, + model=self.model, + direction=self.direction, + conn=self.conn if fit_outlier + else self.conn_sub, + k=self.k_dist, + t=self.n_anchors, + rna_only=self.rna_only, + penalize_gap=penalize_gap, + all_cells=fit_outlier) + + if fit_outlier: + min_dist, t_pred, state_pred, max_u, max_s, nn_penalty = res + else: + min_dist, state_pred, max_u, max_s, nn_penalty = res + + penalty += nn_penalty + + t_sw_adjust = [0, 0, 0] + + else: + + # distances and time assignments + res = calculate_dist_and_time(c_array, + u_array, + s_array, + t_sw_array, + r4[0], + r4[1], + r4[2], + r4[3], + rescale_c, + rescale_u, + scale_cc=scale_cc, + scale_factor=self.scale_factor, + model=self.model, + direction=self.direction, + conn=self.conn if fit_outlier + else self.conn_sub, + k=self.k_dist, + t=self.n_anchors, + rna_only=self.rna_only, + penalize_gap=penalize_gap, + all_cells=fit_outlier) + + if fit_outlier: + min_dist, t_pred, state_pred, reach_ss, late_phase, max_u, \ + max_s, self.anchor_t1_list, self.anchor_t2_list = res + else: + min_dist, state_pred, max_u, max_s, t_sw_adjust = res + + loss = np.mean(min_dist) + + # avoid exceeding maximum expressions + reg = np.max([0, max_s - self.max_s]) + np.max([0, max_u - self.max_u]) + loss += reg + + loss += 1e-1 * penalty + self.cur_loss = loss + self.cur_state_pred = state_pred + + if fit_outlier: + return loss, t_pred + else: + self.cur_t_sw_adjust = t_sw_adjust + + return loss + + def update(self, x, perform_update=False, initialize=False, + fit_outlier=False, adjust_time=True, penalize_gap=True, + plot=True): + t3, r4, scale_cc, rescale_c, rescale_u = self._variables(x) + t_sw_array = np.array([t3[0], t3[0]+t3[1], t3[0]+t3[1]+t3[2]]) + + # read results + if initialize: + new_loss = self.mse(x, penalize_gap=penalize_gap) + elif fit_outlier: + new_loss, t_pred = self.mse(x, fit_outlier=True, + penalize_gap=penalize_gap) + else: + new_loss = self.cur_loss + t_sw_adjust = self.cur_t_sw_adjust + state_pred = self.cur_state_pred + + if new_loss < self.loss[-1] or perform_update: + perform_update = True + + self.loss.append(new_loss) + self.alpha_c, self.alpha, self.beta, self.gamma = r4 + self.rates = r4 + self.scale_cc = scale_cc + self.rescale_c = rescale_c + self.rescale_u = rescale_u + + # adjust overcrowded anchors + if not fit_outlier and adjust_time: + t_sw_array -= np.cumsum(t_sw_adjust) + if self.rna_only: + t_sw_array[2] = 20 + + self.t_sw_1, self.t_sw_2, self.t_sw_3 = t_sw_array + self.t_sw_array = t_sw_array + self.params = np.array([self.t_sw_1, + self.t_sw_2-self.t_sw_1, + self.t_sw_3-self.t_sw_2, + self.alpha_c, + self.alpha, + self.beta, + self.gamma, + self.scale_cc, + self.rescale_c, + self.rescale_u]) + if not initialize: + self.state = state_pred + if fit_outlier: + self.t = t_pred + + main_info(f'params updated as: {self.t_sw_array} {self.rates} ' + f'{self.scale_cc} {self.rescale_c} {self.rescale_u}', + indent_level=2) + + # interactive plot + if self.plot and plot: + tau_list = anchor_points(self.t_sw_array, 20, self.n_anchors) + switch = np.sum(self.t_sw_array < 20) + typed_tau_list = List() + [typed_tau_list.append(x) for x in tau_list] + self.alpha_c, self.alpha, self.beta, self.gamma, \ + self.c0, self.u0, self.s0 = \ + check_params(self.alpha_c, self.alpha, self.beta, + self.gamma, c0=self.c0, u0=self.u0, + s0=self.s0) + exp_list, exp_sw_list = generate_exp(typed_tau_list, + self.t_sw_array[:switch], + self.alpha_c, + self.alpha, + self.beta, + self.gamma, + scale_cc=self.scale_cc, + model=self.model, + rna_only=self.rna_only) + rescale_factor = np.array([self.rescale_c, + self.rescale_u, + 1.0]) + exp_list = [x*rescale_factor for x in exp_list] + exp_sw_list = [x*rescale_factor for x in exp_sw_list] + c = np.ravel(np.concatenate([exp_list[x][:, 0] for x in + range(switch+1)])) + u = np.ravel(np.concatenate([exp_list[x][:, 1] for x in + range(switch+1)])) + s = np.ravel(np.concatenate([exp_list[x][:, 2] for x in + range(switch+1)])) + c_ = self.c_all if fit_outlier else self.c + u_ = self.u_all if fit_outlier else self.u + s_ = self.s_all if fit_outlier else self.s + self.ax.clear() + plt.pause(0.1) + if self.rna_only: + self.ax.scatter(s, u, s=self.point_size*1.5, c='black', + alpha=0.6, zorder=2) + if switch >= 1: + c_sw1, u_sw1, s_sw1 = exp_sw_list[0][0] + self.ax.plot([s_sw1], [u_sw1], "om", + markersize=self.point_size, zorder=5) + if switch >= 2: + c_sw2, u_sw2, s_sw2 = exp_sw_list[1][0] + self.ax.plot([s_sw2], [u_sw2], "Xm", + markersize=self.point_size, zorder=5) + if switch == 3: + c_sw3, u_sw3, s_sw3 = exp_sw_list[2][0] + self.ax.plot([s_sw3], [u_sw3], "Dm", + markersize=self.point_size, zorder=5) + if np.max(self.t) == 20: + self.ax.plot([s[-1]], [u[-1]], "*m", + markersize=self.point_size, zorder=5) + for i in range(4): + if any(self.state == i): + self.ax.scatter(s_[(self.state == i)], + u_[(self.state == i)], + s=self.point_size, c=self.color[i]) + self.ax.set_xlabel('s') + self.ax.set_ylabel('u') + + else: + self.ax.scatter(s, u, c, s=self.point_size*1.5, + c='black', alpha=0.6, zorder=2) + if switch >= 1: + c_sw1, u_sw1, s_sw1 = exp_sw_list[0][0] + self.ax.plot([s_sw1], [u_sw1], [c_sw1], "om", + markersize=self.point_size, zorder=5) + if switch >= 2: + c_sw2, u_sw2, s_sw2 = exp_sw_list[1][0] + self.ax.plot([s_sw2], [u_sw2], [c_sw2], "Xm", + markersize=self.point_size, zorder=5) + if switch == 3: + c_sw3, u_sw3, s_sw3 = exp_sw_list[2][0] + self.ax.plot([s_sw3], [u_sw3], [c_sw3], "Dm", + markersize=self.point_size, zorder=5) + if np.max(self.t) == 20: + self.ax.plot([s[-1]], [u[-1]], [c[-1]], "*m", + markersize=self.point_size, zorder=5) + for i in range(4): + if any(self.state == i): + self.ax.scatter(s_[(self.state == i)], + u_[(self.state == i)], + c_[(self.state == i)], + s=self.point_size, c=self.color[i]) + self.ax.set_xlabel('s') + self.ax.set_ylabel('u') + self.ax.set_zlabel('c') + self.fig.canvas.draw() + plt.pause(0.1) + return perform_update + + def save_dyn_plot(self, c, u, s, c_sw, u_sw, s_sw, tau_list, + show_all=False): + if not os.path.exists(self.plot_path): + os.makedirs(self.plot_path) + main_info(f'{self.plot_path} directory created.', indent_level=2) + + switch = np.sum(self.t_sw_array < 20) + scale_back = np.array([self.scale_c, self.scale_u, self.scale_s]) + shift_back = np.array([self.offset_c, self.offset_u, self.offset_s]) + if switch >= 1: + c_sw1, u_sw1, s_sw1 = c_sw[0], u_sw[0], s_sw[0] + if switch >= 2: + c_sw2, u_sw2, s_sw2 = c_sw[1], u_sw[1], s_sw[1] + if switch == 3: + c_sw3, u_sw3, s_sw3 = c_sw[2], u_sw[2], s_sw[2] + + if not show_all: + n_anchors = len(u) + t_lower = np.min(self.t) + t_upper = np.max(self.t) + t_ = np.concatenate((tau_list[0], tau_list[1] + self.t_sw_array[0], + tau_list[2] + self.t_sw_array[1], + tau_list[3] + self.t_sw_array[2])) + c_pre = c[t_[:n_anchors] <= t_lower] + u_pre = u[t_[:n_anchors] <= t_lower] + s_pre = s[t_[:n_anchors] <= t_lower] + c = c[(t_lower < t_[:n_anchors]) & (t_[:n_anchors] < t_upper)] + u = u[(t_lower < t_[:n_anchors]) & (t_[:n_anchors] < t_upper)] + s = s[(t_lower < t_[:n_anchors]) & (t_[:n_anchors] < t_upper)] + + c_all = self.c_all * self.scale_c + self.offset_c + u_all = self.u_all * self.scale_u + self.offset_u + s_all = self.s_all * self.scale_s + self.offset_s + + fig = plt.figure(figsize=self.fig_size) + fig.patch.set_facecolor('white') + ax = fig.add_subplot(111, facecolor='white') + if not show_all and len(u_pre) > 0: + ax.scatter(s_pre, u_pre, s=self.point_size/2, c='black', + alpha=0.4, zorder=2) + ax.scatter(s, u, s=self.point_size*1.5, c='black', alpha=0.6, zorder=2) + for i in range(4): + if any(self.state == i): + ax.scatter(s_all[(self.state == i) & (self.non_outlier)], + u_all[(self.state == i) & (self.non_outlier)], + s=self.point_size, c=self.color[i]) + ax.scatter(s_all[~self.non_outlier], u_all[~self.non_outlier], + s=self.point_size/2, c='grey') + if show_all or t_lower <= self.t_sw_array[0]: + ax.plot([s_sw1], [u_sw1], "om", markersize=self.point_size, + zorder=5) + if switch >= 2 and (show_all or (t_lower <= self.t_sw_array[1] and + t_upper >= self.t_sw_array[1])): + ax.plot([s_sw2], [u_sw2], "Xm", markersize=self.point_size, + zorder=5) + if switch >= 3 and (show_all or (t_lower <= self.t_sw_array[2] and + t_upper >= self.t_sw_array[2])): + ax.plot([s_sw3], [u_sw3], "Dm", markersize=self.point_size, + zorder=5) + if np.max(self.t) == 20: + ax.plot([s[-1]], [u[-1]], "*m", markersize=self.point_size, + zorder=5) + if (self.anchor_t1_list is not None and len(self.anchor_t1_list) > 0 + and show_all): + for i in range(len(self.anchor_t1_list)): + exp_t1 = self.anchor_t1_list[i] * scale_back + shift_back + exp_t2 = self.anchor_t2_list[i] * scale_back + shift_back + ax.plot([exp_t1[2]], [exp_t1[1]], "|y", + markersize=self.point_size*1.5) + ax.plot([exp_t2[2]], [exp_t2[1]], "|c", + markersize=self.point_size*1.5) + ax.plot(s_all, + self.steady_state_func(self.s_all) * self.scale_u + + self.offset_u, c='grey', ls=':', lw=self.point_size/4, + alpha=0.7) + ax.set_xlabel('s') + ax.set_ylabel('u') + ax.set_title(f'{self.gene}-{self.model}') + plt.tight_layout() + fig.savefig(f'{self.plot_path}/{self.gene}-{self.model}-us.png', + dpi=fig.dpi, facecolor=fig.get_facecolor(), + transparent=False, edgecolor='none') + plt.close(fig) + plt.pause(0.2) + + if self.extra_color is not None: + fig = plt.figure(figsize=self.fig_size) + fig.patch.set_facecolor('white') + ax = fig.add_subplot(111, facecolor='white') + if not show_all and len(u_pre) > 0: + ax.scatter(s_pre, u_pre, s=self.point_size/2, c='black', + alpha=0.4, zorder=2) + ax.scatter(s, u, s=self.point_size*1.5, c='black', alpha=0.6, + zorder=2) + ax.scatter(s_all, u_all, s=self.point_size, c=self.extra_color) + if show_all or t_lower <= self.t_sw_array[0]: + ax.plot([s_sw1], [u_sw1], "om", markersize=self.point_size, + zorder=5) + if switch >= 2 and (show_all or (t_lower <= self.t_sw_array[1] and + t_upper >= self.t_sw_array[1])): + ax.plot([s_sw2], [u_sw2], "Xm", markersize=self.point_size, + zorder=5) + if switch >= 3 and (show_all or (t_lower <= self.t_sw_array[2] and + t_upper >= self.t_sw_array[2])): + ax.plot([s_sw3], [u_sw3], "Dm", markersize=self.point_size, + zorder=5) + if np.max(self.t) == 20: + ax.plot([s[-1]], [u[-1]], "*m", markersize=self.point_size, + zorder=5) + if (self.anchor_t1_list is not None and + len(self.anchor_t1_list) > 0 and show_all): + for i in range(len(self.anchor_t1_list)): + exp_t1 = self.anchor_t1_list[i] * scale_back + shift_back + exp_t2 = self.anchor_t2_list[i] * scale_back + shift_back + ax.plot([exp_t1[2]], [exp_t1[1]], "|y", + markersize=self.point_size*1.5) + ax.plot([exp_t2[2]], [exp_t2[1]], "|c", + markersize=self.point_size*1.5) + ax.plot(s_all, self.steady_state_func(self.s_all) * self.scale_u + + self.offset_u, c='grey', ls=':', lw=self.point_size/4, + alpha=0.7) + ax.set_xlabel('s') + ax.set_ylabel('u') + ax.set_title(f'{self.gene}-{self.model}') + plt.tight_layout() + fig.savefig(f'{self.plot_path}/{self.gene}-{self.model}-' + 'us_colorby_extra.png', dpi=fig.dpi, + facecolor=fig.get_facecolor(), transparent=False, + edgecolor='none') + plt.close(fig) + plt.pause(0.2) + + if not self.rna_only: + fig = plt.figure(figsize=self.fig_size) + fig.patch.set_facecolor('white') + ax = fig.add_subplot(111, facecolor='white') + if not show_all and len(u_pre) > 0: + ax.scatter(u_pre, c_pre, s=self.point_size/2, c='black', + alpha=0.4, zorder=2) + ax.scatter(u, c, s=self.point_size*1.5, c='black', alpha=0.6, + zorder=2) + ax.scatter(u_all, c_all, s=self.point_size, c=self.extra_color) + if show_all or t_lower <= self.t_sw_array[0]: + ax.plot([u_sw1], [c_sw1], "om", markersize=self.point_size, + zorder=5) + if switch >= 2 and (show_all or (t_lower <= self.t_sw_array[1] + and t_upper >= + self.t_sw_array[1])): + ax.plot([u_sw2], [c_sw2], "Xm", markersize=self.point_size, + zorder=5) + if switch >= 3 and (show_all or (t_lower <= self.t_sw_array[2] + and t_upper >= + self.t_sw_array[2])): + ax.plot([u_sw3], [c_sw3], "Dm", markersize=self.point_size, + zorder=5) + if np.max(self.t) == 20: + ax.plot([u[-1]], [c[-1]], "*m", markersize=self.point_size, + zorder=5) + ax.set_xlabel('u') + ax.set_ylabel('c') + ax.set_title(f'{self.gene}-{self.model}') + plt.tight_layout() + fig.savefig(f'{self.plot_path}/{self.gene}-{self.model}-' + 'cu_colorby_extra.png', dpi=fig.dpi, + facecolor=fig.get_facecolor(), transparent=False, + edgecolor='none') + plt.close(fig) + plt.pause(0.2) + + if not self.rna_only: + fig = plt.figure(figsize=self.fig_size) + fig.patch.set_facecolor('white') + ax = fig.add_subplot(111, projection='3d', facecolor='white') + if not show_all and len(u_pre) > 0: + ax.scatter(s_pre, u_pre, c_pre, s=self.point_size/2, c='black', + alpha=0.4, zorder=2) + ax.scatter(s, u, c, s=self.point_size*1.5, c='black', alpha=0.6, + zorder=2) + for i in range(4): + if any(self.state == i): + ax.scatter(s_all[(self.state == i) & (self.non_outlier)], + u_all[(self.state == i) & (self.non_outlier)], + c_all[(self.state == i) & (self.non_outlier)], + s=self.point_size, c=self.color[i]) + ax.scatter(s_all[~self.non_outlier], u_all[~self.non_outlier], + c_all[~self.non_outlier], s=self.point_size/2, c='grey') + if show_all or t_lower <= self.t_sw_array[0]: + ax.plot([s_sw1], [u_sw1], [c_sw1], "om", + markersize=self.point_size, zorder=5) + if switch >= 2 and (show_all or (t_lower <= self.t_sw_array[1] and + t_upper >= self.t_sw_array[1])): + ax.plot([s_sw2], [u_sw2], [c_sw2], "Xm", + markersize=self.point_size, zorder=5) + if switch >= 3 and (show_all or (t_lower <= self.t_sw_array[2] and + t_upper >= self.t_sw_array[2])): + ax.plot([s_sw3], [u_sw3], [c_sw3], "Dm", + markersize=self.point_size, zorder=5) + if np.max(self.t) == 20: + ax.plot([s[-1]], [u[-1]], [c[-1]], "*m", + markersize=self.point_size, zorder=5) + ax.set_xlabel('s') + ax.set_ylabel('u') + ax.set_zlabel('c') + ax.set_title(f'{self.gene}-{self.model}') + plt.tight_layout() + fig.savefig(f'{self.plot_path}/{self.gene}-{self.model}-cus.png', + dpi=fig.dpi, facecolor=fig.get_facecolor(), + transparent=False, edgecolor='none') + plt.close(fig) + plt.pause(0.2) + + fig = plt.figure(figsize=self.fig_size) + fig.patch.set_facecolor('white') + ax = fig.add_subplot(111, facecolor='white') + if not show_all and len(u_pre) > 0: + ax.scatter(s_pre, u_pre, s=self.point_size/2, c='black', + alpha=0.4, zorder=2) + ax.scatter(s, u, s=self.point_size*1.5, c='black', alpha=0.6, + zorder=2) + ax.scatter(s_all, u_all, s=self.point_size, c=np.log1p(self.c_all), + cmap='coolwarm') + if show_all or t_lower <= self.t_sw_array[0]: + ax.plot([s_sw1], [u_sw1], "om", markersize=self.point_size, + zorder=5) + if switch >= 2 and (show_all or (t_lower <= self.t_sw_array[1] and + t_upper >= self.t_sw_array[1])): + ax.plot([s_sw2], [u_sw2], "Xm", markersize=self.point_size, + zorder=5) + if switch >= 3 and (show_all or (t_lower <= self.t_sw_array[2] and + t_upper >= self.t_sw_array[2])): + ax.plot([s_sw3], [u_sw3], "Dm", markersize=self.point_size, + zorder=5) + if np.max(self.t) == 20: + ax.plot([s[-1]], [u[-1]], "*m", markersize=self.point_size, + zorder=5) + ax.plot(s_all, self.steady_state_func(self.s_all) * self.scale_u + + self.offset_u, c='grey', ls=':', lw=self.point_size/4, + alpha=0.7) + ax.set_xlabel('s') + ax.set_ylabel('u') + ax.set_title(f'{self.gene}-{self.model}') + plt.tight_layout() + fig.savefig(f'{self.plot_path}/{self.gene}-{self.model}-' + 'us_colorby_c.png', dpi=fig.dpi, + facecolor=fig.get_facecolor(), transparent=False, + edgecolor='none') + plt.close(fig) + plt.pause(0.2) + + fig = plt.figure(figsize=self.fig_size) + fig.patch.set_facecolor('white') + ax = fig.add_subplot(111, facecolor='white') + if not show_all and len(u_pre) > 0: + ax.scatter(u_pre, c_pre, s=self.point_size/2, c='black', + alpha=0.4, zorder=2) + ax.scatter(u, c, s=self.point_size*1.5, c='black', alpha=0.6, + zorder=2) + for i in range(4): + if any(self.state == i): + ax.scatter(u_all[(self.state == i) & (self.non_outlier)], + c_all[(self.state == i) & (self.non_outlier)], + s=self.point_size, c=self.color[i]) + ax.scatter(u_all[~self.non_outlier], c_all[~self.non_outlier], + s=self.point_size/2, c='grey') + if show_all or t_lower <= self.t_sw_array[0]: + ax.plot([u_sw1], [c_sw1], "om", markersize=self.point_size, + zorder=5) + if switch >= 2 and (show_all or (t_lower <= self.t_sw_array[1] and + t_upper >= self.t_sw_array[1])): + ax.plot([u_sw2], [c_sw2], "Xm", markersize=self.point_size, + zorder=5) + if switch >= 3 and (show_all or (t_lower <= self.t_sw_array[2] and + t_upper >= self.t_sw_array[2])): + ax.plot([u_sw3], [c_sw3], "Dm", markersize=self.point_size, + zorder=5) + if np.max(self.t) == 20: + ax.plot([u[-1]], [c[-1]], "*m", markersize=self.point_size, + zorder=5) + ax.set_xlabel('u') + ax.set_ylabel('c') + ax.set_title(f'{self.gene}-{self.model}') + plt.tight_layout() + fig.savefig(f'{self.plot_path}/{self.gene}-{self.model}-cu.png', + dpi=fig.dpi, facecolor=fig.get_facecolor(), + transparent=False, edgecolor='none') + plt.close(fig) + plt.pause(0.2) + + def get_loss(self): + return self.loss + + def get_model(self): + return self.model + + def get_params(self): + return self.t_sw_array, self.rates, self.scale_cc, self.rescale_c, \ + self.rescale_u, self.realign_ratio + + def is_partial(self): + return self.partial + + def get_direction(self): + return self.direction + + def realign_time_and_velocity(self, c, u, s, anchor_time): + # realign time to range (0,20) + self.anchor_min_idx = np.sum(anchor_time < (np.min(self.t)-1e-5)) + self.anchor_max_idx = np.sum(anchor_time < (np.max(self.t)-1e-5)) + self.c0 = c[self.anchor_min_idx] + self.u0 = u[self.anchor_min_idx] + self.s0 = s[self.anchor_min_idx] + self.realign_ratio = 20 / (np.max(self.t) - np.min(self.t)) + main_info(f'fitted params:\nswitch time array = {self.t_sw_array},\n' + f'rates = {self.rates},\ncc scale = {self.scale_cc},\n' + f'c rescale factor = {self.rescale_c},\n' + f'u rescale factor = {self.rescale_u}', + indent_level=1) + main_info(f'aligning to range (0,20) by {self.realign_ratio}..', + indent_level=1) + self.rates /= self.realign_ratio + self.alpha_c, self.alpha, self.beta, self.gamma = self.rates + self.params[3:7] = self.rates + self.t_sw_array = ((self.t_sw_array - np.min(self.t)) + * self.realign_ratio) + self.t_sw_1, self.t_sw_2, self.t_sw_3 = self.t_sw_array + self.params[:3] = np.array([self.t_sw_1, self.t_sw_2 - self.t_sw_1, + self.t_sw_3 - self.t_sw_2]) + self.t -= np.min(self.t) + self.t = self.t * 20 / np.max(self.t) + self.velocity /= self.realign_ratio + self.velocity[:, 0] = np.clip(self.velocity[:, 0], -self.c_all + * self.scale_c, None) + self.velocity[:, 1] = np.clip(self.velocity[:, 1], -self.u_all + * self.scale_u, None) + self.velocity[:, 2] = np.clip(self.velocity[:, 2], -self.s_all + * self.scale_s, None) + self.anchor_velo /= self.realign_ratio + self.anchor_velo[:, 0] = np.clip(self.anchor_velo[:, 0], + -np.max(self.c_all * self.scale_c), + None) + self.anchor_velo[:, 1] = np.clip(self.anchor_velo[:, 1], + -np.max(self.u_all * self.scale_u), + None) + self.anchor_velo[:, 2] = np.clip(self.anchor_velo[:, 2], + -np.max(self.s_all * self.scale_s), + None) + + def get_initial_exp(self): + return np.array([self.c0, self.u0, self.s0]) + + def get_time_assignment(self): + if self.low_quality: + return np.zeros(len(self.u_all)) + return self.t + + def get_state_assignment(self): + if self.low_quality: + return np.zeros(len(self.u_all)) + return self.state + + def get_velocity(self): + if self.low_quality: + return np.zeros((len(self.u_all), 3)) + return self.velocity + + def get_likelihood(self): + return self.likelihood, self.l_c, self.ssd_c, self.var_c + + def get_anchors(self): + if self.low_quality: + return (np.zeros((1, 3)), np.zeros((1, 3)), np.zeros((1, 3)), + 0, 0, 0, 0) + return self.anchor_exp, self.anchor_exp_sw, self.anchor_velo, \ + self.anchor_min_idx, self.anchor_max_idx, \ + self.anchor_velo_min_idx, self.anchor_velo_max_idx + + +def regress_func(c, u, s, m, mi, im, dev, nn, ad, lr, b1, b2, bs, gpdist, + embed, conn, pl, sp, pdir, fa, gene, pa, di, ro, fit, fd, + extra, ru, alpha, beta, gamma, t_, verbosity, log_folder, + log_filename): + + settings.VERBOSITY = verbosity + settings.LOG_FOLDER = log_folder + settings.LOG_FILENAME = log_filename + settings.GENE = gene + + if m is not None: + main_info('#########################################################' + '######################################', indent_level=1) + main_info(f'testing model {m}', indent_level=1) + + c_90 = np.percentile(c, 90) + u_90 = np.percentile(u, 90) + s_90 = np.percentile(s, 90) + low_quality = (u_90 == 0 or s_90 == 0) if ro else (c_90 == 0 or u_90 == 0 + or s_90 == 0) + if low_quality: + main_info(f'low quality gene {gene}, skipping', indent_level=1) + return (np.inf, np.nan, '', (np.zeros(3), np.zeros(4), 0, 0, 0, 0), + np.zeros(3), np.zeros(len(u)), np.zeros(len(u)), + np.zeros((len(u), 3)), (-1.0, 0, 0, 0), + (np.zeros((1, 3)), np.zeros((1, 3)), np.zeros((1, 3)), 0, 0, + 0, 0)) + + if gpdist is not None: + subset_cells = s > 0.1 * np.percentile(s, 99) + subset_cells = np.where(subset_cells)[0] + if len(subset_cells) > 3000: + rng = np.random.default_rng(2021) + subset_cells = rng.choice(subset_cells, 3000, replace=False) + local_pdist = gpdist[np.ix_(subset_cells, subset_cells)] + dists = (np.ravel(local_pdist[np.triu_indices_from(local_pdist, k=1)]) + .reshape(-1, 1)) + local_std = np.std(dists) + else: + local_std = None + + cdc = ChromatinDynamical(c, + u, + s, + model=m, + max_iter=mi, + init_mode=im, + device=dev, + neural_net=nn, + adam=ad, + adam_lr=lr, + adam_beta1=b1, + adam_beta2=b2, + batch_size=bs, + local_std=local_std, + embed_coord=embed, + connectivities=conn, + plot=pl, + save_plot=sp, + plot_dir=pdir, + fit_args=fa, + gene=gene, + partial=pa, + direction=di, + rna_only=ro, + fit_decoupling=fd, + extra_color=extra, + rescale_u=ru, + alpha=alpha, + beta=beta, + gamma=gamma, + t_=t_) + if fit: + loss = cdc.fit() + if loss[-1] == np.inf: + main_info(f'low quality gene {gene}, skipping..', indent_level=1) + loss = cdc.get_loss() + model = cdc.get_model() + direction = cdc.get_direction() + parameters = cdc.get_params() + initial_exp = cdc.get_initial_exp() + velocity = cdc.get_velocity() + likelihood = cdc.get_likelihood() + time = cdc.get_time_assignment() + state = cdc.get_state_assignment() + anchors = cdc.get_anchors() + return loss[-1], model, direction, parameters, initial_exp, time, state, \ + velocity, likelihood, anchors + + +def multimodel_helper(c, u, s, + model_to_run, + max_iter, + init_mode, + device, + neural_net, + adam, + adam_lr, + adam_beta1, + adam_beta2, + batch_size, + global_pdist, + embed_coord, + conn, + plot, + save_plot, + plot_dir, + fit_args, + gene, + partial, + direction, + rna_only, + fit, + fit_decoupling, + extra_color, + rescale_u, + alpha, + beta, + gamma, + t_, + verbosity, log_folder, log_filename): + + loss, param_cand, initial_cand, time_cand = [], [], [], [] + state_cand, velo_cand, likelihood_cand, anch_cand = [], [], [], [] + + for model in model_to_run: + (loss_m, _, direction_, parameters, initial_exp, + time, state, velocity, likelihood, anchors) = \ + regress_func(c, u, s, model, max_iter, init_mode, device, neural_net, + adam, adam_lr, adam_beta1, adam_beta2, batch_size, + global_pdist, embed_coord, conn, plot, save_plot, + plot_dir, fit_args, gene, partial, direction, rna_only, + fit, fit_decoupling, extra_color, rescale_u, alpha, beta, + gamma, t_) + loss.append(loss_m) + param_cand.append(parameters) + initial_cand.append(initial_exp) + time_cand.append(time) + state_cand.append(state) + velo_cand.append(velocity) + likelihood_cand.append(likelihood) + anch_cand.append(anchors) + + best_model = np.argmin(loss) + model = np.nan if rna_only else model_to_run[best_model] + parameters = param_cand[best_model] + initial_exp = initial_cand[best_model] + time = time_cand[best_model] + state = state_cand[best_model] + velocity = velo_cand[best_model] + likelihood = likelihood_cand[best_model] + anchors = anch_cand[best_model] + return loss, model, direction_, parameters, initial_exp, time, state, \ + velocity, likelihood, anchors + + +def recover_dynamics_chrom(adata_rna, + adata_atac=None, + gene_list=None, + max_iter=5, + init_mode='invert', + device="cpu", + neural_net=False, + adam=False, + adam_lr=None, + adam_beta1=None, + adam_beta2=None, + batch_size=None, + model_to_run=None, + plot=False, + parallel=True, + n_jobs=None, + save_plot=False, + plot_dir=None, + rna_only=False, + fit=True, + fit_decoupling=True, + extra_color_key=None, + embedding='X_umap', + n_anchors=500, + k_dist=1, + thresh_multiplier=1.0, + weight_c=0.6, + outlier=99.8, + n_pcs=30, + n_neighbors=30, + fig_size=(8, 6), + point_size=7, + partial=None, + direction=None, + rescale_u=None, + alpha=None, + beta=None, + gamma=None, + t_sw=None + ): + + """Multi-omic dynamics recovery. + + This function optimizes the joint chromatin and RNA model parameters in + ODE solutions. + + Parameters + ---------- + adata_rna: :class:`~anndata.AnnData` + RNA anndata object. Required fields: `Mu`, `Ms`, and `connectivities`. + adata_atac: :class:`~anndata.AnnData` (default: `None`) + ATAC anndata object. Required fields: `Mc`. + gene_list: `str`, list of `str` (default: highly variable genes) + Genes to use for model fitting. + max_iter: `int` (default: `5`) + Iterations to run for parameter optimization. + init_mode: `str` (default: `'invert'`) + Initialization method for switch times. + `'invert'`: initial RNA switch time will be computed with scVelo time + inversion method. + `'grid'`: grid search the best set of switch times. + `'simple'`: simply initialize switch times to be 5, 10, and 15. + device: `str` (default: `'cpu'`) + The CUDA device that pytorch tensor calculations will be run on. Only + to be used with Adam or Neural Network mode. + neural_net: `bool` (default: `False`) + Whether to run time predictions with a neural network or not. Shortens + runtime at the expense of accuracy. If False, uses the usual method of + assigning each data point to an anchor time point as outlined in the + Multivelo paper. + adam: `bool` (default: `False`) + Whether MSE minimization is handled by the Adam algorithm or not. When + set to the default of False, function uses Nelder-Mead instead. + adam_lr: `float` (default: `None`) + The learning rate to use the Adam algorithm. If adam is False, this + value is ignored. + adam_beta1: `float` (default: `None`) + The beta1 parameter for the Adam algorithm. If adam is False, this + value is ignored. + adam_beta2: `float` (default: `None`) + The beta2 parameter for the Adam algorithm. If adam is False, this + value is ignored. + batch_size: `int` (default: `None`) + Speeds up performance using minibatch training. Specifies number of + cells to use per run of MSE when running the Adam algorithm. Ignored + if Adam is set to False. + model_to_run: `int` or list of `int` (default: `None`) + User specified models for each genes. Possible values are 1 are 2. If + `None`, the model + for each gene will be inferred based on expression patterns. If more + than one value is given, + the best model will be decided based on loss of fit. + plot: `bool` or `None` (default: `False`) + Whether to interactively plot the 3D gene portraits. Ignored if + parallel is True. + parallel: `bool` (default: `True`) + Whether to fit genes in a parallel fashion (recommended). + n_jobs: `int` (default: available threads) + Number of parallel jobs. + save_plot: `bool` (default: `False`) + Whether to save the fitted gene portrait figures as files. This will + take some disk space. + plot_dir: `str` (default: `plots` for multiome and `rna_plots` for + RNA-only) + Directory to save the plots. + rna_only: `bool` (default: `False`) + Whether to only use RNA for fitting (RNA velocity). + fit: `bool` (default: `True`) + Whether to fit the models. If False, only pre-determination and + initialization will be run. + fit_decoupling: `bool` (default: `True`) + Whether to fit decoupling phase (Model 1 vs Model 2 distinction). + n_anchors: `int` (default: 500) + Number of anchor time-points to generate as a representation of the + trajectory. + k_dist: `int` (default: 1) + Number of anchors to use to determine a cell's gene time. If more than + 1, time will be averaged. + thresh_multiplier: `float` (default: 1.0) + Multiplier for the heuristic threshold of partial versus complete + trajectory pre-determination. + weight_c: `float` (default: 0.6) + Weighting of scaled chromatin distances when performing 3D residual + calculation. + outlier: `float` (default: 99.8) + The percentile to mark as outlier that will be excluded when fitting + the model. + n_pcs: `int` (default: 30) + Number of principal components to compute distance smoothing neighbors. + This can be different from the one used for expression smoothing. + n_neighbors: `int` (default: 30) + Number of nearest neighbors for distance smoothing. + This can be different from the one used for expression smoothing. + fig_size: `tuple` (default: (8,6)) + Size of each figure when saved. + point_size: `float` (default: 7) + Marker point size for plotting. + extra_color_key: `str` (default: `None`) + Extra color key used for plotting. Common choices are `leiden`, + `celltype`, etc. + The colors for each category must be present in one of anndatas, which + can be pre-computed + with `scanpy.pl.scatter` function. + embedding: `str` (default: `X_umap`) + 2D coordinates of the low-dimensional embedding of cells. + partial: `bool` or list of `bool` (default: `None`) + User specified trajectory completeness for each gene. + direction: `str` or list of `str` (default: `None`) + User specified trajectory directionality for each gene. + rescale_u: `float` or list of `float` (default: `None`) + Known scaling factors for unspliced. Can be computed from scVelo + `fit_scaling` values + as `rescale_u = fit_scaling / std(u) * std(s)`. + alpha: `float` or list of `float` (default: `None`) + Known trascription rates. Can be computed from scVelo `fit_alpha` + values + as `alpha = fit_alpha * fit_alignment_scaling`. + beta: `float` or list of `float` (default: `None`) + Known splicing rates. Can be computed from scVelo `fit_alpha` values + as `beta = fit_beta * fit_alignment_scaling`. + gamma: `float` or list of `float` (default: `None`) + Known degradation rates. Can be computed from scVelo `fit_gamma` values + as `gamma = fit_gamma * fit_alignment_scaling`. + t_sw: `float` or list of `float` (default: `None`) + Known RNA switch time. Can be computed from scVelo `fit_t_` values + as `t_sw = fit_t_ / fit_alignment_scaling`. + + Returns + ------- + fit_alpha_c, fit_alpha, fit_beta, fit_gamma: `.var` + inferred chromatin opening, transcription, splicing, and degradation + (nuclear export) rates + fit_t_sw1, fit_t_sw2, fit_t_sw3: `.var` + inferred switching time points + fit_rescale_c, fit_rescale_u: `.var` + inferred scaling factor for chromatin and unspliced counts + fit_scale_cc: `.var` + inferred scaling value for chromatin closing rate compared to opening + rate + fit_alignment_scaling: `.var` + ratio used to realign observed time range to 0-20 + fit_c0, fit_u0, fit_s0: `.var` + initial expression values at earliest observed time + fit_model: `.var` + inferred gene model + fit_direction: `.var` + inferred gene direction + fit_loss: `.var` + loss of model fit + fit_likelihood: `.var` + likelihood of model fit + fit_likelihood_c: `.var` + likelihood of chromatin fit + fit_anchor_c, fit_anchor_u, fit_anchor_s: `.varm` + anchor expressions + fit_anchor_c_sw, fit_anchor_u_sw, fit_anchor_s_sw: `.varm` + switch time-point expressions + fit_anchor_c_velo, fit_anchor_u_velo, fit_anchor_s_velo: `.varm` + velocities of anchors + fit_anchor_min_idx: `.var` + first anchor mapped to observations + fit_anchor_max_idx: `.var` + last anchor mapped to observations + fit_anchor_velo_min_idx: `.var` + first velocity anchor mapped to observations + fit_anchor_velo_max_idx: `.var` + last velocity anchor mapped to observations + fit_t: `.layers` + inferred gene time + fit_state: `.layers` + inferred state assignments + velo_s, velo_u, velo_chrom: `.layers` + velocities in spliced, unspliced, and chromatin space + velo_s_genes, velo_u_genes, velo_chrom_genes: `.var` + velocity genes + velo_s_params, velo_u_params, velo_chrom_params: `.var` + fitting arguments used + ATAC: `.layers` + KNN smoothed chromatin accessibilities copied from adata_atac + """ + + fit_args = {} + fit_args['max_iter'] = max_iter + fit_args['init_mode'] = init_mode + fit_args['fit_decoupling'] = fit_decoupling + n_anchors = np.clip(int(n_anchors), 201, 2000) + fit_args['t'] = n_anchors + fit_args['k'] = k_dist + fit_args['thresh_multiplier'] = thresh_multiplier + fit_args['weight_c'] = weight_c + fit_args['outlier'] = outlier + fit_args['n_pcs'] = n_pcs + fit_args['n_neighbors'] = n_neighbors + fit_args['fig_size'] = list(fig_size) + fit_args['point_size'] = point_size + + if adam and neural_net: + raise Exception("ADAM and Neural Net mode can not be run concurently." + " Please choose one to run on.") + + if not adam and not neural_net and not device == "cpu": + raise Exception("Multivelo only uses non-CPU devices for Adam or" + " Neural Network mode. Please use one of those or" + "set the device to \"cpu\"") + + if adam and not device[0:5] == "cuda:": + raise Exception("ADAM and Neural Net mode are only possible on a cuda " + "device. Please try again.") + if not adam and batch_size is not None: + raise Exception("Batch training is for ADAM only, please set " + "batch_size to None") + + if adam: + from cuml.neighbors import NearestNeighbors + + all_genes = adata_rna.var_names + if adata_atac is None: + import anndata as ad + rna_only = True + adata_atac = ad.AnnData(X=np.ones(adata_rna.shape), obs=adata_rna.obs, + var=adata_rna.var) + adata_atac.layers['Mc'] = np.ones(adata_rna.shape) + if adata_rna.shape != adata_atac.shape: + raise ValueError('Shape of RNA and ATAC adata objects do not match: ' + f'{adata_rna.shape} {adata_atac.shape}') + if not np.all(adata_rna.obs_names == adata_atac.obs_names): + raise ValueError('obs_names of RNA and ATAC adata objects do not ' + 'match, please check if they are consistent') + if not np.all(all_genes == adata_atac.var_names): + raise ValueError('var_names of RNA and ATAC adata objects do not ' + 'match, please check if they are consistent') + if 'connectivities' not in adata_rna.obsp.keys(): + raise ValueError('Missing connectivities entry in RNA adata object') + if extra_color_key is None: + extra_color = None + elif (isinstance(extra_color_key, str) and extra_color_key in adata_rna.obs + and adata_rna.obs[extra_color_key].dtype.name == 'category'): + ngroups = len(adata_rna.obs[extra_color_key].cat.categories) + extra_color = adata_rna.obs[extra_color_key].cat.rename_categories( + adata_rna.uns[extra_color_key+'_colors'][:ngroups]).to_numpy() + elif (isinstance(extra_color_key, str) and extra_color_key in + adata_atac.obs and + adata_rna.obs[extra_color_key].dtype.name == 'category'): + ngroups = len(adata_atac.obs[extra_color_key].cat.categories) + extra_color = adata_atac.obs[extra_color_key].cat.rename_categories( + adata_atac.uns[extra_color_key+'_colors'][:ngroups]).to_numpy() + else: + raise ValueError('Currently, extra_color_key must be a single string ' + 'of categories and available in adata obs, and its ' + 'colors can be found in adata uns') + if ('connectivities' not in adata_rna.obsp.keys() or + (adata_rna.obsp['connectivities'] > 0).sum(1).min() + > (n_neighbors-1)): + from scanpy import Neighbors + neighbors = Neighbors(adata_rna) + neighbors.compute_neighbors(n_neighbors=n_neighbors, knn=True, + n_pcs=n_pcs) + rna_conn = neighbors.connectivities + else: + rna_conn = adata_rna.obsp['connectivities'].copy() + rna_conn.setdiag(1) + rna_conn = rna_conn.multiply(1.0 / rna_conn.sum(1)).tocsr() + if not rna_only: + if 'connectivities' not in adata_atac.obsp.keys(): + main_info('Missing connectivities in ATAC adata object, using ' + 'RNA connectivities instead', indent_level=1) + atac_conn = rna_conn + else: + atac_conn = adata_atac.obsp['connectivities'].copy() + atac_conn.setdiag(1) + atac_conn = atac_conn.multiply(1.0 / atac_conn.sum(1)).tocsr() + if gene_list is None: + if 'highly_variable' in adata_rna.var: + gene_list = adata_rna.var_names[adata_rna.var['highly_variable']]\ + .values + else: + gene_list = adata_rna.var_names.values[ + (~np.isnan(np.asarray(adata_rna.layers['Mu'].sum(0)) + .reshape(-1) + if sparse.issparse(adata_rna.layers['Mu']) + else np.sum(adata_rna.layers['Mu'], axis=0))) + & (~np.isnan(np.asarray(adata_rna.layers['Ms'].sum(0)) + .reshape(-1) + if sparse.issparse(adata_rna.layers['Ms']) + else np.sum(adata_rna.layers['Ms'], axis=0))) + & (~np.isnan(np.asarray(adata_atac.layers['Mc'].sum(0)) + .reshape(-1) + if sparse.issparse(adata_atac.layers['Mc']) + else np.sum(adata_atac.layers['Mc'], axis=0)))] + elif isinstance(gene_list, (list, np.ndarray, pd.Index, pd.Series)): + gene_list = np.array([x for x in gene_list if x in all_genes]) + elif isinstance(gene_list, str): + gene_list = np.array([gene_list]) if gene_list in all_genes else [] + else: + raise ValueError('Invalid gene list, must be one of (str, np.ndarray,' + 'pd.Index, pd.Series)') + gn = len(gene_list) + if gn == 0: + raise ValueError('None of the genes specified are in the adata object') + main_info(f'{gn} genes will be fitted', indent_level=1) + + models = np.zeros(gn) + t_sws = np.zeros((gn, 3)) + rates = np.zeros((gn, 4)) + scale_ccs = np.zeros(gn) + rescale_cs = np.zeros(gn) + rescale_us = np.zeros(gn) + realign_ratios = np.zeros(gn) + initial_exps = np.zeros((gn, 3)) + times = np.zeros((adata_rna.n_obs, gn)) + states = np.zeros((adata_rna.n_obs, gn)) + if not rna_only: + velo_c = np.zeros((adata_rna.n_obs, gn)) + velo_u = np.zeros((adata_rna.n_obs, gn)) + velo_s = np.zeros((adata_rna.n_obs, gn)) + likelihoods = np.zeros(gn) + l_cs = np.zeros(gn) + ssd_cs = np.zeros(gn) + var_cs = np.zeros(gn) + directions = [] + anchor_c = np.zeros((n_anchors, gn)) + anchor_u = np.zeros((n_anchors, gn)) + anchor_s = np.zeros((n_anchors, gn)) + anchor_c_sw = np.zeros((3, gn)) + anchor_u_sw = np.zeros((3, gn)) + anchor_s_sw = np.zeros((3, gn)) + anchor_vc = np.zeros((n_anchors, gn)) + anchor_vu = np.zeros((n_anchors, gn)) + anchor_vs = np.zeros((n_anchors, gn)) + anchor_min_idx = np.zeros(gn) + anchor_max_idx = np.zeros(gn) + anchor_velo_min_idx = np.zeros(gn) + anchor_velo_max_idx = np.zeros(gn) + + if rna_only: + model_to_run = [2] + main_info('Skipping model checking for RNA-only, running model 2', + indent_level=1) + + m_per_g = False + if model_to_run is not None: + if isinstance(model_to_run, (list, np.ndarray, pd.Index, pd.Series)): + model_to_run = [int(x) for x in model_to_run] + if np.any(~np.isin(model_to_run, [0, 1, 2])): + raise ValueError('Invalid model number (must be values in' + ' [0,1,2])') + if len(model_to_run) == gn: + losses = np.zeros((gn, 1)) + m_per_g = True + func_to_call = regress_func + else: + losses = np.zeros((gn, len(model_to_run))) + func_to_call = multimodel_helper + elif isinstance(model_to_run, (int, float)): + model_to_run = int(model_to_run) + if not np.isin(model_to_run, [0, 1, 2]): + raise ValueError('Invalid model number (must be values in ' + '[0,1,2])') + model_to_run = [model_to_run] + losses = np.zeros((gn, 1)) + func_to_call = multimodel_helper + else: + raise ValueError('Invalid model number (must be values in ' + '[0,1,2])') + else: + losses = np.zeros((gn, 1)) + func_to_call = regress_func + + p_per_g = False + if partial is not None: + if isinstance(partial, (list, np.ndarray, pd.Index, pd.Series)): + if np.any(~np.isin(partial, [True, False])): + raise ValueError('Invalid partial argument (must be values in' + ' [True,False])') + if len(partial) == gn: + p_per_g = True + else: + raise ValueError('Incorrect partial argument length') + elif isinstance(partial, bool): + if not np.isin(partial, [True, False]): + raise ValueError('Invalid partial argument (must be values in' + ' [True,False])') + else: + raise ValueError('Invalid partial argument (must be values in' + ' [True,False])') + + d_per_g = False + if direction is not None: + if isinstance(direction, (list, np.ndarray, pd.Index, pd.Series)): + if np.any(~np.isin(direction, ['on', 'off', 'complete'])): + raise ValueError('Invalid direction argument (must be values' + ' in ["on","off","complete"])') + if len(direction) == gn: + d_per_g = True + else: + raise ValueError('Incorrect direction argument length') + elif isinstance(direction, str): + if not np.isin(direction, ['on', 'off', 'complete']): + raise ValueError('Invalid direction argument (must be values' + ' in ["on","off","complete"])') + else: + raise ValueError('Invalid direction argument (must be values in' + ' ["on","off","complete"])') + + known_pars = [rescale_u, alpha, beta, gamma, t_sw] + for x in known_pars: + if x is not None: + if isinstance(x, (list, np.ndarray)): + if np.sum(np.isnan(x)) + np.sum(np.isinf(x)) > 0: + raise ValueError('Known parameters cannot contain NaN or' + ' Inf') + elif isinstance(x, (int, float)): + if x == np.nan or x == np.inf: + raise ValueError('Known parameters cannot contain NaN or' + ' Inf') + else: + raise ValueError('Invalid known parameters type') + + if ((embedding not in adata_rna.obsm) and + (embedding not in adata_atac.obsm)): + raise ValueError(f'{embedding} is not found in obsm') + embed_coord = adata_rna.obsm[embedding] if embedding in adata_rna.obsm \ + else adata_atac.obsm[embedding] + global_pdist = pairwise_distances(embed_coord) + + u_mat = adata_rna[:, gene_list].layers['Mu'].A \ + if sparse.issparse(adata_rna.layers['Mu']) \ + else adata_rna[:, gene_list].layers['Mu'] + s_mat = adata_rna[:, gene_list].layers['Ms'].A \ + if sparse.issparse(adata_rna.layers['Ms']) \ + else adata_rna[:, gene_list].layers['Ms'] + c_mat = adata_atac[:, gene_list].layers['Mc'].A \ + if sparse.issparse(adata_atac.layers['Mc']) \ + else adata_atac[:, gene_list].layers['Mc'] + + ru = rescale_u if rescale_u is not None else None + + if parallel: + if (n_jobs is None or not isinstance(n_jobs, int) or n_jobs < 0 or + n_jobs > os.cpu_count()): + n_jobs = os.cpu_count() + if n_jobs > gn: + n_jobs = gn + batches = -(-gn // n_jobs) + if n_jobs > 1: + main_info(f'running {n_jobs} jobs in parallel', indent_level=1) + else: + n_jobs = 1 + batches = gn + if n_jobs == 1: + parallel = False + + pbar = tqdm(total=gn) + for group in range(batches): + gene_indices = range(group * n_jobs, np.min([gn, (group+1) * n_jobs])) + if parallel: + from joblib import Parallel, delayed + verb = 51 if settings.VERBOSITY >= 2 else 0 + plot = False + + # clear the settings file if it exists + open("settings.txt", "w").close() + + # write our current settings to the file + with open("settings.txt", "a") as sfile: + sfile.write(str(settings.VERBOSITY) + "\n") + sfile.write(str(settings.CWD) + "\n") + sfile.write(str(settings.LOG_FOLDER) + "\n") + sfile.write(str(settings.LOG_FILENAME) + "\n") + + res = Parallel(n_jobs=n_jobs, backend='loky', verbose=verb)( + delayed(func_to_call)( + c_mat[:, i], + u_mat[:, i], + s_mat[:, i], + model_to_run[i] if m_per_g else model_to_run, + max_iter, + init_mode, + device, + neural_net, + adam, + adam_lr, + adam_beta1, + adam_beta2, + batch_size, + global_pdist, + embed_coord, + rna_conn, + plot, + save_plot, + plot_dir, + fit_args, + gene_list[i], + partial[i] if p_per_g else partial, + direction[i] if d_per_g else direction, + rna_only, + fit, + fit_decoupling, + extra_color, + ru[i] if isinstance(ru, (list, np.ndarray)) else ru, + alpha[i] if isinstance(alpha, (list, np.ndarray)) + else alpha, + beta[i] if isinstance(beta, (list, np.ndarray)) + else beta, + gamma[i] if isinstance(gamma, (list, np.ndarray)) + else gamma, + t_sw[i] if isinstance(t_sw, (list, np.ndarray)) else t_sw, + settings.VERBOSITY, + settings.LOG_FOLDER, + settings.LOG_FILENAME) + for i in gene_indices) + + for i, r in zip(gene_indices, res): + (loss, model, direct_out, parameters, initial_exp, + time, state, velocity, likelihood, anchors) = r + switch, rate, scale_cc, rescale_c, rescale_u, realign_ratio = \ + parameters + likelihood, l_c, ssd_c, var_c = likelihood + losses[i, :] = loss + models[i] = model + directions.append(direct_out) + t_sws[i, :] = switch + rates[i, :] = rate + scale_ccs[i] = scale_cc + rescale_cs[i] = rescale_c + rescale_us[i] = rescale_u + realign_ratios[i] = realign_ratio + likelihoods[i] = likelihood + l_cs[i] = l_c + ssd_cs[i] = ssd_c + var_cs[i] = var_c + if fit: + initial_exps[i, :] = initial_exp + times[:, i] = time + states[:, i] = state + n_anchors_ = anchors[0].shape[0] + n_switch = anchors[1].shape[0] + if not rna_only: + velo_c[:, i] = smooth_scale(atac_conn, velocity[:, 0]) + anchor_c[:n_anchors_, i] = anchors[0][:, 0] + anchor_c_sw[:n_switch, i] = anchors[1][:, 0] + anchor_vc[:n_anchors_, i] = anchors[2][:, 0] + velo_u[:, i] = smooth_scale(rna_conn, velocity[:, 1]) + velo_s[:, i] = smooth_scale(rna_conn, velocity[:, 2]) + anchor_u[:n_anchors_, i] = anchors[0][:, 1] + anchor_s[:n_anchors_, i] = anchors[0][:, 2] + anchor_u_sw[:n_switch, i] = anchors[1][:, 1] + anchor_s_sw[:n_switch, i] = anchors[1][:, 2] + anchor_vu[:n_anchors_, i] = anchors[2][:, 1] + anchor_vs[:n_anchors_, i] = anchors[2][:, 2] + anchor_min_idx[i] = anchors[3] + anchor_max_idx[i] = anchors[4] + anchor_velo_min_idx[i] = anchors[5] + anchor_velo_max_idx[i] = anchors[6] + else: + i = group + gene = gene_list[i] + main_info(f'@@@@@fitting {gene}', indent_level=1) + (loss, model, direct_out, + parameters, initial_exp, + time, state, velocity, + likelihood, anchors) = \ + func_to_call(c_mat[:, i], u_mat[:, i], s_mat[:, i], + model_to_run[i] if m_per_g else model_to_run, + max_iter, init_mode, + device, + neural_net, + adam, + adam_lr, + adam_beta1, + adam_beta2, + batch_size, + global_pdist, embed_coord, + rna_conn, plot, save_plot, plot_dir, + fit_args, gene, + partial[i] if p_per_g else partial, + direction[i] if d_per_g else direction, + rna_only, fit, fit_decoupling, extra_color, + ru[i] if isinstance(ru, (list, np.ndarray)) + else ru, + alpha[i] if isinstance(alpha, (list, np.ndarray)) + else alpha, + beta[i] if isinstance(beta, (list, np.ndarray)) + else beta, + gamma[i] if isinstance(gamma, (list, np.ndarray)) + else gamma, + t_sw[i] if isinstance(t_sw, (list, np.ndarray)) + else t_sw, + settings.VERBOSITY, + settings.LOG_FOLDER, + settings.LOG_FILENAME) + switch, rate, scale_cc, rescale_c, rescale_u, realign_ratio = \ + parameters + likelihood, l_c, ssd_c, var_c = likelihood + losses[i, :] = loss + models[i] = model + directions.append(direct_out) + t_sws[i, :] = switch + rates[i, :] = rate + scale_ccs[i] = scale_cc + rescale_cs[i] = rescale_c + rescale_us[i] = rescale_u + realign_ratios[i] = realign_ratio + likelihoods[i] = likelihood + l_cs[i] = l_c + ssd_cs[i] = ssd_c + var_cs[i] = var_c + if fit: + initial_exps[i, :] = initial_exp + times[:, i] = time + states[:, i] = state + n_anchors_ = anchors[0].shape[0] + n_switch = anchors[1].shape[0] + if not rna_only: + velo_c[:, i] = smooth_scale(atac_conn, velocity[:, 0]) + anchor_c[:n_anchors_, i] = anchors[0][:, 0] + anchor_c_sw[:n_switch, i] = anchors[1][:, 0] + anchor_vc[:n_anchors_, i] = anchors[2][:, 0] + velo_u[:, i] = smooth_scale(rna_conn, velocity[:, 1]) + velo_s[:, i] = smooth_scale(rna_conn, velocity[:, 2]) + anchor_u[:n_anchors_, i] = anchors[0][:, 1] + anchor_s[:n_anchors_, i] = anchors[0][:, 2] + anchor_u_sw[:n_switch, i] = anchors[1][:, 1] + anchor_s_sw[:n_switch, i] = anchors[1][:, 2] + anchor_vu[:n_anchors_, i] = anchors[2][:, 1] + anchor_vs[:n_anchors_, i] = anchors[2][:, 2] + anchor_min_idx[i] = anchors[3] + anchor_max_idx[i] = anchors[4] + anchor_velo_min_idx[i] = anchors[5] + anchor_velo_max_idx[i] = anchors[6] + pbar.update(len(gene_indices)) + pbar.close() + directions = np.array(directions) + + filt = np.sum(losses != np.inf, 1) >= 1 + if np.sum(filt) == 0: + raise ValueError('None of the genes were fitted due to low quality,' + ' not returning') + adata_copy = adata_rna[:, gene_list[filt]].copy() + adata_copy.layers['ATAC'] = c_mat[:, filt] + adata_copy.var['fit_alpha_c'] = rates[filt, 0] + adata_copy.var['fit_alpha'] = rates[filt, 1] + adata_copy.var['fit_beta'] = rates[filt, 2] + adata_copy.var['fit_gamma'] = rates[filt, 3] + adata_copy.var['fit_t_sw1'] = t_sws[filt, 0] + adata_copy.var['fit_t_sw2'] = t_sws[filt, 1] + adata_copy.var['fit_t_sw3'] = t_sws[filt, 2] + adata_copy.var['fit_scale_cc'] = scale_ccs[filt] + adata_copy.var['fit_rescale_c'] = rescale_cs[filt] + adata_copy.var['fit_rescale_u'] = rescale_us[filt] + adata_copy.var['fit_alignment_scaling'] = realign_ratios[filt] + adata_copy.var['fit_model'] = models[filt] + adata_copy.var['fit_direction'] = directions[filt] + if model_to_run is not None and not m_per_g and not rna_only: + for i, m in enumerate(model_to_run): + adata_copy.var[f'fit_loss_M{m}'] = losses[filt, i] + else: + adata_copy.var['fit_loss'] = losses[filt, 0] + adata_copy.var['fit_likelihood'] = likelihoods[filt] + adata_copy.var['fit_likelihood_c'] = l_cs[filt] + adata_copy.var['fit_ssd_c'] = ssd_cs[filt] + adata_copy.var['fit_var_c'] = var_cs[filt] + if fit: + adata_copy.layers['fit_t'] = times[:, filt] + adata_copy.layers['fit_state'] = states[:, filt] + adata_copy.layers['velo_s'] = velo_s[:, filt] + adata_copy.layers['velo_u'] = velo_u[:, filt] + if not rna_only: + adata_copy.layers['velo_chrom'] = velo_c[:, filt] + adata_copy.var['fit_c0'] = initial_exps[filt, 0] + adata_copy.var['fit_u0'] = initial_exps[filt, 1] + adata_copy.var['fit_s0'] = initial_exps[filt, 2] + adata_copy.var['fit_anchor_min_idx'] = anchor_min_idx[filt] + adata_copy.var['fit_anchor_max_idx'] = anchor_max_idx[filt] + adata_copy.var['fit_anchor_velo_min_idx'] = anchor_velo_min_idx[filt] + adata_copy.var['fit_anchor_velo_max_idx'] = anchor_velo_max_idx[filt] + adata_copy.varm['fit_anchor_c'] = np.transpose(anchor_c[:, filt]) + adata_copy.varm['fit_anchor_u'] = np.transpose(anchor_u[:, filt]) + adata_copy.varm['fit_anchor_s'] = np.transpose(anchor_s[:, filt]) + adata_copy.varm['fit_anchor_c_sw'] = np.transpose(anchor_c_sw[:, filt]) + adata_copy.varm['fit_anchor_u_sw'] = np.transpose(anchor_u_sw[:, filt]) + adata_copy.varm['fit_anchor_s_sw'] = np.transpose(anchor_s_sw[:, filt]) + adata_copy.varm['fit_anchor_c_velo'] = np.transpose(anchor_vc[:, filt]) + adata_copy.varm['fit_anchor_u_velo'] = np.transpose(anchor_vu[:, filt]) + adata_copy.varm['fit_anchor_s_velo'] = np.transpose(anchor_vs[:, filt]) + v_genes = adata_copy.var['fit_likelihood'] >= 0.05 + adata_copy.var['velo_s_genes'] = adata_copy.var['velo_u_genes'] = \ + adata_copy.var['velo_chrom_genes'] = v_genes + adata_copy.uns['velo_s_params'] = adata_copy.uns['velo_u_params'] = \ + adata_copy.uns['velo_chrom_params'] = {'mode': 'dynamical'} + adata_copy.uns['velo_s_params'].update(fit_args) + adata_copy.uns['velo_u_params'].update(fit_args) + adata_copy.uns['velo_chrom_params'].update(fit_args) + adata_copy.obsp['_RNA_conn'] = rna_conn + if not rna_only: + adata_copy.obsp['_ATAC_conn'] = atac_conn + return adata_copy + + +def smooth_scale(conn, vector): + max_to = np.max(vector) + min_to = np.min(vector) + v = conn.dot(vector.T).T + max_from = np.max(v) + min_from = np.min(v) + res = ((v - min_from) * (max_to - min_to) / (max_from - min_from)) + min_to + return res + + +def top_n_sparse(conn, n): + conn_ll = conn.tolil() + for i in range(conn_ll.shape[0]): + row_data = np.array(conn_ll.data[i]) + row_idx = np.array(conn_ll.rows[i]) + new_idx = row_data.argsort()[-n:] + top_val = row_data[new_idx] + top_idx = row_idx[new_idx] + conn_ll.data[i] = top_val.tolist() + conn_ll.rows[i] = top_idx.tolist() + conn = conn_ll.tocsr() + idx1 = conn > 0 + idx2 = conn > 0.25 + idx3 = conn > 0.5 + conn[idx1] = 0.25 + conn[idx2] = 0.5 + conn[idx3] = 1 + conn.eliminate_zeros() + return conn + + +def set_velocity_genes(adata, + likelihood_lower=0.05, + rescale_u_upper=None, + rescale_u_lower=None, + rescale_c_upper=None, + rescale_c_lower=None, + primed_upper=None, + primed_lower=None, + decoupled_upper=None, + decoupled_lower=None, + alpha_c_upper=None, + alpha_c_lower=None, + alpha_upper=None, + alpha_lower=None, + beta_upper=None, + beta_lower=None, + gamma_upper=None, + gamma_lower=None, + scale_cc_upper=None, + scale_cc_lower=None + ): + """Reset velocity genes. + + This function resets velocity genes based on criteria of variables. + + Parameters + ---------- + adata: :class:`~anndata.AnnData` + Anndata result from dynamics recovery. + likelihood_lower: `float` (default: 0.05) + Minimum ikelihood. + rescale_u_upper: `float` (default: `None`) + Maximum rescale_u. + rescale_u_lower: `float` (default: `None`) + Minimum rescale_u. + rescale_c_upper: `float` (default: `None`) + Maximum rescale_c. + rescale_c_lower: `float` (default: `None`) + Minimum rescale_c. + primed_upper: `float` (default: `None`) + Maximum primed interval. + primed_lower: `float` (default: `None`) + Minimum primed interval. + decoupled_upper: `float` (default: `None`) + Maximum decoupled interval. + decoupled_lower: `float` (default: `None`) + Minimum decoupled interval. + alpha_c_upper: `float` (default: `None`) + Maximum alpha_c. + alpha_c_lower: `float` (default: `None`) + Minimum alpha_c. + alpha_upper: `float` (default: `None`) + Maximum alpha. + alpha_lower: `float` (default: `None`) + Minimum alpha. + beta_upper: `float` (default: `None`) + Maximum beta. + beta_lower: `float` (default: `None`) + Minimum beta. + gamma_upper: `float` (default: `None`) + Maximum gamma. + gamma_lower: `float` (default: `None`) + Minimum gamma. + scale_cc_upper: `float` (default: `None`) + Maximum scale_cc. + scale_cc_lower: `float` (default: `None`) + Minimum scale_cc. + + Returns + ------- + velo_s_genes, velo_u_genes, velo_chrom_genes: `.var` + new velocity genes for each modalities. + """ + + v_genes = (adata.var['fit_likelihood'] >= likelihood_lower) + if rescale_u_upper is not None: + v_genes &= adata.var['fit_rescale_u'] <= rescale_u_upper + if rescale_u_lower is not None: + v_genes &= adata.var['fit_rescale_u'] >= rescale_u_lower + if rescale_c_upper is not None: + v_genes &= adata.var['fit_rescale_c'] <= rescale_c_upper + if rescale_c_lower is not None: + v_genes &= adata.var['fit_rescale_c'] >= rescale_c_lower + t_sw1 = adata.var['fit_t_sw1'] + 20 / adata.uns['velo_s_params']['t'] * \ + adata.var['fit_anchor_min_idx'] * adata.var['fit_alignment_scaling'] + if primed_upper is not None: + v_genes &= t_sw1 <= primed_upper + if primed_lower is not None: + v_genes &= t_sw1 >= primed_lower + t_sw2 = np.clip(adata.var['fit_t_sw2'], None, 20) + t_sw3 = np.clip(adata.var['fit_t_sw3'], None, 20) + t_interval3 = t_sw3 - t_sw2 + if decoupled_upper is not None: + v_genes &= t_interval3 <= decoupled_upper + if decoupled_lower is not None: + v_genes &= t_interval3 >= decoupled_lower + if alpha_c_upper is not None: + v_genes &= adata.var['fit_alpha_c'] <= alpha_c_upper + if alpha_c_lower is not None: + v_genes &= adata.var['fit_alpha_c'] >= alpha_c_lower + if alpha_upper is not None: + v_genes &= adata.var['fit_alpha'] <= alpha_upper + if alpha_lower is not None: + v_genes &= adata.var['fit_alpha'] >= alpha_lower + if beta_upper is not None: + v_genes &= adata.var['fit_beta'] <= beta_upper + if beta_lower is not None: + v_genes &= adata.var['fit_beta'] >= beta_lower + if gamma_upper is not None: + v_genes &= adata.var['fit_gamma'] <= gamma_upper + if gamma_lower is not None: + v_genes &= adata.var['fit_gamma'] >= gamma_lower + if scale_cc_upper is not None: + v_genes &= adata.var['fit_scale_cc'] <= scale_cc_upper + if scale_cc_lower is not None: + v_genes &= adata.var['fit_scale_cc'] >= scale_cc_lower + main_info(f'{np.sum(v_genes)} velocity genes were selected', indent_level=1) + adata.var['velo_s_genes'] = adata.var['velo_u_genes'] = \ + adata.var['velo_chrom_genes'] = v_genes + + +def velocity_graph(adata, vkey='velo_s', xkey='Ms', **kwargs): + """Computes velocity graph. + + This function normalizes the velocity matrix and computes velocity graph + with `scvelo.tl.velocity_graph`. + + Parameters + ---------- + adata: :class:`~anndata.AnnData` + Anndata result from dynamics recovery. + vkey: `str` (default: `velo_s`) + Default to use spliced velocities. + xkey: `str` (default: `Ms`) + Default to use smoothed spliced counts. + Additional parameters passed to `scvelo.tl.velocity_graph`. + + Returns + ------- + Normalized velocity matrix and associated velocity genes and params. + Outputs of `scvelo.tl.velocity_graph`. + """ + if vkey not in adata.layers.keys(): + raise ValueError('Velocity matrix is not found. Please run multivelo' + '.recover_dynamics_chrom function first.') + if vkey+'_norm' not in adata.layers.keys(): + adata.layers[vkey+'_norm'] = adata.layers[vkey] / np.sum( + np.abs(adata.layers[vkey]), 0) + adata.layers[vkey+'_norm'] /= np.mean(adata.layers[vkey+'_norm']) + adata.uns[vkey+'_norm_params'] = adata.uns[vkey+'_params'] + if vkey+'_norm_genes' not in adata.var.columns: + adata.var[vkey+'_norm_genes'] = adata.var[vkey+'_genes'] + scv.tl.velocity_graph(adata, vkey=vkey+'_norm', xkey=xkey, **kwargs) + + +def velocity_embedding_stream(adata, vkey='velo_s', show=True, **kwargs): + """Plots velocity stream. + + This function plots velocity streamplot with + `scvelo.pl.velocity_embedding_stream`. + + Parameters + ---------- + adata: :class:`~anndata.AnnData` + Anndata result from dynamics recovery. + vkey: `str` (default: `velo_s`) + Default to use spliced velocities. The normalized matrix will be used. + show: `bool` (default: `True`) + Whether to show the plot. + Additional parameters passed to `scvelo.tl.velocity_graph`. + + Returns + ------- + If `show==False`, a matplotlib axis object. + """ + if vkey not in adata.layers: + raise ValueError('Velocity matrix is not found. Please run multivelo.' + 'recover_dynamics_chrom function first.') + if vkey+'_norm' not in adata.layers.keys(): + adata.layers[vkey+'_norm'] = adata.layers[vkey] / np.sum( + np.abs(adata.layers[vkey]), 0) + adata.uns[vkey+'_norm_params'] = adata.uns[vkey+'_params'] + if vkey+'_norm_genes' not in adata.var.columns: + adata.var[vkey+'_norm_genes'] = adata.var[vkey+'_genes'] + if vkey+'_norm_graph' not in adata.uns.keys(): + velocity_graph(adata, vkey=vkey, **kwargs) + out = scv.pl.velocity_embedding_stream(adata, vkey=vkey+'_norm', show=show, + **kwargs) + if not show: + return out + + +def latent_time(adata, vkey='velo_s', **kwargs): + """Computes latent time. + + This function computes latent time with `scvelo.tl.latent_time`. + + Parameters + ---------- + adata: :class:`~anndata.AnnData` + Anndata result from dynamics recovery. + vkey: `str` (default: `velo_s`) + Default to use spliced velocities. The normalized matrix will be used. + Additional parameters passed to `scvelo.tl.velocity_graph`. + + Returns + ------- + Outputs of `scvelo.tl.latent_time`. + """ + if vkey not in adata.layers.keys() or 'fit_t' not in adata.layers.keys(): + raise ValueError('Velocity or time matrix is not found. Please run ' + 'multivelo.recover_dynamics_chrom function first.') + if vkey+'_norm' not in adata.layers.keys(): + raise ValueError('Normalized velocity matrix is not found. Please ' + 'run multivelo.velocity_graph function first.') + if vkey+'_norm_graph' not in adata.uns.keys(): + velocity_graph(adata, vkey=vkey, **kwargs) + scv.tl.latent_time(adata, vkey=vkey+'_norm', **kwargs) + + +def LRT_decoupling(adata_rna, adata_atac, **kwargs): + """Computes likelihood ratio test for decoupling state. + + This function computes whether keeping decoupling state improves fit + Likelihood. + + Parameters + ---------- + adata_rna: :class:`~anndata.AnnData` + RNA anndata object + adata_atac: :class:`~anndata.AnnData` + ATAC anndata object. + Additional parameters passed to `recover_dynamics_chrom`. + + Returns + ------- + adata_result_w_decoupled: class:`~anndata.AnnData` + fit result with decoupling state + adata_result_w_decoupled: class:`~anndata.AnnData` + fit result without decoupling state + res: `pandas.DataFrame` + LRT statistics + """ + from scipy.stats.distributions import chi2 + main_info('fitting models with decoupling intervals', v=0) + adata_result_w_decoupled = recover_dynamics_chrom(adata_rna, adata_atac, + fit_decoupling=True, + **kwargs) + main_info('fitting models without decoupling intervals', v=0) + adata_result_wo_decoupled = recover_dynamics_chrom(adata_rna, adata_atac, + fit_decoupling=False, + **kwargs) + main_info('testing likelihood ratio', v=0) + shared_genes = pd.Index(np.intersect1d(adata_result_w_decoupled.var_names, + adata_result_wo_decoupled.var_names) + ) + l_c_w_decoupled = adata_result_w_decoupled[:, shared_genes].\ + var['fit_likelihood_c'].values + l_c_wo_decoupled = adata_result_wo_decoupled[:, shared_genes].\ + var['fit_likelihood_c'].values + n_obs = adata_rna.n_obs + LRT_c = -2 * n_obs * (np.log(l_c_wo_decoupled) - np.log(l_c_w_decoupled)) + p_c = chi2.sf(LRT_c, 1) + l_w_decoupled = adata_result_w_decoupled[:, shared_genes].\ + var['fit_likelihood'].values + l_wo_decoupled = adata_result_wo_decoupled[:, shared_genes].\ + var['fit_likelihood'].values + LRT = -2 * n_obs * (np.log(l_wo_decoupled) - np.log(l_w_decoupled)) + p = chi2.sf(LRT, 1) + res = pd.DataFrame({'likelihood_c_w_decoupled': l_c_w_decoupled, + 'likelihood_c_wo_decoupled': l_c_wo_decoupled, + 'LRT_c': LRT_c, + 'pval_c': p_c, + 'likelihood_w_decoupled': l_w_decoupled, + 'likelihood_wo_decoupled': l_wo_decoupled, + 'LRT': LRT, + 'pval': p, + }, index=shared_genes) + return adata_result_w_decoupled, adata_result_wo_decoupled, res + + +def transition_matrix_s(s_mat, velo_s, knn): + knn = knn.astype(int) + tm_val, tm_col, tm_row = [], [], [] + for i in range(knn.shape[0]): + two_step_knn = knn[i, :] + for j in knn[i, :]: + two_step_knn = np.append(two_step_knn, knn[j, :]) + two_step_knn = np.unique(two_step_knn) + for j in two_step_knn: + s = s_mat[i, :] + sn = s_mat[j, :] + ds = s - sn + dx = np.ravel(ds.A) + velo = velo_s[i, :] + cos_sim = np.dot(dx, velo)/(norm(dx)*norm(velo)) + tm_val.append(cos_sim) + tm_col.append(j) + tm_row.append(i) + tm = coo_matrix((tm_val, (tm_row, tm_col)), shape=(s_mat.shape[0], + s_mat.shape[0])).tocsr() + tm.setdiag(0) + tm_neg = tm.copy() + tm.data = np.clip(tm.data, 0, 1) + tm_neg.data = np.clip(tm_neg.data, -1, 0) + tm.eliminate_zeros() + tm_neg.eliminate_zeros() + return tm, tm_neg + + +def transition_matrix_chrom(c_mat, u_mat, s_mat, velo_c, velo_u, velo_s, knn): + knn = knn.astype(int) + tm_val, tm_col, tm_row = [], [], [] + for i in range(knn.shape[0]): + two_step_knn = knn[i, :] + for j in knn[i, :]: + two_step_knn = np.append(two_step_knn, knn[j, :]) + two_step_knn = np.unique(two_step_knn) + for j in two_step_knn: + u = u_mat[i, :].A + s = s_mat[i, :].A + c = c_mat[i, :].A + un = u_mat[j, :] + sn = s_mat[j, :] + cn = c_mat[j, :] + dc = (c - cn) / np.std(c) + du = (u - un) / np.std(u) + ds = (s - sn) / np.std(s) + dx = np.ravel(np.hstack((dc.A, du.A, ds.A))) + velo = np.hstack((velo_c[i, :], velo_u[i, :], velo_s[i, :])) + cos_sim = np.dot(dx, velo)/(norm(dx)*norm(velo)) + tm_val.append(cos_sim) + tm_col.append(j) + tm_row.append(i) + tm = coo_matrix((tm_val, (tm_row, tm_col)), shape=(c_mat.shape[0], + c_mat.shape[0])).tocsr() + tm.setdiag(0) + tm_neg = tm.copy() + tm.data = np.clip(tm.data, 0, 1) + tm_neg.data = np.clip(tm_neg.data, -1, 0) + tm.eliminate_zeros() + tm_neg.eliminate_zeros() + return tm, tm_neg + + +def likelihood_plot(adata, + genes=None, + figsize=(14, 10), + bins=50, + pointsize=4 + ): + """Likelihood plots. + + This function plots likelihood and variable distributions. + + Parameters + ---------- + adata: :class:`~anndata.AnnData` + Anndata result from dynamics recovery. + genes: `str`, list of `str` (default: `None`) + If `None`, will use all fitted genes. + figsize: `tuple` (default: (14,10)) + Figure size. + bins: `int` (default: 50) + Number of bins for histograms. + pointsize: `float` (default: 4) + Point size for scatter plots. + """ + if genes is None: + var = adata.var + else: + genes = np.array(genes) + var = adata[:, genes].var + likelihood = var[['fit_likelihood']].values + rescale_u = var[['fit_rescale_u']].values + rescale_c = var[['fit_rescale_c']].values + t_interval1 = var['fit_t_sw1'] + 20 / adata.uns['velo_s_params']['t'] \ + * var['fit_anchor_min_idx'] * var['fit_alignment_scaling'] + t_sw2 = np.clip(var['fit_t_sw2'], None, 20) + t_sw3 = np.clip(var['fit_t_sw3'], None, 20) + t_interval3 = t_sw3 - t_sw2 + log_s = np.log1p(np.sum(adata.layers['Ms'], axis=0)) + alpha_c = var[['fit_alpha_c']].values + alpha = var[['fit_alpha']].values + beta = var[['fit_beta']].values + gamma = var[['fit_gamma']].values + scale_cc = var[['fit_scale_cc']].values + + fig, axes = plt.subplots(4, 5, figsize=figsize) + axes[0, 0].hist(likelihood, bins=bins) + axes[0, 0].set_title('likelihood') + axes[0, 1].hist(rescale_u, bins=bins) + axes[0, 1].set_title('rescale u') + axes[0, 2].hist(rescale_c, bins=bins) + axes[0, 2].set_title('rescale c') + axes[0, 3].hist(t_interval1.values, bins=bins) + axes[0, 3].set_title('primed interval') + axes[0, 4].hist(t_interval3, bins=bins) + axes[0, 4].set_title('decoupled interval') + + axes[1, 0].scatter(log_s, likelihood, s=pointsize) + axes[1, 0].set_xlabel('log spliced') + axes[1, 0].set_ylabel('likelihood') + axes[1, 1].scatter(rescale_u, likelihood, s=pointsize) + axes[1, 1].set_xlabel('rescale u') + axes[1, 2].scatter(rescale_c, likelihood, s=pointsize) + axes[1, 2].set_xlabel('rescale c') + axes[1, 3].scatter(t_interval1.values, likelihood, s=pointsize) + axes[1, 3].set_xlabel('primed interval') + axes[1, 4].scatter(t_interval3, likelihood, s=pointsize) + axes[1, 4].set_xlabel('decoupled interval') + + axes[2, 0].hist(alpha_c, bins=bins) + axes[2, 0].set_title('alpha c') + axes[2, 1].hist(alpha, bins=bins) + axes[2, 1].set_title('alpha') + axes[2, 2].hist(beta, bins=bins) + axes[2, 2].set_title('beta') + axes[2, 3].hist(gamma, bins=bins) + axes[2, 3].set_title('gamma') + axes[2, 4].hist(scale_cc, bins=bins) + axes[2, 4].set_title('scale cc') + + axes[3, 0].scatter(alpha_c, likelihood, s=pointsize) + axes[3, 0].set_xlabel('alpha c') + axes[3, 0].set_ylabel('likelihood') + axes[3, 1].scatter(alpha, likelihood, s=pointsize) + axes[3, 1].set_xlabel('alpha') + axes[3, 2].scatter(beta, likelihood, s=pointsize) + axes[3, 2].set_xlabel('beta') + axes[3, 3].scatter(gamma, likelihood, s=pointsize) + axes[3, 3].set_xlabel('gamma') + axes[3, 4].scatter(scale_cc, likelihood, s=pointsize) + axes[3, 4].set_xlabel('scale cc') + fig.tight_layout() + + +def pie_summary(adata, genes=None): + """Summary of directions and models. + + This function plots a pie chart for (pre-determined or specified) + directions and models. + `induction`: induction-only genes. + `repression`: repression-only genes. + `Model 1`: model 1 complete genes. + `Model 2`: model 2 complete genes. + + Parameters + ---------- + adata: :class:`~anndata.AnnData` + Anndata result from dynamics recovery. + genes: `str`, list of `str` (default: `None`) + If `None`, will use all fitted genes. + """ + if genes is None: + genes = adata.var_names + fit_model = adata[:, (adata.var['fit_direction'] == 'complete') & + np.isin(adata.var_names, genes)].var['fit_model'].values + fit_direction = adata[:, genes].var['fit_direction'].values + data = [np.sum(fit_direction == 'on'), np.sum(fit_direction == 'off'), + np.sum(fit_model == 1), np.sum(fit_model == 2)] + index = ['induction', 'repression', 'Model 1', 'Model 2'] + index = [x for i, x in enumerate(index) if data[i] > 0] + data = [x for x in data if x > 0] + df = pd.DataFrame({'data': data}, index=index) + df.plot.pie(y='data', autopct='%1.1f%%', legend=False, startangle=30, + ylabel='') + circle = plt.Circle((0, 0), 0.8, fc='white') + fig = plt.gcf() + fig.gca().add_artist(circle) + + +def switch_time_summary(adata, genes=None): + """Summary of switch times. + + This function plots a box plot for observed switch times. + `primed`: primed intervals. + `coupled-on`: coupled induction intervals. + `decoupled`: decoupled intervals. + `coupled-off`: coupled repression intervals. + + Parameters + ---------- + adata: :class:`~anndata.AnnData` + Anndata result from dynamics recovery. + genes: `str`, list of `str` (default: `None`) + If `None`, will use velocity genes. + """ + t_sw = adata[:, adata.var['velo_s_genes'] + if genes is None + else genes] \ + .var[['fit_t_sw1', 'fit_t_sw2', 'fit_t_sw3']].copy() + t_sw = t_sw.mask(t_sw > 20, 20) + t_sw = t_sw.mask(t_sw < 0) + t_sw['interval 1'] = t_sw['fit_t_sw1'] + t_sw['t_sw2 - t_sw1'] = t_sw['fit_t_sw2'] - t_sw['fit_t_sw1'] + t_sw['t_sw3 - t_sw2'] = t_sw['fit_t_sw3'] - t_sw['fit_t_sw2'] + t_sw['20 - t_sw3'] = 20 - t_sw['fit_t_sw3'] + t_sw = t_sw.mask(t_sw <= 0) + t_sw = t_sw.mask(t_sw > 20) + t_sw.columns = pd.Index(['time 1', 'time 2', 'time 3', 'primed', + 'coupled-on', 'decoupled', 'coupled-off']) + t_sw = t_sw[['primed', 'coupled-on', 'decoupled', 'coupled-off']] + t_sw = t_sw / 20 + fig, ax = plt.subplots(figsize=(4, 5)) + ax = sns.boxplot(data=t_sw, width=0.5, palette='Set2', ax=ax) + ax.set_yticks(np.linspace(0, 1, 5)) + ax.set_title('Switch Intervals') + + +def dynamic_plot(adata, + genes, + by='expression', + color_by='state', + gene_time=True, + axis_on=True, + frame_on=True, + show_anchors=True, + show_switches=True, + downsample=1, + full_range=False, + figsize=None, + pointsize=2, + linewidth=1.5, + cmap='coolwarm' + ): + """Gene dynamics plot. + + This function plots accessibility, expression, or velocity by time. + + Parameters + ---------- + adata: :class:`~anndata.AnnData` + Anndata result from dynamics recovery. + genes: `str`, list of `str` + List of genes to plot. + by: `str` (default: `expression`) + Plot accessibilities and expressions if `expression`. Plot velocities + if `velocity`. + color_by: `str` (default: `state`) + Color by the four potential states if `state`. Other common values are + leiden, louvain, celltype, etc. + If not `state`, the color field must be present in `.uns`, which can + be pre-computed with `scanpy.pl.scatter`. + For `state`, red, orange, green, and blue represent state 1, 2, 3, and + 4, respectively. + gene_time: `bool` (default: `True`) + Whether to use individual gene fitted time, or shared global latent + time. + Mean values of 20 equal sized windows will be connected and shown if + `gene_time==False`. + axis_on: `bool` (default: `True`) + Whether to show axis labels. + frame_on: `bool` (default: `True`) + Whether to show plot frames. + show_anchors: `bool` (default: `True`) + Whether to display anchors. + show_switches: `bool` (default: `True`) + Whether to show switch times. The switch times are indicated by + vertical dotted line. + downsample: `int` (default: 1) + How much to downsample the cells. The remaining number will be + `1/downsample` of original. + full_range: `bool` (default: `False`) + Whether to show the full time range of velocities before smoothing or + subset to only smoothed range. + figsize: `tuple` (default: `None`) + Total figure size. + pointsize: `float` (default: 2) + Point size for scatter plots. + linewidth: `float` (default: 1.5) + Line width for anchor line or mean line. + cmap: `str` (default: `coolwarm`) + Color map for continuous color key. + """ + from pandas.api.types import is_numeric_dtype, is_categorical_dtype + if by not in ['expression', 'velocity']: + raise ValueError('"by" must be either "expression" or "velocity".') + if by == 'velocity': + show_switches = False + if color_by == 'state': + types = [0, 1, 2, 3] + colors = ['tab:red', 'tab:orange', 'tab:green', 'tab:blue'] + elif color_by in adata.obs and is_numeric_dtype(adata.obs[color_by]): + types = None + colors = adata.obs[color_by].values + elif color_by in adata.obs and is_categorical_dtype(adata.obs[color_by]) \ + and color_by+'_colors' in adata.uns.keys(): + types = adata.obs[color_by].cat.categories + colors = adata.uns[f'{color_by}_colors'] + else: + raise ValueError('Currently, color key must be a single string of ' + 'either numerical or categorical available in adata ' + 'obs, and the colors of categories can be found in ' + 'adata uns.') + + downsample = np.clip(int(downsample), 1, 10) + genes = np.array(genes) + missing_genes = genes[~np.isin(genes, adata.var_names)] + if len(missing_genes) > 0: + main_info(f'{missing_genes} not found', v=0) + genes = genes[np.isin(genes, adata.var_names)] + gn = len(genes) + if gn == 0: + return + if not gene_time: + show_anchors = False + latent_time = np.array(adata.obs['latent_time']) + time_window = latent_time // 0.05 + time_window = time_window.astype(int) + time_window[time_window == 20] = 19 + if 'velo_s_params' in adata.uns.keys() and 'outlier' \ + in adata.uns['velo_s_params']: + outlier = adata.uns['velo_s_params']['outlier'] + else: + outlier = 99 + + fig, axs = plt.subplots(gn, 3, squeeze=False, figsize=(10, 2.3*gn) + if figsize is None else figsize) + fig.patch.set_facecolor('white') + for row, gene in enumerate(genes): + u = adata[:, gene].layers['Mu' if by == 'expression' else 'velo_u'] + s = adata[:, gene].layers['Ms' if by == 'expression' else 'velo_s'] + c = adata[:, gene].layers['ATAC' if by == 'expression' + else 'velo_chrom'] + c = c.A if sparse.issparse(c) else c + u = u.A if sparse.issparse(u) else u + s = s.A if sparse.issparse(s) else s + c, u, s = np.ravel(c), np.ravel(u), np.ravel(s) + non_outlier = c <= np.percentile(c, outlier) + non_outlier &= u <= np.percentile(u, outlier) + non_outlier &= s <= np.percentile(s, outlier) + c, u, s = c[non_outlier], u[non_outlier], s[non_outlier] + time = np.array(adata[:, gene].layers['fit_t'] if gene_time + else latent_time) + if by == 'velocity': + time = np.reshape(time, (-1, 1)) + time = np.ravel(adata.obsp['_RNA_conn'].dot(time)) + time = time[non_outlier] + if types is not None: + for i in range(len(types)): + if color_by == 'state': + filt = adata[non_outlier, gene].layers['fit_state'] \ + == types[i] + else: + filt = adata[non_outlier, :].obs[color_by] == types[i] + filt = np.ravel(filt) + if np.sum(filt) > 0: + axs[row, 0].scatter(time[filt][::downsample], + c[filt][::downsample], s=pointsize, + c=colors[i], alpha=0.6) + axs[row, 1].scatter(time[filt][::downsample], + u[filt][::downsample], + s=pointsize, c=colors[i], alpha=0.6) + axs[row, 2].scatter(time[filt][::downsample], + s[filt][::downsample], s=pointsize, + c=colors[i], alpha=0.6) + else: + axs[row, 0].scatter(time[::downsample], c[::downsample], + s=pointsize, + c=colors[non_outlier][::downsample], + alpha=0.6, cmap=cmap) + axs[row, 1].scatter(time[::downsample], u[::downsample], + s=pointsize, + c=colors[non_outlier][::downsample], + alpha=0.6, cmap=cmap) + axs[row, 2].scatter(time[::downsample], s[::downsample], + s=pointsize, + c=colors[non_outlier][::downsample], + alpha=0.6, cmap=cmap) + + if not gene_time: + window_count = np.zeros(20) + window_mean_c = np.zeros(20) + window_mean_u = np.zeros(20) + window_mean_s = np.zeros(20) + for i in np.unique(time_window[non_outlier]): + idx = time_window[non_outlier] == i + window_count[i] = np.sum(idx) + window_mean_c[i] = np.mean(c[idx]) + window_mean_u[i] = np.mean(u[idx]) + window_mean_s[i] = np.mean(s[idx]) + window_idx = np.where(window_count > 20)[0] + axs[row, 0].plot(window_idx*0.05+0.025, window_mean_c[window_idx], + linewidth=linewidth, color='black', alpha=0.5) + axs[row, 1].plot(window_idx*0.05+0.025, window_mean_u[window_idx], + linewidth=linewidth, color='black', alpha=0.5) + axs[row, 2].plot(window_idx*0.05+0.025, window_mean_s[window_idx], + linewidth=linewidth, color='black', alpha=0.5) + + if show_anchors: + n_anchors = adata.uns['velo_s_params']['t'] + t_sw_array = np.array([adata[:, gene].var['fit_t_sw1'], + adata[:, gene].var['fit_t_sw2'], + adata[:, gene].var['fit_t_sw3']]) + t_sw_array = t_sw_array[t_sw_array < 20] + min_idx = int(adata[:, gene].var['fit_anchor_min_idx']) + max_idx = int(adata[:, gene].var['fit_anchor_max_idx']) + old_t = np.linspace(0, 20, n_anchors)[min_idx:max_idx+1] + new_t = old_t - np.min(old_t) + new_t = new_t * 20 / np.max(new_t) + if by == 'velocity' and not full_range: + anchor_interval = 20 / (max_idx + 1 - min_idx) + min_idx = int(adata[:, gene].var['fit_anchor_velo_min_idx']) + max_idx = int(adata[:, gene].var['fit_anchor_velo_max_idx']) + start = 0 + (min_idx - + adata[:, gene].var['fit_anchor_min_idx']) \ + * anchor_interval + end = 20 + (max_idx - + adata[:, gene].var['fit_anchor_max_idx']) \ + * anchor_interval + new_t = np.linspace(start, end, max_idx + 1 - min_idx) + ax = axs[row, 0] + a_c = adata[:, gene].varm['fit_anchor_c' if by == 'expression' + else 'fit_anchor_c_velo']\ + .ravel()[min_idx:max_idx+1] + if show_switches: + for t_sw in t_sw_array: + if t_sw > 0: + ax.vlines(t_sw, np.min(c), np.max(c), colors='black', + linestyles='dashed', alpha=0.5) + ax.plot(new_t[0:new_t.shape[0]], a_c, linewidth=linewidth, + color='black', alpha=0.5) + ax = axs[row, 1] + a_u = adata[:, gene].varm['fit_anchor_u' if by == 'expression' + else 'fit_anchor_u_velo']\ + .ravel()[min_idx:max_idx+1] + if show_switches: + for t_sw in t_sw_array: + if t_sw > 0: + ax.vlines(t_sw, np.min(u), np.max(u), colors='black', + linestyles='dashed', alpha=0.5) + ax.plot(new_t[0:new_t.shape[0]], a_u, linewidth=linewidth, + color='black', alpha=0.5) + ax = axs[row, 2] + a_s = adata[:, gene].varm['fit_anchor_s' if by == 'expression' + else 'fit_anchor_s_velo']\ + .ravel()[min_idx:max_idx+1] + if show_switches: + for t_sw in t_sw_array: + if t_sw > 0: + ax.vlines(t_sw, np.min(s), np.max(s), colors='black', + linestyles='dashed', alpha=0.5) + ax.plot(new_t[0:new_t.shape[0]], a_s, linewidth=linewidth, + color='black', alpha=0.5) + + axs[row, 0].set_title(f'{gene} ATAC' if by == 'expression' + else f'{gene} chromatin velocity') + axs[row, 0].set_xlabel('t' if by == 'expression' else '~t') + axs[row, 0].set_ylabel('c' if by == 'expression' else 'dc/dt') + axs[row, 1].set_title(f'{gene} unspliced' + ('' if by == 'expression' + else ' velocity')) + axs[row, 1].set_xlabel('t' if by == 'expression' else '~t') + axs[row, 1].set_ylabel('u' if by == 'expression' else 'du/dt') + axs[row, 2].set_title(f'{gene} spliced' + ('' if by == 'expression' + else ' velocity')) + axs[row, 2].set_xlabel('t' if by == 'expression' else '~t') + axs[row, 2].set_ylabel('s' if by == 'expression' else 'ds/dt') + + for j in range(3): + ax = axs[row, j] + if not axis_on: + ax.xaxis.set_ticks_position('none') + ax.yaxis.set_ticks_position('none') + ax.get_xaxis().set_visible(False) + ax.get_yaxis().set_visible(False) + if not frame_on: + ax.xaxis.set_ticks_position('none') + ax.yaxis.set_ticks_position('none') + ax.set_frame_on(False) + fig.tight_layout() + + +def scatter_plot(adata, + genes, + by='us', + color_by='state', + n_cols=5, + axis_on=True, + frame_on=True, + show_anchors=True, + show_switches=True, + show_all_anchors=False, + title_more_info=False, + velocity_arrows=False, + downsample=1, + figsize=None, + pointsize=2, + markersize=5, + linewidth=2, + cmap='coolwarm', + view_3d_elev=None, + view_3d_azim=None, + full_name=False + ): + """Gene scatter plot. + + This function plots phase portraits of the specified plane. + + Parameters + ---------- + adata: :class:`~anndata.AnnData` + Anndata result from dynamics recovery. + genes: `str`, list of `str` + List of genes to plot. + by: `str` (default: `us`) + Plot unspliced-spliced plane if `us`. Plot chromatin-unspliced plane + if `cu`. + Plot 3D phase portraits if `cus`. + color_by: `str` (default: `state`) + Color by the four potential states if `state`. Other common values are + leiden, louvain, celltype, etc. + If not `state`, the color field must be present in `.uns`, which can be + pre-computed with `scanpy.pl.scatter`. + For `state`, red, orange, green, and blue represent state 1, 2, 3, and + 4, respectively. + When `by=='us'`, `color_by` can also be `c`, which displays the log + accessibility on U-S phase portraits. + n_cols: `int` (default: 5) + Number of columns to plot on each row. + axis_on: `bool` (default: `True`) + Whether to show axis labels. + frame_on: `bool` (default: `True`) + Whether to show plot frames. + show_anchors: `bool` (default: `True`) + Whether to display anchors. + show_switches: `bool` (default: `True`) + Whether to show switch times. The three switch times and the end of + trajectory are indicated by + circle, cross, dismond, and star, respectively. + show_all_anchors: `bool` (default: `False`) + Whether to display full range of (predicted) anchors even for + repression-only genes. + title_more_info: `bool` (default: `False`) + Whether to display model, direction, and likelihood information for + the gene in title. + velocity_arrows: `bool` (default: `False`) + Whether to show velocity arrows of cells on the phase portraits. + downsample: `int` (default: 1) + How much to downsample the cells. The remaining number will be + `1/downsample` of original. + figsize: `tuple` (default: `None`) + Total figure size. + pointsize: `float` (default: 2) + Point size for scatter plots. + markersize: `float` (default: 5) + Point size for switch time points. + linewidth: `float` (default: 2) + Line width for connected anchors. + cmap: `str` (default: `coolwarm`) + Color map for log accessibilities or other continuous color keys when + plotting on U-S plane. + view_3d_elev: `float` (default: `None`) + Matplotlib 3D plot `elev` argument. `elev=90` is the same as U-S plane, + and `elev=0` is the same as C-U plane. + view_3d_azim: `float` (default: `None`) + Matplotlib 3D plot `azim` argument. `azim=270` is the same as U-S + plane, and `azim=0` is the same as C-U plane. + full_name: `bool` (default: `False`) + Show full names for chromatin, unspliced, and spliced rather than + using abbreviated terms c, u, and s. + """ + from pandas.api.types import is_numeric_dtype, is_categorical_dtype + if by not in ['us', 'cu', 'cus']: + raise ValueError("'by' argument must be one of ['us', 'cu', 'cus']") + if color_by == 'state': + types = [0, 1, 2, 3] + colors = ['tab:red', 'tab:orange', 'tab:green', 'tab:blue'] + elif by == 'us' and color_by == 'c': + types = None + elif color_by in adata.obs and is_numeric_dtype(adata.obs[color_by]): + types = None + colors = adata.obs[color_by].values + elif color_by in adata.obs and is_categorical_dtype(adata.obs[color_by]) \ + and color_by+'_colors' in adata.uns.keys(): + types = adata.obs[color_by].cat.categories + colors = adata.uns[f'{color_by}_colors'] + else: + raise ValueError('Currently, color key must be a single string of ' + 'either numerical or categorical available in adata' + ' obs, and the colors of categories can be found in' + ' adata uns.') + + if 'velo_s_params' not in adata.uns.keys() \ + or 'fit_anchor_s' not in adata.varm.keys(): + show_anchors = False + if color_by == 'state' and 'fit_state' not in adata.layers.keys(): + raise ValueError('fit_state is not found. Please run ' + 'recover_dynamics_chrom function first or provide a ' + 'valid color key.') + + downsample = np.clip(int(downsample), 1, 10) + genes = np.array(genes) + missing_genes = genes[~np.isin(genes, adata.var_names)] + if len(missing_genes) > 0: + main_info(f'{missing_genes} not found', v=0) + genes = genes[np.isin(genes, adata.var_names)] + gn = len(genes) + if gn == 0: + return + if gn < n_cols: + n_cols = gn + if by == 'cus': + fig, axs = plt.subplots(-(-gn // n_cols), n_cols, squeeze=False, + figsize=(3.2*n_cols, 2.7*(-(-gn // n_cols))) + if figsize is None else figsize, + subplot_kw={'projection': '3d'}) + else: + fig, axs = plt.subplots(-(-gn // n_cols), n_cols, squeeze=False, + figsize=(2.7*n_cols, 2.4*(-(-gn // n_cols))) + if figsize is None else figsize) + fig.patch.set_facecolor('white') + count = 0 + for gene in genes: + u = adata[:, gene].layers['Mu'].copy() if 'Mu' in adata.layers \ + else adata[:, gene].layers['unspliced'].copy() + s = adata[:, gene].layers['Ms'].copy() if 'Ms' in adata.layers \ + else adata[:, gene].layers['spliced'].copy() + u = u.A if sparse.issparse(u) else u + s = s.A if sparse.issparse(s) else s + u, s = np.ravel(u), np.ravel(s) + if 'ATAC' not in adata.layers.keys() and \ + 'Mc' not in adata.layers.keys(): + show_anchors = False + elif 'ATAC' in adata.layers.keys(): + c = adata[:, gene].layers['ATAC'].copy() + c = c.A if sparse.issparse(c) else c + c = np.ravel(c) + elif 'Mc' in adata.layers.keys(): + c = adata[:, gene].layers['Mc'].copy() + c = c.A if sparse.issparse(c) else c + c = np.ravel(c) + + if velocity_arrows: + if 'velo_u' in adata.layers.keys(): + vu = adata[:, gene].layers['velo_u'].copy() + elif 'velocity_u' in adata.layers.keys(): + vu = adata[:, gene].layers['velocity_u'].copy() + else: + vu = np.zeros(adata.n_obs) + max_u = np.max([np.max(u), 1e-6]) + u /= max_u + vu = np.ravel(vu) + vu /= np.max([np.max(np.abs(vu)), 1e-6]) + if 'velo_s' in adata.layers.keys(): + vs = adata[:, gene].layers['velo_s'].copy() + elif 'velocity' in adata.layers.keys(): + vs = adata[:, gene].layers['velocity'].copy() + max_s = np.max([np.max(s), 1e-6]) + s /= max_s + vs = np.ravel(vs) + vs /= np.max([np.max(np.abs(vs)), 1e-6]) + if 'velo_chrom' in adata.layers.keys(): + vc = adata[:, gene].layers['velo_chrom'].copy() + max_c = np.max([np.max(c), 1e-6]) + c /= max_c + vc = np.ravel(vc) + vc /= np.max([np.max(np.abs(vc)), 1e-6]) + + row = count // n_cols + col = count % n_cols + ax = axs[row, col] + if types is not None: + for i in range(len(types)): + if color_by == 'state': + filt = adata[:, gene].layers['fit_state'] == types[i] + else: + filt = adata.obs[color_by] == types[i] + filt = np.ravel(filt) + if by == 'us': + if velocity_arrows: + ax.quiver(s[filt][::downsample], u[filt][::downsample], + vs[filt][::downsample], + vu[filt][::downsample], color=colors[i], + alpha=0.5, scale_units='xy', scale=10, + width=0.005, headwidth=4, headaxislength=5.5) + else: + ax.scatter(s[filt][::downsample], + u[filt][::downsample], s=pointsize, + c=colors[i], alpha=0.7) + elif by == 'cu': + if velocity_arrows: + ax.quiver(u[filt][::downsample], + c[filt][::downsample], + vu[filt][::downsample], + vc[filt][::downsample], color=colors[i], + alpha=0.5, scale_units='xy', scale=10, + width=0.005, headwidth=4, headaxislength=5.5) + else: + ax.scatter(u[filt][::downsample], + c[filt][::downsample], s=pointsize, + c=colors[i], alpha=0.7) + else: + if velocity_arrows: + ax.quiver(s[filt][::downsample], + u[filt][::downsample], c[filt][::downsample], + vs[filt][::downsample], + vu[filt][::downsample], + vc[filt][::downsample], + color=colors[i], alpha=0.4, length=0.1, + arrow_length_ratio=0.5, normalize=True) + else: + ax.scatter(s[filt][::downsample], + u[filt][::downsample], + c[filt][::downsample], s=pointsize, + c=colors[i], alpha=0.7) + elif color_by == 'c': + if 'velo_s_params' in adata.uns.keys() and \ + 'outlier' in adata.uns['velo_s_params']: + outlier = adata.uns['velo_s_params']['outlier'] + else: + outlier = 99.8 + non_zero = (u > 0) & (s > 0) & (c > 0) + non_outlier = u < np.percentile(u, outlier) + non_outlier &= s < np.percentile(s, outlier) + non_outlier &= c < np.percentile(c, outlier) + c -= np.min(c) + c /= np.max(c) + if velocity_arrows: + ax.quiver(s[non_zero & non_outlier][::downsample], + u[non_zero & non_outlier][::downsample], + vs[non_zero & non_outlier][::downsample], + vu[non_zero & non_outlier][::downsample], + np.log1p(c[non_zero & non_outlier][::downsample]), + alpha=0.5, + scale_units='xy', scale=10, width=0.005, + headwidth=4, headaxislength=5.5, cmap=cmap) + else: + ax.scatter(s[non_zero & non_outlier][::downsample], + u[non_zero & non_outlier][::downsample], + s=pointsize, + c=np.log1p(c[non_zero & non_outlier][::downsample]), + alpha=0.8, cmap=cmap) + else: + if by == 'us': + if velocity_arrows: + ax.quiver(s[::downsample], u[::downsample], + vs[::downsample], vu[::downsample], + colors[::downsample], alpha=0.5, + scale_units='xy', scale=10, width=0.005, + headwidth=4, headaxislength=5.5, cmap=cmap) + else: + ax.scatter(s[::downsample], u[::downsample], s=pointsize, + c=colors[::downsample], alpha=0.7, cmap=cmap) + elif by == 'cu': + if velocity_arrows: + ax.quiver(u[::downsample], c[::downsample], + vu[::downsample], vc[::downsample], + colors[::downsample], alpha=0.5, + scale_units='xy', scale=10, width=0.005, + headwidth=4, headaxislength=5.5, cmap=cmap) + else: + ax.scatter(u[::downsample], c[::downsample], s=pointsize, + c=colors[::downsample], alpha=0.7, cmap=cmap) + else: + if velocity_arrows: + ax.quiver(s[::downsample], u[::downsample], + c[::downsample], vs[::downsample], + vu[::downsample], vc[::downsample], + colors[::downsample], alpha=0.4, length=0.1, + arrow_length_ratio=0.5, normalize=True, + cmap=cmap) + else: + ax.scatter(s[::downsample], u[::downsample], + c[::downsample], s=pointsize, + c=colors[::downsample], alpha=0.7, cmap=cmap) + + if show_anchors: + min_idx = int(adata[:, gene].var['fit_anchor_min_idx']) + max_idx = int(adata[:, gene].var['fit_anchor_max_idx']) + a_c = adata[:, gene].varm['fit_anchor_c']\ + .ravel()[min_idx:max_idx+1].copy() + a_u = adata[:, gene].varm['fit_anchor_u']\ + .ravel()[min_idx:max_idx+1].copy() + a_s = adata[:, gene].varm['fit_anchor_s']\ + .ravel()[min_idx:max_idx+1].copy() + if velocity_arrows: + a_c /= max_c + a_u /= max_u + a_s /= max_s + if by == 'us': + ax.plot(a_s, a_u, linewidth=linewidth, color='black', + alpha=0.7, zorder=1000) + elif by == 'cu': + ax.plot(a_u, a_c, linewidth=linewidth, color='black', + alpha=0.7, zorder=1000) + else: + ax.plot(a_s, a_u, a_c, linewidth=linewidth, color='black', + alpha=0.7, zorder=1000) + if show_all_anchors: + a_c_pre = adata[:, gene].varm['fit_anchor_c']\ + .ravel()[:min_idx].copy() + a_u_pre = adata[:, gene].varm['fit_anchor_u']\ + .ravel()[:min_idx].copy() + a_s_pre = adata[:, gene].varm['fit_anchor_s']\ + .ravel()[:min_idx].copy() + if velocity_arrows: + a_c_pre /= max_c + a_u_pre /= max_u + a_s_pre /= max_s + if len(a_c_pre) > 0: + if by == 'us': + ax.plot(a_s_pre, a_u_pre, linewidth=linewidth/1.3, + color='black', alpha=0.6, zorder=1000) + elif by == 'cu': + ax.plot(a_u_pre, a_c_pre, linewidth=linewidth/1.3, + color='black', alpha=0.6, zorder=1000) + else: + ax.plot(a_s_pre, a_u_pre, a_c_pre, + linewidth=linewidth/1.3, color='black', + alpha=0.6, zorder=1000) + if show_switches: + t_sw_array = np.array([adata[:, gene].var['fit_t_sw1'] + .values[0], + adata[:, gene].var['fit_t_sw2'] + .values[0], + adata[:, gene].var['fit_t_sw3'] + .values[0]]) + in_range = (t_sw_array > 0) & (t_sw_array < 20) + a_c_sw = adata[:, gene].varm['fit_anchor_c_sw'].ravel().copy() + a_u_sw = adata[:, gene].varm['fit_anchor_u_sw'].ravel().copy() + a_s_sw = adata[:, gene].varm['fit_anchor_s_sw'].ravel().copy() + if velocity_arrows: + a_c_sw /= max_c + a_u_sw /= max_u + a_s_sw /= max_s + if in_range[0]: + c_sw1, u_sw1, s_sw1 = a_c_sw[0], a_u_sw[0], a_s_sw[0] + if by == 'us': + ax.plot([s_sw1], [u_sw1], "om", markersize=markersize, + zorder=2000) + elif by == 'cu': + ax.plot([u_sw1], [c_sw1], "om", markersize=markersize, + zorder=2000) + else: + ax.plot([s_sw1], [u_sw1], [c_sw1], "om", + markersize=markersize, zorder=2000) + if in_range[1]: + c_sw2, u_sw2, s_sw2 = a_c_sw[1], a_u_sw[1], a_s_sw[1] + if by == 'us': + ax.plot([s_sw2], [u_sw2], "Xm", markersize=markersize, + zorder=2000) + elif by == 'cu': + ax.plot([u_sw2], [c_sw2], "Xm", markersize=markersize, + zorder=2000) + else: + ax.plot([s_sw2], [u_sw2], [c_sw2], "Xm", + markersize=markersize, zorder=2000) + if in_range[2]: + c_sw3, u_sw3, s_sw3 = a_c_sw[2], a_u_sw[2], a_s_sw[2] + if by == 'us': + ax.plot([s_sw3], [u_sw3], "Dm", markersize=markersize, + zorder=2000) + elif by == 'cu': + ax.plot([u_sw3], [c_sw3], "Dm", markersize=markersize, + zorder=2000) + else: + ax.plot([s_sw3], [u_sw3], [c_sw3], "Dm", + markersize=markersize, zorder=2000) + if max_idx > adata.uns['velo_s_params']['t'] - 4: + if by == 'us': + ax.plot([a_s[-1]], [a_u[-1]], "*m", + markersize=markersize, zorder=2000) + elif by == 'cu': + ax.plot([a_u[-1]], [a_c[-1]], "*m", + markersize=markersize, zorder=2000) + else: + ax.plot([a_s[-1]], [a_u[-1]], [a_c[-1]], "*m", + markersize=markersize, zorder=2000) + + if by == 'cus' and \ + (view_3d_elev is not None or view_3d_azim is not None): + # US: elev=90, azim=270. CU: elev=0, azim=0. + ax.view_init(elev=view_3d_elev, azim=view_3d_azim) + title = gene + if title_more_info: + if 'fit_model' in adata.var: + title += f" M{int(adata[:,gene].var['fit_model'].values[0])}" + if 'fit_direction' in adata.var: + title += f" {adata[:,gene].var['fit_direction'].values[0]}" + if 'fit_likelihood' in adata.var \ + and not np.all(adata.var['fit_likelihood'].values == -1): + title += " " + f"{adata[:,gene].var['fit_likelihood'].values[0]:.3g}" + ax.set_title(f'{title}', fontsize=11) + if by == 'us': + ax.set_xlabel('spliced' if full_name else 's') + ax.set_ylabel('unspliced' if full_name else 'u') + elif by == 'cu': + ax.set_xlabel('unspliced' if full_name else 'u') + ax.set_ylabel('chromatin' if full_name else 'c') + elif by == 'cus': + ax.set_xlabel('spliced' if full_name else 's') + ax.set_ylabel('unspliced' if full_name else 'u') + ax.set_zlabel('chromatin' if full_name else 'c') + if by in ['us', 'cu']: + if not axis_on: + ax.xaxis.set_ticks_position('none') + ax.yaxis.set_ticks_position('none') + ax.get_xaxis().set_visible(False) + ax.get_yaxis().set_visible(False) + if not frame_on: + ax.xaxis.set_ticks_position('none') + ax.yaxis.set_ticks_position('none') + ax.set_frame_on(False) + elif by == 'cus': + if not axis_on: + ax.set_xlabel('') + ax.set_ylabel('') + ax.set_zlabel('') + ax.xaxis.set_ticklabels([]) + ax.yaxis.set_ticklabels([]) + ax.zaxis.set_ticklabels([]) + if not frame_on: + ax.xaxis._axinfo['grid']['color'] = (1, 1, 1, 0) + ax.yaxis._axinfo['grid']['color'] = (1, 1, 1, 0) + ax.zaxis._axinfo['grid']['color'] = (1, 1, 1, 0) + ax.xaxis._axinfo['tick']['inward_factor'] = 0 + ax.xaxis._axinfo['tick']['outward_factor'] = 0 + ax.yaxis._axinfo['tick']['inward_factor'] = 0 + ax.yaxis._axinfo['tick']['outward_factor'] = 0 + ax.zaxis._axinfo['tick']['inward_factor'] = 0 + ax.zaxis._axinfo['tick']['outward_factor'] = 0 + count += 1 + for i in range(col+1, n_cols): + fig.delaxes(axs[row, i]) + fig.tight_layout() \ No newline at end of file diff --git a/dynamo/multivelo/globals.py b/dynamo/multivelo/globals.py new file mode 100644 index 00000000..cb18de65 --- /dev/null +++ b/dynamo/multivelo/globals.py @@ -0,0 +1,58 @@ +import os +import platform + +# Determine platform on which analysis is running +running_on = platform.system() + +# Set up locale configuration here +REPO_PATH, ROOT_PATH = None, None # To make the lint checker happy ... +if running_on == 'Darwin': + # ... OSX system + # ... ... root path + ROOT_PATH = '/Users/cordessf/OneDrive' # <============= CHANGE THIS !!! + + # ... ... repo path + REPO_PATH = os.path.join(ROOT_PATH, 'ACI', 'Repositories') +elif running_on == 'Linux': + # ... Linux system + # ... ... root path + ROOT_PATH = '/data/LIRGE' # <============= CHANGE THIS !!! + + # ... ... repo path + REPO_PATH = os.path.join(ROOT_PATH, 'Repositories') + +# ... Path to base directory (where code and results are kept) +BASE_PATH = os.path.join(REPO_PATH, 'MultiDynamo') + +# ... Path to cache intermediate results +CACHE_PATH = os.path.join(ROOT_PATH, 'cache') +if not os.path.exists(CACHE_PATH): + os.makedirs(CACHE_PATH) + +# ... Path to data +DATA_PATH = os.path.join(ROOT_PATH, 'external_data', 'multiome') +if not os.path.exists(DATA_PATH): + os.makedirs(DATA_PATH) + +# ... Path to reference data +REFERENCE_DATA_PATH = os.path.join(ROOT_PATH, 'reference_data') +if not os.path.exists(REFERENCE_DATA_PATH): + os.makedirs(REFERENCE_DATA_PATH) + +# Structure the data as it would come out of a cellranger run +# ... cellranger outs directory +OUTS_PATH = os.path.join(DATA_PATH, 'outs') +if not os.path.exists(OUTS_PATH): + os.makedirs(OUTS_PATH) + +# Path to ATAC-seq data +ATAC_PATH = os.path.join(ROOT_PATH, 'external_data', '10k_human_PBMC_ATAC') + +# Path to genome annotation +GTF_PATH = os.path.join(REFERENCE_DATA_PATH, 'annotation', 'Homo_sapiens.GRCh38.112.gtf.gz') + +# Path to multiomic data +MULTIOME_PATH = DATA_PATH + +# Path to RNA-seq data +RNA_PATH = os.path.join(ROOT_PATH, 'external_data', '10k_human_PBMC_RNA') diff --git a/dynamo/multivelo/neural_nets/dir0.pt b/dynamo/multivelo/neural_nets/dir0.pt new file mode 100644 index 0000000000000000000000000000000000000000..92fb3454e63f82da1dac7f2ec5cd0141a6da960c GIT binary patch literal 117788 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 S`GXU1#VNj{>X7sZh7i6`I&Bu>DLoY86S4FM&$r z#d3d0cc^AZIuyyi>r&)?>J+|-lQ?bN_Z|*kh+sY+n#M+joi? zhiSWXh4q=+ju%P>8G;({X6`>&Gw6vo&F$fRa~t}vr@Z-E=i%Cmg?PNgg#F}t6B}G^ zFrN7bp=XC4t}RO>nya|?NlPLu9~j2T?Q$?v?Ki%cc+AEp&SW~DE6@bjdQjXUOpi>i zXYx&r@Q#%Rqhs|JpU2Shque2VoF62ZsCf_R$iCFmxFGSXUA_$zJ# zc_C>>b52i!h6Oi4cGm`cEhJAD{Pso%j-OsGCr^d1IgmY*c`*Da8bJqNmfUrBtz=kg8$cSpf8+F}W4Js<6cd+9 z75Tc)gay-Pl1V>;aE8Po!V!6y-Osb=&PCi_=iYy0uIp5OrhWo*|GEk+@Rg&+7Y3NP ze>QN{+nfk2(6Vp;a-Gd(AK-_@ZSXcZ6yg{@MTPl;q7cSVN@seZ8_d-DK z8JH~emKEe&4`SahQ4LohEfOM_+ck@L$Sj9rvN5pqup+G+or^NUao8kx6Zfz2r`ntY zxqkQ{EFCIEA*(?C$TKg<7mb9FC1#}dRyfDW_koj}lDXNe82^3AU``J^;X`dLdPVgm z)9ndGGb7cYpfH??T*>Xa4xGZFk`R=22H5N4kI#2(qa`0q$eGDkVN!|>`!`LMXn%c$ z%M0>JXKo6_pG-yDEo;mQF|LRIAxD-{xD{fws zK6;M{oVAvnD0+?@|K`Z~IkwZ}*`L5rVG^shE|}U~7blOmMMFT>89vGZPMzDv)=dax z9-WP+hVpBu^O38}3(peFKRShE@2PH%3f5ZdKELVS zcjnN?Wd7bSduVFjM^>&UiFG`&9ilR2*~JwOSfi1Mmsftkw@yMh9D5)4=xWlCe_URo zaT`1ODiISdRtzc#MN@#*U$4W)o#C)L z^&p%Gxs4CzKH>M$6yDUE3#O)6B$dyBTNguc7f9f8FQx%j@uOxsseMte@ zdFc}CkAHB7-%n(9Uc#Fr@#qv6#IHGj9W_&z!(XunaBJROzOv3wSa)RtT{1<7?jQ6A zgXlxVV(u_5_?W@wD*uL!FD1Aeq!r{woxmBX1E9>kfhNB?28)ZcSrtKfjQJ=>6F>gK zy!K_}_^disWP37LhkU^>GcB_5(+v2V*ALH!pMhU+5w4FAA(Ps=arL~pM411pu*muV zrXRWw#|o05@92D{YwdBIqtEe?xp!z}2-gA0j)S@zl9;Xe2&}R`)4zj;;TI1#seYd^hPsyAG;Z(Enmap{nfmNw<3_@H;Yc%b__nWn?v%3sdS5l3g$c# zCo2M{WAHj5Qkk=ozppuz@g0{Z9tttI@Z~Qs`?wo*FV(O%N4w#^`dmDiUxT0YL*SOj z4ZiThWF~A*HM}pEg+`|)=BMpDUgy^y#=PS$^Wj-1+a0t70<|q5`+X~@9}9-W!;N5* zqX{SZ*9z9EmZHr83lwpUC#wSL?b{9>#G@lwz`JpswGvXJ#-GFRZg2t~7+wtDG;X3} zNg_-V;eJ1@Zs0K`9a7hk#1@9+@)v2wL#N1ASQhbt-SkeKKtV1;Cf>&8Bh_#^BNOZI zag5)6j<{~EJ81~WhC_D+QQFM_eRn*-rM-q^VzVY$nH|kcjXcX7Eu0U(ea%3`U^}zn z$WHd?wtbk%I>7Xj2KJ7qA~mx7$Er!*$1^9!Sxe>pVC}dYU#bbSp0A{cX=pjdy%Ztl z!{yBNA{Cygu@_VpiID`)V(@ix!aJArS+D#iJUuE%%nj<`&%y7=>sKIJD}<=szo#&J zQVl9kHYdjx$1+9rrxe!(SnVw@#?pEC?squ#T@ z1GiACRS_S=UB?qG0(5@OfSq~&JW{SQhxuCg3dA0IWBFD&eCU?PRQQ|Q*E%ZFrELYU z?_)K-*gl8)qfsV_R^#6tP+ukc&!7rdwyV7_j3!A~E}$^N~)Y#je7)9bK_P90(00++CuB!nfs?zls&jrXLbi}47GfR?`bZ0~nbQrDnK+^0Xn z8lz-%cn7T+O!cChTcVo21OEg zlY6&cFk&pLdKsy>PJCqQ0^nPXO*u}ewcP|VCF{7Iwh|4l-3pqkB*_**UGhx9g6cOZ zkpp|ovE}y^veM--rkqY?gIA6)2^TJ)vUoKJ`w_Np-*wQq@Bu5D`td5ai~g6#c@7qe zk{d$1;NLYNI^_Ndd({6h2bSIdro8}OKIi6~*`KiJssIsLq6eP8!tMLVwxJbIfC!6v z68$?*QBijXw?7r<3-8b-|Bheh1s}Ns+OOXGG1kGM|Gp&8`6KnN4Qh=*QBWyvXVSvq@@HYuN0fV*qn;rC_! zkY;=pV=@J?)kB`z>^7q&7H^ogw#V=*$`6`OEk-RF7H@$ho$vF6^Xv(ew?0-l-!=p6 zMz%rJ)OT1q7Q(@lVleZY6SayKB;g@9aYvE}UAlM-*UrmgGUlCzaW82aG9m}X;fB2V z!>c@q?SZ(<$0y>dRMbjdclbl4!=&2ZfS`tDufKRGnW_j)+W&Rh2pZa=&Tc{@eufV(+W;8=?fvRdJ( zvp)6Va$;SIukk^10h~$qWi|a)qv=jV*8gZ2GoG~;qx4dFtfUh8QaFK#WgC)e;aJq% zHyyUDT!#;)oX3T&`@!wOY)t>!gCo+DNu^T(qv@nXTDL!f`TZ~9)zas%+M&BK*6ZWEYXgR#RbqN>!ieTg-9>Brv zn($PtfHA%w!mM2?K@NQ1jPozoF)f*qs97sYE^+VbGfUigT16@>Z_`ci;C48t-dEy{ z9&uv1+ZnDVPUlzxtD(um9nXd9lKulRsB@$MghDHETC*EF$tjcRo46YUxjX;z>OUx_ zqyj-2%9z}~4f!T*c5K-`YaD-C7P0NAAPjMxp5V-4gGAN`u`xx4CXP z#h!{qxXWM^4z<2vik=)|#h<-nY?6O~nidGLMWNg8Nz{QgpMP z&Ht4F&yVSX)Aw|kkQjy+Ukii8@+*v`(-wFcv7Yzg&S41u{sh|(&BdA{3Y07!hbm=x z>Rltm%zGz86W{aUF~_Lf8J!P5^eDb|9EIn8KX9 zbA;^|-bm_hzCh|&3sODlSl#dhE_6R)bhz*4pbW-Q*QE`zK9*K*Jc&t znxTcuVrK?kVOQL3he<;MU}pFT0=7P5vW(>@wc7VX8|DbVMbw zCmu-C4Tf>(xTgb4jU2G*QxnX8*2NB`RpW!5R(OHF!0Ck-A!gY=#{Gat;r7>RWMokn zJ6fU%x(BbZXFpGd=6|N-Sf>V6Ul@#c$}e+Fr`MPms7<~VJcc#1Z@>|cF4QlV0}0az z{QM&Xgg-sTe9;a(zEYl>Vf@fg;VkA!w!quOTPTn2ywtE(=@e4z%@1N0a%`pfcOM|YWCvdf2jDFjDp7XkLZbBb% zdgWmqR{XsUDd*2(|JyEH9CijnA}e{_mtVsrdo_+ztxLZ6g>c#NMH~}LkT{5bW?Jv8 zh1V^G$W7GX^e2{G&HWp9HVH7F7c0{B74Oi6wSm6f%0!HwflSWXXi_Cc6Yj>r4j*B( z3G0NaJ%y~mvB$H#RHD(5+ZS!)vU-nFA3*AZF)YRN7@s|iV-B9!ZMPe3?CpqelnSxA zs!b2e*TdpRYsg)`B!Br_B|4C>2`9RD;jcgES+b;t`6Sr`fxd#w5`n|ewke7Zt$o5O zZPX*_ZWoy5Vs&^qVG6VBBFANxu);n3FtEGuk^Qf|6lc2IW00v4skkFem5t7@Z6=i z9J_9y_NgS+M8%x_;XjBo^4|&u>`5e8*H8-Paaq0VL}m$|5c8$3$|~=`L|5y zpa_>=<+yBLY*X-cq!{tBD1It2UOmF9Wjc4*<E`A{~%Z-Dvih4Hpm-3 zhNVdx=!ld!Y{;vHO;u^I?WsB2+u#h9&x5f_E(b2_%aQ{TH`oCuV^aP284MkZWK;#G z&>y>|Ge55<2<}Z<6wcVB&|7RiAsik zgrBGjda^t4!b7e@P^Jnv)2?9Jhl99Y=nSq&SLKu32$Yr-hBnTXC2B21E_l5K`I8|i zV7r^=TK<#q(UE}V8K;q|x8U%3MVithKvl%J%%1looH}h7`((Nxq;?HvFL?*EZwrvo zI*w@)nTd+CvXT8PO2d=IXmsc=kPwVztekT|v#}PBD+goB90}TZ(+P_AG~z?Y2$W){ z;>KCMctRx$#P;*?{o`Knmnp<(fuk7h`5g{qDWU(5m)zZ;iLvKquwvO!o|DfB@OOK| z-@@xe8+CQ?bLPA+5mzB>|01x;UWgmR-ZPuG_rcFuQRu(3f~ho3WX5+2k-OO%OyeLYYG$K(U#FOK@r9!TH7c3iBpf;PFVRn=zsg$(E%@g0kjz$SO zO+tcv%Zmn|UCY=$|46LnI{q6HB&mS&6lNoT4pVqviYgq+XR>-7!B#U7qGZ#dZ0Ry! zow<(SQ!{?j%Q{T>lET(qUB(96#-rsSVWK2(2Uf(W5d-oY3j7<{%KahuVX7J=cSrHu zQm5dXYurA#PMWsP-v)y}e{oq5RU$BNYN3OR9-I~xBXt84K#05T2)vvQ0{==NOxGGV zzHQ~xzOQH(sa9NX7C8Dx^6BG|P@%JW()1Q%ASZ5^4Y|qHTJ73e#b=IC|^)VE1JZfg|Vc1=k2C?Jo;NjA{=v>go?JGl}P$m!T&Evpz(1iKm{~b3* zdx1nx7prc0mN{3+2Wej=YLcZ+1rp=!Zf+>!$7Pp+*;O9TU^s}~u<|a}nm#8}DzoX5 z+2O41+#)~+r=`S-|*}t9+0kUL~Ce}ULUjOdqM_={;h$FK^))hz5%~zSu04tH*(D6`RTYs zg5#bjtmYkEUW+=8#~@mD9-NpjLB@|NlErEqpYU%ab=W+Qp3#WI=bfdD{Mrz*+2K35 z?=NSRr!OG4mL#!J4<%^-s|a-aX~#-CZ-VC3KhRtD96zb65Qm66xF`7*3NKRPICn+N zYVFNvkn@2_(J-YZZ9nm+k041G-vY%tg}8a06znWIi$9k+Q?;}|SoBSom_De2PC4tmrq1GDvSm}N%S z`HN<7JN)ftT;58EW_vG%!WLE1v|=5kN&UxE=ak}D=FUSJueJ9uOEIuPKUqDh4UWP#5z-aV@;?5DC&xHb1EEJ+MO(zJwb zK6)Q=Jp*8i$0=sU^HM18euj6Sa6X^VK|Fky&zARH!?(o|5PV4ogYMemyk-8J_d*58 z7S0bzIUbFx67;K7qu>@boOz@iMP-U`w<71th`LQ}1FgvUa6R;6repD>8+;S?Dt`F# z1eb9);K@_h(2xJVGRqVaU{Y;626x_rX?LZ_*-iJ+%JU|U{`!NZ;}n9~0{FYC4|6Wu zhW!!g$cq^Slg%=0wMqn-pX5WV<~7W}$9Z?YonnFysxbnUd4z*WK{2?QBc@ql+O8q z%leJzaQ#!zud!mv&a9-n*DH{B4a#H}6(ehh3*n}%3oBkBNPBml!?BD%_@(_i?Ul@f zUE1y3o%bXTs#$`sm^CZN@s8F;|Hb^kjl}ofM!dQ(5$!(tVyp2SRPLBSen?rv*PaE8 zw~H;Vt?YpZmA!0&i?`?LO7@-V17;of`Bgp^3>zEm;T_jsoNqLf zWlROgN*{k{GTsQ=f4xSZTxVEp%?BKN4qdL6w1lKVqInI@esGa3b=4!MzsCXH<_WVZ z6k&Z_F+b1t1jk*nC93b1(cR$U%BD)pSku%n{l?2r0cbx!B1~dW{}s%R?>-dd|NR_ zr*K~4h6+|g!Uvz{TSJ9#Dx5SHptb9oz~kyw-0Y@AEz1d5Zrcic%?$oJ8w+&XeT8jX z$6}e_MX0NP$rcZ(!>loT5U9P1uQ!j`7j?g7A|@u#zJ0dnHTE7q3@Sr(Xdl;CQs?f9 zoQve5At}!B$3)K4Fq9xdehB*9)lHC#^j@h1`PjGMc;z$*nIm8KKmO9KfJkJ zsJA*>Rv^NTFS&%}`8U9O;cwR|3II^?L5D&Z*h9lQ@obl3{SZ`^cLAKjC*DvWGuaa>N9qt zpqT07hu@88bkj$?G3y4T9u%Q#rORQ;gf^74o&dgCGDKMLFO)ldU|muh_{Z;WX8DDM zXgl*K%3U{y80BELFT{dtqwbR?LW&Bqr|pP;@RFv!?07 zKgk&WquKyC>?uat*44uEtSEe=dV|vjTgbjIR@l_ZvwAd^%9=@`4Ie`^FZBlBX`06&Gh_j#J7GeVCpy< z+a$XlvwuP$u}78|O}s)Igk;FpT16t0&CSfUkNLpPgG&2%=$!Krp0x_mda?OvMz%bj zKBEJdN+sd9eK#>eC=4BM*A|C1cF+cl$AfWQU=~a=jm3FYR(Qli7VT0B zacS{k_Sb!L(y6cthc$xnz+)*ika@>%&NHEh=p#7kBFcW_c0f8VnV2T=0)v##;}MNr z>@5FjWM1oUZ2oFVnors@wOl5__KP>yLmg(#A34E>gN+yzC9Rk%YzF z#0)l`xht+hZKCGFIoWTVqe+WyL>ZE1HO3PalOpHu9S4oi6cz}+k{fIR z)`XtJP3q@yM&B`%SiT-QN>9+Y+#7zCbRX})o42q`Zw!@c4RNQUF7exAMTY6=!XSYc zAQu}7=l5HXapy#w^!hrT@ZkmP7n;H9%T8cg9vIX5)F0>&?}Z5iPto?*1~U2bJ^UJB zKpa-7qrPe(GfV9jXdO7q`HBzXaN2aIlJd!4 zm_OYG5ccC6EWPs$MSg35xqBSZw;qB_x0`V6>UWG^t_6YL|Kb~S84M^m%@g(d3Pa_N zq^(tghQ@AYw;a31bcTuXS6>A+~JwL|RX)l8qL8m8DT zL|KCyOoX91eWYHE)h{<;n@k>`hwI7iE30vyZ9k*ByMUP(F@i$gK#zRB3wNJtkY}%S z$#)$I$}f#(r--MpJ5#;M(Z^EU_rf=9pYjC-7B6C(Tq40gW-+SyZX}hH(=k9!obZOZ zd(Edm?4g=c93}Rw59eeV{;NwBTu#BfeSFa4$&!a|o@hUBHWW$RWaN0AKu+3|Sw?qQ za7iTvHKj~#ejMxYSO8Ysxd!4o|Cks0Hth5FQDnDR1$1T|A?JNp!au$$eHv^5Wh%E| z&aEeSwqTHLPUCX!n-0K&_5HB=ksV%fONN=cGPuL#6LZS{JqE0Ag~6Tk=;7rPNZGoT zWFWSV$$wRbY{*flp74aV)7OIS?Rv1Kz5-L_N-+NMMBKJ-IyT+r{O>cx>CKEf-ZeoP z;y5*&jL1J>^2}bq-}j=}UZF@dKkL!q{0C^}sZK4ohCth!BWy(N8`!IFPmfsH&^(oV zShjf%im!Z+awZ#{BW+Fibu6&0)33aa9vIw?KK1{ZA9@_7f^}MQuyXd}LC!D$` zh!wP%jZGJFFnEJ6?^m}V;fJ@wkwyP_6DRcGW*S;x@0~$w#}C4R)3dNMUm3i4613TB z4!BiF(K7Y3OmKEM92C-~0^7Tx>sc`5$ZOKCoD1pXECG1MuqMHr)57r=_`B3V!WLJ;ms*GR96Q3%>pA1M{TsS_1;C3yHM;lJIF`!xg7+#N zdMJ1ZYsBrqcdIMJcQ!DFAr-iD;USn(z7CEQR)Z|Zx3nGefc0r!%-7c^$ejzXaGhx| z231^U7Qd@OnRpR4$0`Xot8u=K5jpZVR){<~FNgD^Z^7*ycI3>H58#D@M4039iEL9R zqZbumov8)PstUl~>Sm^PWCh-AOu)!8cUt`JC59|gfN{^mkEi}tp$*L^*#%j~bnco! zrhML4R3CrB%9J*v=}}8endwB5&vTlG%O$v2K^RxQFJ#WG3d5?~d}d8_Ha^g}#<;XH zurXJR$SyyFJiT~UVK3)0YmEiHfp(B|cnk~k1371g6qL8uLC>9ac;I*db8A4FuC<&1 zv#rFaueB+&{OD}9^3OX^eA^C77v5z%q|2FrH6@T5{EGKu@g-33Yh(JV8sR^!D$r@k zWu8up2ld7j7(`DnPc*?dZq{J3LIHY~N@JSK3;f%$7Q(rkDZfz)BAtK1jIj~CIei+g z^^qWDpBbDx7K@Wix%^4PY+5O%cd)(x3}8 z6ktc}F#MijL>vzMpY2nE9v5n1?2{Zh7aoZVH@af$&mq(q?t?2+Ip5*d-NfYWd%S&P zHPd-V1%%H8vpb*LK+vcTx7QFM&!{5&m5&A?iynq=7=T+Iwc*-Fb+E}XnfIYgoYF9T zBHc3vYR5t#yhoMf+gF2#pf&Zsa1c_|B#BGGIr!SDM(!n1w$SYZik29`$tj97&)^eQ zIjF!X-2m9(Jriw4!{BoL2)GPh<5ljmVC^?@On*6DMow4(uHBKNO8Z$jYO6^$t@B~} z6W4>u?(g8OxsvHTCP|l!KZVgJU-05;88S9QkWu0QS7%>{TlWy*xje`ntz*b2A9kBQVxPX2r=uhHKyOqMS1oqOh$d|^!|XclQMCYZ74Al$ zpABOMkMW}Z+QRsIfw=l|8rU^|hx=TYSYWmy*(vlG-Ip&y2QPp2%bz<)JPJTVj>U%h zE4a(&76#jmaNH0vGG(Y4SFj%-AiscrCSfi3%v{L&+m3_o7FG6mgbWe;rAEmoGwwS> zisxA*NV+Cj;^|wD*v7#HqH3nz!Fzq`HhW~DB25sw35}n_`7<|iTNI8vX1yUA^>w9GZ6+qQm0=O%S3m&);yogCB3CR(1j^6m%um26kDJFV=eZ!qh-KDrh2Og$N$*II%HLFT#MPb z#KE6udNCMZG?)~=e>;t7?A{C;Jr~fwpX$M>!5dpAPM{KZKeL%v7P4KtzF^1WOy1Y% z&FK8j6>PbzqreSSw$17Yylc}WAuFpveue`~vkAcehUP<5_yzmGi4{DDuuRCkdfo>IK{C9Ej#4FZjl;P4c=o~L7q$+T+BawOC6(w*5jQy z9Q!*}8;sM;h+5V(ux;566@Gc_+{zk8ien=!o|3?(>-=GNR&l)jDf0B~d^yrtrO9?1 zD^OoALE^hk2=yoGvwZ2x{8#%^(Zu~P7N-aEf8?LXlIavYtCnI}WhJg&DZ)x=#9|RG zf}5i+@qFG~%3JNt-}0agBC0Gfb-6k2*}V?7+WNrQwgBd%em^KlYz3)?Ky)mRg@AdA zcPm4dQXR&3%>eVmwVY3(4Ni?Eqp4mO=$ZxzA3KcS1hWpPdk$TSkBX;=~ z%)AhbJDVM-{Q#F!x{?iA1*M>UQG|#a4B`js2J14li9z5C{KL58%HW+*H4wS^E`q_nkZXm62HJ?FYp zS`-SUB#JT`L=q)_=l2Kn>eZ{~Ip@Bw>+^ZPHFa?1aeMl{Ya%tjSq~zbG3<_7?wL?1 zOa7X1J-o)5h_R&Nf0n>}2veYOlqc%F$@k4lq8vF9P;aVfU{v}c&H z*KEP2{cLZ*91>wEgh!psP|l##LbW&;mF(gfa>NJ7eQD?{4Z%Uq3rF@0<2IjWTsyaj zHB_WHW#a`<@PC0Y=Q*t5wXu!M&%*+KC5;F$2GRevaodF(u>SzXCSWAo-nV=*Td6)SD{U27MHO# zr}1%|>(_S?ZhM`{@d<&f`QQPkSW$8!+LyekbR}t56ljA*BdL%%g3l*^VA`3fM2LHS zMWrOOZ`1|(nsGC@e79NY&B^j4HCC2<^xTVz-{r`5+X#67`yMoNow$)`4J^GYgrmZ5 znUA_6^kAAcREzh+pp7omnE#fM{;!8a@!i6u4MJGEH4El)xjF4sGjYQ!YkGK{5zp~Z zHy$;+23z_T!&Bu@NVDi+Di#Ye8%_w4mV!z6-sKVJZEFE}>q?Z~{sj2on7;>eTx*whjjy*W$3Dc(~!=HzolYUAHER*O# z&8$SYkQD}gJVkOWA&qTbEl3XU6rux(h8P=Vf@AVkyo7=#IQq<(Dj(0mJ^yl0`*9j* zJ~aWsA1nBg{hR~Kp&rcY45;<(SzvJVAjT;_;Ypw7;~I}sYy{Ug{jWm`ZAYHM%=Zmw ztFM5cIQEl?^D@5d5i|bMzwclsPlkRkjASOv4}h;T17Knv=VBlGi>ICr!Lt`H@Ka|B zI+$ePy$W4ayY2&B9207}s~Fik`xvXd{{>sjaWfw`OoD_JXJG$oaS*B$qWdl5yBK1S*iKwwKAANfxQa;^0Cb{V*>l@w+E)QH$JXL}4nKAW zBLZsc_35-sUoa8}=vKc9JyMJC&vs>4hnrAC!yD(#ZDmf3{D8}=zeC%62L4bl`1eAY z{T-5t8->+~oG5|Am!9L?D$0iG-@@}L^ROa7kbYFFfsmZru*-55d-+f)E;u7W?#j6_ z;o(UnC641Kjtzshs5H2pISH91CoL0|6qy6vW%x#$o85c3K=2Mz+Hc>+Z*D&hk_OeB zXU7|&4zGtveOyoEf)E~$P+-b*7$_+BfuI-7C~KieO~y~7V%BcH@&YNEE!l{>9xukc zuUwBhVg@$p`+zW-klCDz$aGphE*x=zjW>V6zQz6^8{Njv8TtkngM;8#RxlWpU9sfF z-p1ndJJE9yvfAqc;N1Ja_+M-l%yhnjuef~vhckP?`POw_nEQ1$P%DESI&+M_x5ygq z?s6gu|J1Na=L*=@n9=#y4?sxyIoNuAFL5&Q!=|^d*z#8sXxgX%k^gO|#`m2Q z;Q0&{zMhdA|C1fZ@9ti~x65+k$9&+NUg!ETO?@R@^?=)JUTtL!=0Adedo1v$u@f|< zYLhjm^`Hs3xlZj2Vh>VG*ibD>ZTW{Xp%t+8NHz9&yaDx{9=PaF8@j#n#42lP(%{;O zN{@c9p}up8QdU0X#i)?5OQ(=0nS)a~mf*IjzOZdk8_(|8DG2=9%eml0$hhM(&KoI= zO82zL3};7T2U^s2hA%`M-b(jn2jgq?gLrIhENqms$FUx+JJ$BpQZ=`oz4(@bbp9X{ zlMu!PI!V!itr2Ljn6jS(CR5&GigcMQ-JJK0G5OWai;6wZDtI`f+`MOyur&aGh#*9- zupkHjbn|D-)j%g(Ga4g1fI^bzahvln_*FIHBt;#PADIfK=T5U5CUVTaoML$4pUJwW z7~+Y_hp6q72t#r;?CxL&4O5Rm#ClbFZoVyh_*B2;jh*$Zp}i4t`<;t!wmYE9J(L$J zG#$>D$dP$-D&WeN8d$ex13fyv2P&%%5uwx1;q}YCm?PMPp+>TpP@+gax;+Loo&)@n ztIXU08?tiU7xv$0bM&0}o6UPKN)>HWSSRwAKYm$>6*{O!YmJKVj`#pn&G3Vc&}djt z?1gW0o`A&6NalWw5UK_?p^eUMus=yj-Y!ENHq3@yF2jufT@?~OZ)4fT z#Elpv(SZ$tGN>axlRcDI3{Ih6@L8B4SS;dlNw3y0?hR8x?+#%lBGhQE>S1bJ*NJi!Jf7CK4eJaPh};P&?j?`n4JS+pm?FU3aV5q-l4-YQGE5 zVxb*V{`G? zW?NDw%fm%?q{tD$OYDBD>#W7`S+w8;pTDOd6OB$bpq<)LxG?WNT;P0?!oIE06E=hz zcdlaEV@dYird8lKwuBXay8s)wyo}I62}^3&%yoK2D1CAW547z8`939F%L4hVUda#s zbd~8$(q_Ed#pvBadr0^j0C(I8=tvC%*=sl2OvoHVZ>yhB zAE*NJdyx0iMF*;TEc3-SjY@dl>bih4Hk!i>2cfSal;TpHPi1;Sn&@e2-K<^oISnlCUb9 z+nJo+h`O7OLD%KGP_m~LJTH0Uxl=r%IjsRiiUjEDzw>Z{dN~Z76M=>QB3Q=Rn`DCu z%@M5znNVr=Z|zjrbI>1U?Y6^4UO35$T2C+S>Vww{y6Mt(Z_@9(8~Jw2*{WY5u=~*z zs)N$>#1Avl;JpvmZnA=$q3Z|^hahvi4|8ND1<6GZQPp!b9oAe{em|y#6|M<~x7CjD zP*jhUc}^t;i(k{EoIv~;Zbc8#Ae6LxK$YHaf^&|-wDR9y&^9SY8sEk4xymCc@>VFg zMUfUgdqX}R7bIPKk7DtE2K1-EPMED&0FP7NqPI^097#9;Gc*uoxOu`OO<%grwv;65 zkCO)BO4jCn2+j)Arh7N`p&+H8Z=!?o7H-V?unPQA z8!sbZLZmrol>_VgGvOuHbzb%{~S(U$f!awro(=^kCn7%w{|F*1$`n4|wYB zOLRs-e1Fr3+PY_g+qILR7;Z;fvg@IFtPJvW;_&+XR)}~}4~ZK%_J^-L{g`0Kb@LNo z!81gmif>TyUpbC-9l^*;%Jk(dN$x#hK%IHZ(UIeRZo2lFJ+jK4q^CY9wbu+pb5UQA zc(R@+)>;Wd`%IxzAciTE5g<}r|K#cGi|kZoDRMzofOK&^n9c6l&|WS^B{s;yuFd*5 zL3K0U6&uAJmY&R^87gGU)jQbNyo4G5EkZ9y@_|{V3KLbTA?vRSf6g&`)>(E3zOa17 zeC7B9t{qq>?NxjG0l&;1K-PJ>H^LzN8ct1Kz??6Yjtq{{JPIIkd;duN6 z;%_`3A{T~$nf7b=&n*a3?3?k?fF9XtG8-2^mBT~3?qHW;DF($Jfm4#tc*DJaN@Ft;O~L#G?Zv(*B2U)2Q|EfvFa;T~{0 zPKMH|HB2pg2Taq4@YMt}bWUE+u8P%#3N3x;4#@+pYuj+8qZ*!=G#7Nk6Ii8#z3^-A zVvIl92`2H|v3Z{$eeNNOLTgsz3ZdW3K&t^?__74)iZKSKJ7V;hyBZa95+PTzn&6Pd zN)WBo=lW8LRQcBgs+U>~1sRIuz1vH$I`V_>k($Yjo%+IEvSU%j_CBNiMx6xur?M$K zxmi^ETjiMO7q{TA z%b~D4U7Z?p?-PMsUzEvM2pP9z$lX*S`ZiRY=)LWQg|3q1zM~dz^43Wh(<4HUDhIJ! zZ^q!prK6~$c%5fDF^)C;+t2>^B2PUxen$b0Gx@3i1s<+=3<4Q;WglkL;j5e%&`KX* zgI5HEIfr8!?Z*NSSvoj9k(HU-%I-HAVWTw%F?NkI@m#T(b(c5*x4u+xK0R0P+9OF6 zLX7b4d<~rV&==W&b0FWtWk&?dV968>YVI6>f_u}@P1_aDo~Xnv!Lro-wKQ02x#1s< zo1Jj946RjVNl9@tyTZN%|Je!?J>fs#skVlH_yvQR>d#T^;uk!a!2>%v5&B{8J5c{n zh;t1k$=|&JkUW-#5z12ZS78KG$7M+-ANGbDqN5n{NSLambN8uD8Z_NE3amD#@ig^H z&?xCEQbmmL;8Pv8 zrgX8z1+THl-~!_}o`h#Rl_1bTj2yG7VlHZHkvp`4%bot=eO`4Rnv0G=8s`mjejCQF zf7y?EasF7UVM0S|Lm+QuH_S3#f)zK!$cju|I^8mq&E5TjO%W`>v#)iqY|d41P-#L< zb2T#Ht4e#4C5V5^RknEA0(7lugS*QN`CXHL!y)~8;2rS9hd<|%Kb32E2}a$F(amhj zJpM}Po&*@P`7+FZ-UvM!J#4`faT>mebAvyA4BnRpvBPXJJ+z#nra=&!@$);ZtdEEG z7fow~u~>B@U^~x%hAFysQ^| zzb9|7a`9i#nI6N+9!W$`pF&8i{s}VaQVr&pBi!@>OtLvxK=_s{#%Q*D$a{0Vb)~0+YLou&%48wuaE&>e!SpT~VIwc}uo5v>Vei00cGb(X>?_?sxc}V( z&vhw7VA>?euC9TfXZ@Msv|Pre>^jq+DT(XaH&B;-Ex=u$AZnEY@s}5)>+3~9;A8-M zykQYpIaPuL3Q6&!SB?RBxtJ_j#`zeNCt#VeFw7G=#U9xyK}`xCL)vTu^0X!!ie#mz zXiP1$czy%>_FNdxf94cC@1jEG^QOSgoEVItL0pV3|2jzJ{_sHk!a-{s2C zX$ytuy97z_Ix0fCyB68v_zVtOK7%ht`RE;DNNpc0P{*%xaNn>g zG2U?#Mwi}1D_2*Hk(0z$(#quHUqbeRx^*f)Il=UhrPSx3KXuKj300M zlYlryD#Puhor+B9v#(0Ts|cySKTqXQXiW=$RA$GZYkdZtjTYiihM(}s6`XrfHtByz>#1?-#rnIFCkNpGkl z$*=`HFbDa3J!`6xGKR0?Hql6)DWhq6k7{y#GMC5h?BqBPd}!c;X?35W#kC*9ynXS@ zyIj&9xr0pS93N>q_o49AZCnMnq5VofPUE#-wzAryR#7P&NH03BJY?L5P_nT2+-DeON zEnye&fz+j3hwv&JHh7LOmCAL6(5F72tG1uXo>>T|1#{WUPA>RXY#x8#)XR*;LXP>X zeT;41JjmOa&M{|RaT(<-uFK}BM9p)!4#4y*_^tVqb*UVOb!Y#u8Pn&HFY^zON6i#0 zC0C(@dk+ZIB! z@zgzBv`!YjtTZJrzv>clA_?yf-h@`wkIXv%-}twxi8^Z!^WFtTLi00mS{A85D*c1u z&5rXlZ&Z%C@`B56ZRaz*&SG}D&knLVk8?kKjN~zrVPVQ4C!RjVtN#Er=J{&-;sn&3N=^wajJ%@TH+~B2|H?Y$uufu`j zsgTWdG3u^E%>6rKaC>zRD(sug+CKZmJdF^iCpo6i&oNJCGp_*J){H>rC3PYnt`2cT z9ds{EqdJ$m;au!`I+`biAxB5}+3_RrufYnEmmTKU7>QWCovlW{8ZekBu0>p5UWUDe zlSo8GGp26UB%)HesBm2XD`#+>GM($Jl2RlrN&N*2^yfpE?GkkTy$-`2g2^6*lkiKr zoIM<{0N*qn#d7snwrH%G(ckg~Dz$F0KmXig#Y(-=-_L|*It}84u*+!f`v+Hi^TP`F zNZg*@31=4GVcb2pGIsOZpsk#R8FD`?o-QEy>D+F&!nd7qb z`zWzn3tYb`py-TiR8LKnJbJN!S%1cy+won--s^wip!pUMc_Kpw>`T~^;bJ(obpu_m z>rDcVE1Kuov8Q$cTnl8VM2@-;UQ{B&3tx}SJ&U8TQ}#Q2^S%XR-fSB>?}5K`9z{& zv>#gKE;HEbO7|U7C-j^tSBo+3$KKs?UR zpvxX#0eN*_cGU?Pl71nDRcd^Vn|WirsP~jkEiNPPrCz}K?rwG=$FTKspH8-3mj<)0 zotUY$2Xm9kpw8k3FF;S4+>=$HEB+ZlZB!+s_xr*8ExNR!aRgVXYZCwCncy4fLi%f0 z(GROfVOx+EMC>^XHuY1;j~VkoO%{Zd&SbfQ9|U;qY&EG62N}{^Z-&Gv%K1sQe@2q8@#%89mBUS zzzHV{L8vc|kv?xj%flLjF8nlK{Q5$;2oD8WA=BjuDPN#VAzo{ zSn1CnRcm0%E^lN$Ot}s5R+rc_MC0Jz^;-F=D9=;JIb6^tb19VjlP$wFJ(h zqSj9G;PD&w>5Ur9{Gvf_8hC=We>xVIezG(P=a^D@Uvc%%h3v&2XXut5AFzLE&Dx@#k;P(}sOq)=v`~ttP^egwC9AQ3&-Nd;mHn2u@ zIoexPq5ZXqRDLKBU!D>qAAYv8NfR4k$g>l-f6ZiiX+LE`)4(-lHmR%*1OC20jGHo_ ziRYQrUT=n>d0OOT_(50_k;ACHsKcql!BE8;05Q`JwB42n9iIW?G)_ZO zi6eZYzd^-pCNy(1x1^)#fWzC!9lMj*Kg9|U2z5c`95wuWaR)bpy9A%B&ygb~3GBYW zelXYJ?)TnmY>xPDe0#kR@+al<4A*`D;l-yRzrujbUnq*t4@5HCi@7{Ummytt=oX4+ z9fGsuJKJdbl9?s0#a}Bog&J&|K*CcJvHCZUat{<;a|**j%ww62jEoH2u$^x!^- z?pG)KbR3~yNeln|lOa2orC>JC2y1M;*=>#7s%5a@2@iEcnLQ^7A0l zjQc&g@vtYYk#Cdzjg`4t1iZs(c;ep@8rtrUVO76SxlNkc99O_G6g9bCPzaaNd&n0t zc?N;Av}oQ+DSF_v9;(}?Frxo1u}7m`u|jzz?0465c=xpp?$qA}dnFwqerhY5b08U{ zV=t3gj*V={aWihmSqK{=4&v!TZa#h6fCLYy(JL4x9LM-fBKlVena4i zUASdXk~oyE!S}+R7|s1`gn$R!{3=LNd$~P?-#(ag^9K|MedC;0G0-_4&CXxC2_3Fq z;d(Gi)Of!Z&Mg%n)#a%uuy70GI~s_KgFZoG=TCeg#(A-I9^j+B0`x|53~sPhr*pZk zalc?bbcU@5N!?WZzKmjKe=M`V{u4`Y7U3@kUuOQL?O6QY1j9_E=`OApbUK`KJ4s1$ zyTc}yW0J$}G&yQj^qws!ii9Z-X5j5^IqH6JBf1EsqUX_2%gr3CLr1BUdGetQm8Lp! z-v?1xu6qR44LAo2Q;FsSPuP3LB19uH9I3zv?%0rnvzrs}9XGc)CH57R<`|)i@hI$= z{Q@FRpMg$?SPx~agaa1zX4iRBw&O9TF<^==lIf#_l7AkM_Y$zA{~PGan6elF{s#KX$%d%&Z($ zrw6B;0v&N1CTf)!-Z_55@_Aeo$R9011tDQNKIq5RC{JatwEB^=<`PuSv!3oifApLG z5a+$Hq3=&}UCE`BNpG_f%nQwCQx{}n;jUYtN|i{m)Hci$Xo3BAjF~;Yk~H*EKG=^$ za~z+!Q0-NZ$5g6$TjI5uI|K2&GhWeH++j>B*Pdow>=)o#H#_#vk&T=)T#h^oZ-P^* zxhQpbCG0!-jcKV&hcTJiU~x=}+?q!3CB7Ofv2*v#Q3r&n&Y6vhOfdtUt~5jjdtFx>k5uBS-x{ z3gN5_RpM{Yaq3qO@qEM%Q>VVitb?65xX-6tn^TFpq@@!%gJ9xd7(X2De6PG%n}8R5PGP1?I%gQ)LHX4PLvvbT%3 zBX8W4>$A_myM8y&K_m$Lgdeegtxed(I!W?p0e7COol75Ym8Bg6-@(8y8Zuj|;P9zM z^qP|vX|FEC;>c*6w?duI2Cu;8AzM06N090_u3}pcxudg(9n5*i&CyQOGHLED_~*(2 zjM?;<`R5pefjM#b!hRx_^`C?PuC%}}gFc2$oeM5Y6Y#?W0cvrJ^BUORgNq%7_~?`x z-alT&m|aqVE&V^yX0|51koy9vH~+%erf}x-n{&)Nn@P06HXEM?f8zY@+_}=Ll38}R z876B13?(EmKRDNlRFD_GIcbC%=`kQU@eg|byUr{;Bu0f&`|z>xYVshwae4};_r)Q$3zK=ib79t>R9sXeNoH1lL0L&Dx?sKl zIWyq{C`=lFidfo2^jBJ73x`XbA+y53L-Z`iuy5EBOYG9?V6BwvCW8VFN7qwuNzdum}^Es}pVACh&~Ngt{k>(d+pI zhQ4{rxyf>{x^oVAZ(hvo&o`rznn~d9(#5Mv9b$Wr7lXvkov8fNkK2u@fcC6u^zB|n zT(jAl+3sM8H>Ek3^oR9m)KJXvHubp7a1S2*Pm$IL>;vc9-*}t1AHY`@hIDC;D2=ha zg@)dSXyVq#<(H*tRQV)q^rRrR<~xQ*e*p7ok*wM7?fkz#-O;dbBGoH3155ETG`=iM z?&=JZ-cSL2DKna; zn}^Sjo@G`vjN`175$s&QA2CaTetK_48lCQAaFYy)a(My=ZmU7R`6zZ&T?Vz}#kged zHQYMA7&f^+g+;0Q^kzaklN|LDLc)_^ut|n{-%bIY?;$kdgcyBLG=+rXYTQ~RL2~bX zz+h`DYJT(zIa#!ptsE$T&fIxuKHdTj$1I3VjRRHdv!`{_B?jPkB(Px z(G*$Q8&`<0r>Ai4k3>BFOPKACG^bzOuYpIg5Y3aF1u;zzu^6K9h_wP)nytxku{g$} zJwtwSnLW2P6)*qpL*CNo>M7F$!rEu-tYt-R7Fl|ab{1x}!;v;`IXtC78 zP31Qs{F^!nu;uQ!t-55%8ZG*F6X!5C9%HRD)X9VY1gL`0bXv;!lZ%GA9Yx{`%rdTo z-gR!QarZyql{B%F!}VFYUL90Qk;E_3Yk3Rz$+IT%6k_|t@Oohjb0dS}k~rI9)#jTR zURJ{h|7eHVWl>OJ_lMJPtIZ8JriJ6UmQklV)2Q< zR9cvP1gDQ012gY4>voIla}8gIXQji~!#RQ^$EGqBV?%H+`82%Ac?K8PJx1ZHRRFb; zWcv(ta&>qKobGK$^)nkGV^2I}It1f}nWDsL*?myuK7(i28<>8-9+}8Kh&FKnbA2IF zLPRO+RS!jDui=ERKNC#2x##>L6rcPJK2!{=~-i7+{N^&#B0_95O; zkKlZtFF7I*3Iz*faLTSy#$*N8X?w5EA0vr$UYZ4|NPCY~Uj}domwk+RC`ii$vP<7@ zl7-eK!sO+&R*=|n8Se@+>_gcw!)+hrmkU$b$wjy; z+?{jizF#K9)Lx1FuD3?KvJ*?oNY6JVh$((2k}W zw)o^5$H_Y>M4Wa<L@nx{!pRl! zC@%4l`uk)M>Dk*bzvT~{5y^n_qdQ=3Mg^8-K7o>Ts?_1N6uJJU95#4|FoNts7zKT@ z?sqLl?6o1c>#kv#ryhxTvYMtm&4q&%l6c{C4@?l;g{cPzAom)f4To;hJ=_^PlJ^1J zoX>(Fmt!kjwt^8{n}p4^%GiHJnAT+W(x0CP*eZE%h+epgIX34b)2E?ByF;(QNk1;L z(w@#A-Q0*bzqW8rSSOq_Yck$Ib?_fw41=2kn39;wbY!zByW`GHeEg<}lrFo&EY&ch ztHchF_HU^yZ_i_R2~ zxcm+DSw4q&RMf!}`E>YQ7{JZloNNiwQY25#=OyG6jESu{Aah<-aMB8UPSOydQhspKtcZ}bk|alLMd*~_u+M;%;D zJpdP`O<|w99>Pse($VwY0{S}Ml5C1qB6{C$;0BvwRxc}twP{#FCUv|-zjPxiuxT;R z@bJ%a5LiEsrL7v8-IMsIRgn0Q^?6UmX zaIe9h>B>{5u}6C_N3ImNJI7Ox>S~C!DP*5vC(W4J2GLIfFnnwlF*NmtZkq;Rca>Yz zUp9sPt@gxGs~awSX+kFyAvfA4f{o+C@#Cb^?D-xx_zh%yFGYce5ep+;ax%!8!WNeBW465Ham66AAptoV)CWCR=-< zdRabB>?;JZiX{G%_Z5&devjRg$hqYX9;Q9pyexg=yg)CA@=~WA!mUFlIM7&trH?h~ zSBYA&NC3)xqI`=i$n>F1aK@3k8U~h5) zME;#k-WVw}V0Q{{R_EaN9cfI%fxl3{>IyqLDG+QXYvK1pm$)A38|*w10-NS+;J-RI zh^ysm;9bQSN`eqo-+Kb1wEAe-O(9~~+XfZf-Fjeu4;Y>g#fuF9Z*Rrm$;T_Ve%2rstwrX_ z9%WO0X0cD|E7;AuwTOVUG#z>{hiv&4kD*OMB!5s8()(^ReDyQnvpI@>P7)?x-~Yg= zeh<*Pb_Wq`3uhZ56kt^H3^H%3$mW$9*wrPAzRBNUk;oPZeLo$4TDRlJE1GoOb}o+r zXYrUJ!i}}oWi4(0Fr-_G6g~Qj@>?epv4OSxKacy^>Q!l&azhNo4u1p7=N7P5wG+Rs zqL#~!ePdhSds^_gd&WraepV~Qi=J50&p+*+1>0miA*HL1>%Y$?DjAYs$uEXuTD$PG zLm1|}RIvu#su1AZfXh3gh*sPU2nkBXrDs>sj|J{b+%84t+;w06x&n1N?WaE3v|%H( z#%<<_9Og5>_bZT{Djn?dqHu7Zy`TNjp-Gaa7J&u77S`XBK`pzd;9?cRye!wJ8M$l8 z=dC+I+mu30RX?7THHHNTI4|e=7e&aV>w0j@ zI)L@{oQV>NML3~Yo=z;__W0}toMdOnHw|utL~jY|wN9PnE(=Bvv6ZNJXcbO#;drO5 z_TZ51jyuY}a{U?sV#xJ6hB;2@?M?c$F1~;%ODaK!SLJws<85q{2m*!sx*Rjc7tVOz zfPZrhNO98wa?H0An^Rk1_4Kp6-OWW%O*-&yc^MXOyvvT~AabWcx^_zv?#_>fl^oA? z$kzrWFK=XNUm1Sp-NxvVO*o<27aslWhVgzc5PfY-lkd&L9p)vBIX{t`J#pvQ=6q-q zl%)4njqpcB30q;IL0U$Y;M?j_2st;3pLXZLv^A$8$W4T3cd^WXUpnx9TVm9+j~KSy zh~D}q!R@MFLCMB=NG6kT=~QJBs-VdZPWOPFxnUT1T$apLOy>9zSJ<>fIU;w#5mpD7 z61hVN)iZTzeS#zzzAHjoWMY8!|4a2a{r5@sF7-A5Mvp!Isk~ z&z%`-tp`!psS!*s*TRf;BYGMGz^=_4{_NijAMbR-qixm@a;pPosml|WR6+95vXFJT zIf)iHoML8$Pp6ByXSAQCEq+^g2Hzd{3IAry=PeIDh9(K0;jzbEES~X+xC_l=%gr-T z()lWw{^ssC8y6#RZqB2DTWDTL9q~Iy*%SM|L*6YL8rgK7czO}Zm^KxY(h4B{l^j(F*Q83?vh>@k8mO_p$~q3Kpr`3_w$@aL?%sR=^ZNr) zAZZ%OdT2>D7R@Vb9n%0Sw_+@LxgOsB8i$lRRhU^H3qlQdQF!JhcA1D-?yv);u@ZS zoqsq#=v^)@c`(9Tue=KH1Q-%bJdd+W57LzxguYu}3KxI$Fw<=-AbT>`&wsd$#ARhu zrxIb3IzblQZ*sH5f59}>VjON~<>JLDQFtQu1uCd0!mE%Ktj4G)@l6*4s}l|AbLBWY zS$_&{4k=32OgYln_I~w)-!mxTG94u~tvuAWjv+G{ISfd!Vcz6fiU$Q~#uZko* z*^1vO!$QEtB+&2CfWvWns~c$z0JecFO`tA*GfTp#n~xh=>>{{x}LyRaig zh_KxJ^mlAEROjl#q=o^W-L^c~w)8Ldo)jQoX9lsCg_pv=g;^-{lf?m(c+OL~m^^lq zg=ubS_`XFODn=b)%r6lHg%sEw&qb-v@-fu$K8io42$5&v@4#YtJa`ps$1cwu7#Sr? z>XdKcgSYc7z1DGaCC@_GGo*l*e(I4{i5$4~1lbo?*WjAzE$qw#JUqUA8AN8Mf&9A< z@OgR|T)n9c$8X&uW;x}^E9T}-!)Ixhofnurnn)do(m`j*8@O{e7+%TlWP|4TVC3-< z>{Cw%v%-AlzwsVI*BmHM9 zW>h_eS4!?ULD`&m%$9{k33*t__02EOuZGa|m*6o!n$3FW#}sYdgG(ChFsMk4n>&03 z_Z$iX>V166XaO2KV+78wZf5qR7}5vhF}P1{GLsl1LXIv>MeqD+=(|V&f|jV#sk+C& zxb6@5t}I7C(RP$yCCU7#`o(M>k)~E^DYz(k978`eLx%TvoGvU(2V>>%^nyA_6k#AJ zLV>!*2f}_O6M9W!9T9CDVmxafGU%m7#@7gA?}0PWvBHe-yv8AQ6F2kGOJp9t+Ri@t z;tAy~TAW8<9%x=wCUuX5iPopdL@96*Rm+;mWCo! zENiN!82x-U>v@Me3vv$KH|G_w%J3?R9?QqZ!3ey^YHbeKH1j& zjma>}1D(?yd>^~L*mph_lbWQM-))>nDJKEOyE*61z&pILIufr3NWq{)8gKlY3lp!R zLJs{-W!GiT!zWXusBDuW+bA3ljlF&Z@3_jIYWUa*n$?{Kef{=(Zy8c~OCWfifgJx|EqB5r9PJHUDqa zC46Ikj~Th$4l>7-VP%O6KHB<;{nL0K_TQhxFXknh{I$-aRhMyN!U^w|XAnF19OYM%#T8-Br-Js+6`wI5*0kRH!K z&kItO55PC2B2*Z^icu+QZjNg>g` z6EycWLFa7~Y`1%WZ!?@RC_WoxwBK7s-!o^wPS}YHXUo#YwF7W({v0&8n8dkKG9W4^ z4BS=;K~qx*=fjXAhr`8*nXeS??TUdj>o=2&Q$1m8EK`;_P>yNexX-u-$K=&FVaw-U zfgJ;uNJX{q#P@NWJT3`!Uz{NM(@A)GY##Tn9LHcK&XF-8^nZ+v8@5d$gffH> zA#($oJo~y6(j@s+lu~I>X+%^cDH5SF7a~L?NlNnU>qaCpgo;cJQqg2ggXG=+wcc;< zm$$WiZ#@rt-}iN$=W$@$EDQ9B560;HLR8n1!B-!<7|8=1hyMH&a%SKbf7gztcK!l%%hK0a7Bb9^gk0f)s^jXv(@3I(0zKmDhyPVt zVDO9peyCglwtMq=(k10^@?;H672*7^>z!~<`WeVFh-FB3JrpE=0`WJGQH%QdxCr!)H5>sq_;GT((?yQ_zkbLa9^>(-DBpRd3{OJh2}eHAs|Sqy`p6IfYGBmVTg zd{~=s0HixlqF=uNO^|$r;g8g4<-)F3)6J0{>((R>A{NnN4Flq0 z%DF}a1DVyHY9wBBH#KPzViFddWgX}MSsoh+@7<@7Cn~?#%979E|G|X1o_LA|N-t1H zSBPG&Rv}j=EamOp{e_V{R}3TJDzq~CJnu5C0n6&eRB(qYX}4*{7ypgmmutf`da4(i zI_|@kC&@TV_alCp`~!SVuVabFe(Zjo3g&6exMjH>3ctTcJM`X>j7d4Avu(mZldi}i7eeQ7jM*y!t1Xw#CGsMD!6TcNnaWPDnG8! zW~E0kKaJw?!Jp7KIE_qq8G^r@k7efbV7%{lm5yAUL6v8|V;@F})9XL}vQvdpIX*xx zb7${ka5(Nn!hTzku?KpjmVJdq@vA{gXFGmdu17a+4dEB`r!uA^D=_bl8n)+3k??&@ z)TitQCWmFRcU}#nkdh*)vy!H-Z~X-S;Rn$5R}L%+9GR^;y=<;oIm4!nGwM|)RO7ua zZVuUqVNX6`?+C$q?*A*jI~rvCzv20c7>sdKAk#$#K`qw@;#{rpk7Xvl`K&@@ntR~& zvp6&w{J`~KWJ&*jc0_`EHqX#-grJ^$R8Cq!ws$?p7yUbMufSSz=;LCD8LEUn$4vBY zspOsip+Fw)cHw$A05?R$srAfMuwOcpwY%nsgVJxY^w>BnyzBuZa8HTakRotaeT{8P z4#TD{XKFN~37ZE+Ncx#>u+Gq@kJEoa?#wr6p_vDJHaFq1drBB#qD0$D>=3qvV60^x zW0`;v_qvTtSj$JoO>!DZZB&EOg2%XhzZTg)=L=@8e~TFOA1EEX42|(0*wWb| zT&`V!=5(pjmgMOWkj-OLSBR0wdl#7l4t%^VWlevAB6WTeL{bmMv6dXqE>r$7+8z<0 zqHT5X`LhuAUfB!7BpGU6NzuzU8sK?B2n46NqitCdbSTuZuKfsc&wjH>9}v=;LRjw> zMS7&J6W&_v2&|5}P{9#@0vkD=JLCxl5Y;Cc7sV zqj<;maV`ke4%i^? z0}ZOC=(9|Qynu~h{UIFh9Mu5jSYhc#7H?N+I`$sE&dhP)&UXsK(4?US6P;LG_%DLFRW%QH?HT6%yz~E!(SwV-A*boO;?EyDRSRKcr42M{6-P|Pwd3) zlGIdElgcShgo-mYbXv|zn)px;f=L+p1$Yde483LS(O7pJAI&U>up#+ z`8hgq*}nVh|6>28GTaxbN*;TN&{xy5aU-Kc&wQIk3M-6pONT6R??XJdtR8Q8Bw>yn zmz6V9q%uz1KsLpm#KjtrnbxA@(}u5jMy~}I9$yTMivW#QjKg)}GBkEu5%VEE2nBv> zf?`-DY`W`DtQd2=rF(L0#ibP9uChM>@pBsrk}ho{)S6?eVh`la=0m|oHeXGFS- zV&<#E#Lmy~Xt^}$t69x)CdA23(NK8fyPuI;GY+fQ{e_<|b*M>s1k=O3K&c(kaCM;; zMe`E#4PLj=#l4h?u9d|)u!4|CS^A$!4SUp+JJZB*nIjsG`pdX3)~jSNxulC{{|;fu z=|lKRtQYT}I!&BYEita57p#&wR@_{D@HLl(KvQq93GXylsGkeA9Diz*bB#HSzs1F- zlQ5y}7RT4(k*De{FmJs)DUuInKR$25Pf1N^U)2LQlZWVtShz>qGnXkiR%hyb*uw}TXJyq#clqt4I*eE_zZTX3>07dQwr;qgoq2C1^*^R`X-TMZKzc+ z=9!ZS@koP;z!LuFH%}S0i%-!|U7nidg^?9;(@EA%OSm7r6MV`YnB#}IXOCM0wO5a1 z!|io(jl((KswtOH=kNl!`ZyA;Be*+_e-uMfWYOZhBk#@DZ>Zz943!A0=O9`NU5eR4GlEt^OjhC`Uj zlj=04*dBAlIAv%f=dbIQpm(>bGdrjF@>|2RnG-FJaGlS2&rT(=F@9rkr}8TP^!Wn~ z&()YNi#C4q?;Lzsv5PK!Jqq_O36YP{bdfT@7F~xYlVd;E}*Ye<4axmB~z66Iq2IGyNfozf8A67PdfC-CKB2Nx1 zh1mu^Z3Mjc4?}dPK2@@K%X@I{5TjRi zk;@f%(g*g+n0N0kZvM{_&Fsq2AaDrmBFdm?{y%VeydA~-^+`@w9>$8^M~|Gx*nyMj za*!v2+%83E!4DkszRTOl*I-0{&O^CGWfI7pC1gi0;$*D>=IMoLjL-)mBDHfqDiv14 zf)Q@U!S%rpzZ}EQ21c|;M-b{vjsw%JiM~g?=sJt{9o3I3Gn6Gm}y2YQC5A9>x@y5sx3LB+^zK zEVc+!Hz7F)YKq5W4JP1jv6;&jo#8hM$H6*NN1SndkUhBO0Yqd*66KszWM*U#42MfG z{oRtJZOwPAFZm7aLlg~~CX&R1&)|xaF>`;V8F%hfrN5+uaqKt8P)v}a@$d6UXG#bN z%)7|;8VXR#Sovaab+Q9uiJ{!dE zT&LIk4lYTq@A_rVc$Z{D;WyxB@=C znrJ*IiNC`pkghegm{;z|8$4LgPOvM7Z@DZzc z{sktNOeJ4d9A-+NDKJ<2C!p-(=P;glh`BUVo)~idl0CsD_+dc-XdJO-&|i!imlrTD zL$R2Z{SZ&Mnc?YK|Cr~MMlf9{gT1jqLo@k00`Gv48hoVQzjEAQ|6tSwRD3((YUe zWwj3>d(UO~^zREOOrL;WOcsCnA3NN)Uxluk4J1r29R0Ke;aUF$-1t2bOdaR2?P{|0 z!-Xek<;S^$4^E}WIlj7Kav08Z&4lFTI=C!_vaOYTe!0RMG zJpg&PbE!L*=_!@j$P@aS3{tB^>8GU4pytVO!%U^|O-%%UgS8mF(K-)ge#q06f@N@C zy_Jc1rj3RlC$bAW7w{D8RGnT`5ii#7i-8?-#aL zC2KHynaCG|xV1={r@~|A>;fJs$`Y*0t{~XTzj6zk{U?w5T6J-*@$$s&F7(K3m z_V#T|N$GWVnOQuxCx7M}y;=v}3coP_oDdyiLdvhLC%hMOo6lz@%A)_qP+z`-U?IS#4C6wUk~*Mb;+{St$R8;K}8f zo8#S>fIr$eYl9S3W$!@Mp$)8z6lKJ&RWsqot3ag073ym)vL&gyP?z@%l>&NMJ)2;d zbj}uLO7DQg6wV0{%wL*1Q>jHF4DP#u(D%qn439Q!^!i#b*x!+qU;x18z@)N|#i)3*!Tly>nCacf~Pz@g2 zTVU|u2o@QrQ<2;NqHyOGS{pvX$KRCUZIco$FmZr^2r-fqat>Eqy#cMMPE1~0GhV_& zq|Z5s5$#!l8(Ng<{!t}T74!+zv?h_&s|={w%VeF2ltzaLsi)Xnql_%HHkCF9HSvlu4_GQ;i*w2@s>x;`!kj=yV`HFNxpbxW- zkz&M`RzU0K=e&me-Rz7-&tR|aPW~iAbuu2N!sH$Og$H+Mf`l#j zo47M~e!L8Zws7|$-$s6AK_1+J3pnZV9DMav9BJ80#^!()J25yK`e#lA&CmOZcQav3 z=EzfxZx^6!>jFcy$t1ZAdK2LplDDCRRJt@a7a$fX}xfEZN}4*RVavDm~!1!3ApQqV)h& zL-#|#LM}IX;Uk{hwj58s+sC@^G{V@JuQ26p{)M&7gE`PyHJrK@VDwFb4F`#CpNy>d!5C@6r#A2flichM> z%sCgCd6QRygq|SH3*zSNExRC1jnBH=zQFwFZ%Ri*MUXuXxMlq_bU6~vn#d@kRo*rX zduN5>UT5%t+c+x_Hwyg^FR?$*%Fs@yk633FkEeffEW90p)OYv;i00hJ2$cjd6@Ce- z=QTjE@f>J$Mgwnh3W}Xnrtik&=#!;w%*(+V=s$g*$rf(Gurbazx#1&wVo;v+j_re} zPu%bHQ$9}4(4)LGVW>0aa@a@m* zHJmq6BOG*h%TdR3hG1}7iC(Im%`^sI;F*lSM71faSYBuk++5y=0gi{UAu))37xM-W z6`q2ZYuoVZwZ|Y};Lj92ddLp%`iL&%Jk#nL1e;d=1+Riy^m`S_d=vSNHc4;s(k^30 zAoDS9(>@1lX&MCl_2+5!{$@=sh9l9|fb?$yF3lxS<%il-h>37BsM5 zCkf-*#xL;lCU=+Rd?(kH{kYvB=Mnl(gbY=Ez~YzzjLH%tu7~fy`QJ8h$wrTrzotju zGpk^Yk}N&s%_8eNnZEx>VX?U&iO36pGZxPv>)d(f>@g#-IJt{4-CxLDe9Lv$Rnr)S z-b{ShvJ@qc%aV9=TM())LZh?FG{Td+gE~ZSH z)mPQ&<-rILIDN0Us;(NopD1UiY2L?Uh4S>?nrU?Q*hD%wO0cDW5&NHGAFl0vf=o~~ zO#8S50<2T{jr%9^s%DhJ!LV_hcua*}*xm?P)|6RL-U}8^>-k2#+`IK=Aoa@?gk-HO zJSy}L9>3mCuWq|cZ)t2LmzOG_M&@_oF`5S*!&}&Yl~JI(P=pSs=}`5$t*nKfBV7@& z1EV|(7@vA$^3On;YIAc3$(AW}>&{bzf7%&UZ*`!`*jr2(`vZrH2h4wLzQYPX)}+Z& z&LHt|9{BBBwrCA68@z*rN=CN-#3UoO91Qq|;pWDV&?` z8f)JvK#olj0JV>0)I%qjm+P{hmVH?YdM#3P4gVo3%-19lGKZ-@myK>Ymq1NqdO_J> zCFwMnOjmCoK(9@^cx7`vVei&YOymnax?HK3y4PB;xBf<8h}t9%F~&m@KP2Xh?_ zk!4)gQ4FqIyv62Ex}fdW4U!W!kbg#(Y47KsY*x`am@%e>?HyTc$a1+Z3a2WYgH!1VerjP4PMl9#tf*$WLH!CPC8Hn?}gh{<$z z%{_hUIsXqViCPXjoUO1%Ns4r}2GDEVo^P*7jr*{aR}I?dpYmK+&SRtHOvo;CQS!I`6>Qn|8d5zwS;@qUtc9B_b^9R1T;t@r z-?oWU`8yJ5T6><|)W-1!i%P-x<44d;Qv^Hfc-R-a1W%bTq%?5>onLH>88Ww6S+8J7 z43fiHGrA#Vj}qq1Sx)bHdV=!0BzE(}MtJp5j*RI3W!1PG^HIM#_@nJWOu6%+(iRp& z&8N`UJBwI5;USjiCPl&|7vaXk4MgLJA$86E!-&e&W6gp$pvM@Kf#;9VH{lVQlzJ0M zwR!xK-cX`3ro)7CTq0j$)Zi;@{Wu5GWPG7vMmV+Ohd|SjC1gNw2EEJ7A>%$@$ddAK{yT8%)zvE~n!C z6%N&hVu0aW^UTOjG*b5mrm7s3cj>`wj<53I(P{RI_c^LNx`pmeQ>Wt1l2my3DPza6 z5&I_uqkQLHvfO$De~$Vt*t^gSY?g+>yZOq*c|D&ov=E?Hx-w)QGmZ%&YsuWq8FcEN zbl9=Mn?I1`LhdCk!}K0`)N2ocBo9FhN`C?dJV6LIlth~@itU27@Wn=)D0p9lIb;54 zwq+h_2P;zR>R|ZcMcK;dMc`#J8xABZ;nS>8vdNgx{=(bfbFmMd1UXD;YXs;`y~%#N z)&<8W`O#x(7lA(`0a{YmP@HWIqMKSlv#}f;yIXOl)C?k|zL7O;D?}UPK0H?I0rO2n ziD{V)p7`xdTpt+I52od8kD~;C$)^N7`?VHNofkpXktK}AHWR%4Lzz#X1~Jana#ZJ6 zDjemUI>uZ-Yxz-@H{7+Cbj`_Nu&P(=1+h?iyf2gU;#|i^evu&N_yi4i@8`{X z@|@T_RHOlG?Qv0A3SU%zBHruyf#Is+c-LtaxS9vTrr(dT>=Bm(%2~^7`=w52jl^KU z?IPHDA(}jCw8hatS1S3#2zQw7A>kJeqRlr!svtHF#Z7^jxzC845w^#@&RaoWHx(|Z zDU!$W=h(2M0M4@-%533S@mV2nQ6g^y6{b3IZYc?BzL#)WjSMjUkcTyra^%6#3TnZf zKYP-4F+#Nwm}4}Z9_rO%+Rux^vkM2|LUIB$oX|zDe<7@tp(gcxb{gN!nM#&gM!g2HUMq z;Fp9K2zI)#MMKIU95;bZkDQ4O?YGe@{0_cT;~Wt;2pR2b;=k#%p+_S2^YkJeQD@kO zMCE3a=ThmQuR-zI_bAN!a|WI)<#vVddzgx=R&@9ki|%6~SgOCA7*<_`hRdc{v2ZWL zH_ipAp%h}cx{?Z3UxgWS+|gC0iJ7RVLO(m|lA@!Ev`IG@nx9LTNZVziOA*%_H}8cl z3-lnVSAZ;P-Acb@T_l?W2Ju<83lTP4O7q)4!8^V?BfGVN85vGyZdG#K{Ns+KK5riM zyiCWak78(J9|n>NLey9F7VHz^&h;Ey{NAg_?2R{YYZ961H&^4*4Qa%qw-pU%q;i~&e0+P?6Z&6FBdWt{C110+bKUw{NFB*TTPJP2 zP#8xxad)EF-A(Z4*>==x)Lwx)jP+As2`&=fJkdubkRLprf zRVI;x@t$zW(UA=eEJ7vIqv%!5C61D1NMd{`v#aBPL5m#Yo}mXhSGJx)GS1Big1F07%Md)|+`S8KrJ zZIWF6--bDQ{w!4ad_p(r8!(n1!0x+$0e>-SjQ#7y5Sv{LO;1cn;fo4($wEio@QVL% zsfZNGjc}u+AsX8v2!$1Y)d$$AI0_B$D#P;27E2|4|tB#O8jpclpLgU@L9Mt>$xNw4Hr+QXWS2i zQ-KF#YI2*+R*y-;dj($4A3}-TU?@}(r}pn8>1@u|$!}M|LUXRqc_e+2N8d8G z#_cI2jC)~YM;4ULO~g|}Ax!ZyX;Rr8jY-2nY~PMEuyDgT3>>xR8_WoRd3R4UmsV=A zvcpw4d^QIU33cPeb8~1&g(~?x@C|;w6wj>90`(=YP&s=t zm$6d;j}>b$u=o+K^H~P1FQ*a@;{>pjU4$qd1k2-oG2i{lm^87!w&5!ye2C%!pxk{D!-Q? zudDiS!16cvgv*ny3e^ng36MX!|8ow1XIE{WMO^vCP?tJ^Huvg++R!uRKGNlyI=n0qC;`oKJ zXQ`RAFfQvjjobGBfs;oy$%M`^3{sJSsb?nAe7j(D(C6-)2^rwcxsiIe%aFK7D^Nho z8s;6ELT88%z|jq5@QlktPD%K}6eh~ksek22#_nz=^FT7cKR=IMSA82acTM9t zyG!V7b43zfU4ScJny`Zpb;&VXWjc>U@my>#^XjukVCvW>JTxwfzq#{#NItci?s?cMl$} zNBQ8B&{)CEILbwc%W56QFGU!2#kEmjQ!45fC_##lIkng+!z_jm})g|EY7??jkx8^>009cCZPUaVKEz*2u-L1n=!4n`;87)2Dr{o0IrH& zL;H;?A!d9v{N;9RM#}%N|BDIVQm&l&k#!Axr#CZe&d3t=*MGt0;c@D(VMl(;8_{^1 zT=cnDi2@TJbDfJi_Jmm~qm$@J%U^D%TS|YhuMcOkvAbSC(PwqH-k?l}6Mj zh0E!>rs7Ze7Vy>R#@49MsA$)Pilq^_F`MHwN-DsG-4{ST^aJ1E^kvY0z-Eeb}fp9J$*l6iF*U-eSQ(NS31ELk5tm&oy$44RLI3#KF6WB2^POcp!v-( z%uG^5XH{2RdUXY3CH)gpcA5~2+d9O4{EfN#3eNL#R*n9*CyUz?@}bv+%QD(E;(MV6 zcGY1$QW{wSi8k+;ov+Gahk7*AUhd5({0WEaXT{0u``rAR%ho$|O3;3bYrtA75$j#1 zaMt@D_=$+pr#}S9{lq2Yv`qzy9Vo{=O77@>`UA5yVwkz&B2Ju4Clb7P3k5!`=hc4N zg#CWM;EplZ-MnbeE=>()0`1zMtV9}7^ZNk{SDt=1|e9o0n zwcr(-(%pcc)#^ci&uVhNMjBH#bf93^QTFD~1x(SwU=-I@z%(+MOwD}5adYg^Y;!AS zYgV9qPyr~FNU*a5M)BNs3Hp8eJ7)PmZR+U$f$@2;1C_E#5i!UMd3j8#h9V>=hL81#oLD=ZhVkhtq9- z<0TIL_@!$(7EG`u$KU%i8`2K*4&VQVTS61z^3^PO;yoEs-5x@w`bRjPB}3h~cksE` z5^%jDKwdV#MXwdUIQIP)boN-l@oZ&sKlm2D_0NG$?PADZ{TJHreuf49%0%^@Df!YH z&&n6L;kTrh5bPnt|9;VjpUS@m(!-z7eUb|CzemBMd;lwidr`xGGhUh^%^Zt1rRf(( zAwF)L-R>1eB5XuCPD>PXBjPNw;Ti0e`Q_kZ(Fi-#LNQZo7k{Vo6xcDxi9D_P1aFo~ z($j+$>=DPKi^e|bfyFIhzTuQ&mfqjWUR~)x%hta_gVU#|j!i0R?5tw;1t;(YCfU&Q zc}2K|^Vs+<9Rb-K0dh$14){AU8&kv9^yORhmaL<6UsV(|Y99858fJ3Y1;l!St;^ zOWD~WXk9XwEcn&QPCv9Bs*dl)ldlGOGrINgPa>E3C@8?a+g0FQ#w_R&evb>)FOukx z+00Ot6Ak4}r(;}iWKQ@wSktrv)gRf>g8dGhJLNNzS+@fCr+f)n?S(f*&FQw>Rj}Dh zhP<`SK&^d-ARHpfzVcOoD;3-`RWJ!pjS3Ox_gbXqY&?u_jU-0ZZoDJ^Zo$jwZCGJe z0ee!UsJHzSXxT5ys`%Vvnsw)s7i)`|sL&`p>RpFoE1tuLLLu_<^(3k%DhBd?#h`!s z4o>{Hn+!$=!3t&$mS)%UJ8pSE(T*@`ze-H}w#VVV08v4K|NYpn5*85<5EmEtfBs1d zhzaa+^PDq#*LJV%vrTO*g#|Wk+QfSp45{BQVb0R^AS)j&Y^ zCk2`uFa;H^HDJjJK|1GeI8Qo!2n+*c!06L8zI#AA`v0tCSM1w^^NXd)tIILWf$5fT zDftXrX0`(rl4e1)w;s$a`+?35qKwuKJ#=*mB4>s!Vn8sDyK@QCxL1R??ye~1?Kgr4 zxPe5ZdO@{pF*yA>i9b?XVR^+5EO=VWyYWSVn4VF_V_mBJe?lg7*TMNr{HAMI{gvxm zT}fqi778*S^~*pu>o4!=p{>mSzW-_x)>JAeAn?DR|KIQ5?EilMaknw=L#cB$+ zr_k#6EHbvE516F~NZ$R+aBo^O9*8(YBW8(GcC8+Wj6Z}fVGS;Gkqh6N6VUg)8fBBK z3U^q~<2-MJP#Slf{MfXYoHn0DtPFoMmR|FzeQ^;k(G|cCcb~$X4t>Zs5+>UMoN+<2 z6j||CgS6a}qeqHsFs9H9()J9YU1y&8bAxJ>8@~(s9?L0pH>L{JR&;IqWv~yEpw0hI zGd}UgP_Q(C8ZGLD3+}UcFG4G+&WU`cPbjeD_m+iZR=p_c30w{DLuX=2ZXJBLmnR2y zZUb+H|=lc?u}7~-H|Qp9Lu%{B?cLmJc=xy*(xJ8O*s;%ooZ93EOa3Sm zGm?!}dT-brH>QG)i59hr2fAOq85Pd2;6MJf9%iboX0K1P2l_l3d>qcRVH>64&$={@ zB{P#odrpVf`j64Va5f6qo?w-o`0x=#$d#YdNKpDDu>Zak+G6Y2jc?9E{?2dU&Q*|> z@8808(c<|}4)}q7ehtjiP31+bwdKwmBTQSeAaP|Pf%Qs-roEhR^QaHei7=rZ^S%Je zaPvcp;NpR@0w&~XDV*SPg+(em;C%cV8ltMsEHj=?rv3bk`xp%(es3O;y%Gz%l!U?U zcQ2OZ&qjr5X6!GshZtG*31+49aLY9(uvNQ?rLOB94%sQth2z^{Rl!_Z!z+Nf?I%cT ziZ}9nIUde~we-@YDD3fx#6N=?B<9}&a{AIHzMbYHzH?MCYZgvu;vB9YzxN|kIO!Km zkdKDTN+;0WRfXzhTmiC-V9D00PNt49E@{|H`+wl$C zP@1T$(!yx&vz@I`fuTD)A^hKTBH?C0-bpajI82aHh?>g^Z{7zE`-)NIOB7mKsWNT?x+hB`#{OaRsvmG7FMBo&BI~MsCf}Wm25Q z$k&$1%!J4^z@-Dsf~GdOySN6Ht+%3a0qxA!SxcFUw?6DZyBd{onMR$C{=+Bp)EVh_ zk|e~b6;FPB!|NPA!gJM0y@js^O5`ErAjH6NyuiU53YnC@2X*qd^4_26rs_hglt}y zj~h4^g8%e+bn+h~dX}3BPtu)1yk?EE5`Fz3pT@$v&GKC5LzD#hn30bWhQz0M37dLB zoj&~=%-j+VW6!-$!0%o^pdfWEnbJI&JPh=N>kiHsc#p*)Qp)I-wqw7KH2H7+a_S&4 zi7LzrMKi&lxME5d9=wtWtmAU5ALMueOBBi38&UZ2t~^Pc^9783wMpu85&GYTE5Lqk z#jRU(z`}bRS*Z-B&Tl43dHW7$OE2e~Pnn!|k^}jW&{lC@b>;|@JDGm4!=Hv zgNmG6C*c9^QrQZ8V{>YJj^k+Volmz#XTmd9i!Lbq3Dye;xO67ryCMrBta*~L);*7d z(*|Io;9D$l>A>T+^0DIIM0{5*LK4cy*~GZF*xB_Gr;T>;4u+lv`Qwt*=jUU8XcL!e!vFIovjJMy0fOqo@vb;!u*LBQ}yk*A~q%yPrD;@HnryvjeCO z>k!RsW$HNBh?)h>#mlmZu#43Pp>hGXgzHue2$o>lhg|HJX+p8z{~+yfBIdYUg#$Ty zM4Dq@J37XJ<@zpM+Ggp3j=7{KvX{azdj}A1|@U&i3 zF$30Z%+u-t=#cmf`I=YQ3D##xok}>adp^R(eMrYy&fVb5xpJw)f6qdTQ@ns$?01Z_AR7n6k^JNFW~?w^d;igRE(S^^saU*TU}S=7DQ zgqL5akqsL6QE#R+oc=0KjJQ5y_m6%s`oVcqR$t(<(&i+Ln^TXOodGmfpmURDiB0QG z+~hQfo;SV$^K3G`Rk0afzdQ?OcQ!)IP7PxDG!9KW3~63gJ{vxb;yH&TOo-y+kFE(c z(YFg9o2S8R1x@eSkSD;N_{B+t@f9U2e+T; zx!_7yeSQiXJyO`szD+pO;49SpF(ma#i@_}%VOIHaI0huUZ@#}ADXR4q*p^R^V?3H;57S@iNm*F404!y1hH z?L4;gh6uf*Zh(7d58zJYa8@z=8f-HQ0HLob(8zH>&er_k3z`pe_x0y^R78RPS+R)9 z%r0lOXAPk6m?^|;*}xpq4aS>mBjkGV;Wq=|R8$gLq#KgO z9M8-2SqT$6`v&SaO(s+BY175sotT-rj2&@%fYUq{l8l^IjMJD#dZG=<_EQ4Hz)pf_ zZ0ab|+_(pYXU>8CDsBx?D1#$G+|1D5glyE2CWT89A$r0(SjpQ7(h{D$ZATb7A$KBG zSbc=SZPK_>Sdoe~-a?g&6X@|F8{kJ3nm-jf4EFbXnT+`_;J#1;?Bw5LF86Xg1u-ue z{lY*&&}ImkQNn`oT#%WmM;*A)M)}*HaCXg9QZQjIovw71d620_dTo2z$cj9+DoqD| zpPNK}8zsOR?^Ha}xR3_-G0^|M2P%zK$m?Wn(qOzDcUOp0ORxDnYnl$e0Qy++m-+T0{4wcQPjXw8*HMEYUmjoS(YWkvTCKgY(YG z(h+|%vSnm4ni*J zi*)`vM83%eVeg6|=G7bymTQOV)m9pyOls6>0Gt+GleMG~RVvXUZ&dp-|Ji8QpR zWECYOl&Iu)et*K{-gBPk^Lf8tFOvOxH}+RfcG-UA0IMdP3*t+nSf99G*sm z^b9>6oH4m30jB@)=3N!8=5>fG^E(fDQ_b4~)P2W6xEa0_)<$n6hxRl^isXHdJQeHo!ujXL$jX>`#EJWkggs-?_nunC$@LS6ar7_j&9vfelI*}V zl%wLDUo2z6Wmw8VhuREjZf4c9lEWXwZbbeCGJaQQR2egI9fyW2Mz|sQa>k%_uE^ zc#D0w%rKRAT4^CMdm+iw*7(BwJ(7ppQwmVC*NWz6eq#=_X5z&HF2lpZ4A^gA?c4e z_T$vKbmt0p(%|%{yy*Qmn5mI~FMKVi2$@9#8eNF9yCt#Y_EFp8mB}*h@SC@F20b&Q zm$^P_PVQdSA%he2i1BV&e6~lQT-C4#xoeYQk;oM4c{UJKMQ(t)zYKYFMgkj@6F|ps z7JSy1rK3sK)Y)V$SaaOj(I5J}WN}j}$jyDfxJuC*6CLQ*9Z&Jkir38d0|U^jnvau( z58~by31(~9TbOLM81vq($Lgm?$fBQGM7Vw%dADPTS+3?tf1)R9=q3Sb5rbx7oF;Q1VBZ(Nk)XsHq zdFvWh)iWFpwydN$n1DVIo5ey7t@vYKCSC<|7sC)V|9N0P$56P{pQMOGiak0&Dez>5tICDM{n3^qe?m!s+FB&+jdJcyu>A z)UlR^Ouhn{zr<4Nh2&T6FL#u#WP*kb6-7-so3HCol0&&^g-@LhcrYaIU$)th;Y*1Gv5 zeC>5sP`C&r=@v9neFOZjFJR2&BxaW{MpG`6R2DG>4LFX&TDwqqGw%p&D|AQamlH{y zVKM9x7Ds0O2)jKr1Ws>rV=VLknx;K(wKM2sx8y|3y*xZ| z-i~!!8-lx9qOnTq1k@y5f^W8oaKDT5od%V2S?sykn5s>e$IH17HzO8Q=sAY=b-MP0aUtuVls_{>D7QXIIZ1` zV=hLdu)=~$f0<98Db0oENv9ZgRXWuN-9i?6rieZS2^yN1M$$mNwcUh8BmAr&R zG*^Ze^vaTnUGbz&0?~41A#Yl+zN=lGA)NTDPyYstviF_LX^{I2Sa-vM&QN$mj}1*H z|A|VI_-?Mi^R|Tj($at(N(!L0q7@>EsSWw_C@sW zuqD>*2x0D>?7;3t&h+~lVOJG7Nm>{sOxUDF6*m-Z=p*q>uxVV;^;;II_UuwDme-kpZ~CKsTxayq7VA1A-##442vB|vnmJL$2w zLa+Jix=KkY5;2uXXm)RfZryC07QYiN=W*=+FYUhNC(tU_n^bU~IQXb-VJ|5L zG1-loToh1_PLMuCou{}_MWOu=6WxsS>q6mCsS{h7B!h##JDJfUZhtbE#x#}{VNJa( zxs+H-Pq)XDZ!dCT*MD<32k8lRNuC4U9m=_()D`K)=ayh{IiIO4bgVEJ)}l^IwwPJ^ zjOSlB4?-?V5WB;-aUdxZ*36ZrH#}5OJ9Gp@G!DYsL%MWx=OmfAti39ZaNkIKj5&}%SXr-?S%b+PPD z6D$ESSi{XOe(W5>h^Cv6C;N%o#>;}GF2|`YlA!`y{;@B%ET;=MhTwzMS&ZEuVX|X^ zZiV{Q7O*UIBpX)tLxET$?9=;(sRw1~kxDaI<6p@2eJ?U+(9{|6 z3N_e#AMR8xfU&1?WaHs%JQQR=3TsTM6)Q#8@30|Z4jJXlb9-=K@{YCMGzHvC?t;6H z95H!(87%gG<{$M=!^>uU==Hq}Zd+dk#Wi9y^73>n8C9b${yJpB&r?)WVh%g$*G1gO z_2ouG&f}?`$J{IP86ZrE%phlO@S0~SF)hnWuId@vO4}2f_5L!#wAyl+$gPM+zKx7L;6CX?~yFM zkuaYf{Iv%Hqf4=E{}!%rZ;Usb9cht-0zLhADbI1EBGoyrM7)K{;oDO}S_Zdc8kgC- z^vH-#8@miH-a+8+{E68zn2rzn4{*-75KM1TB{MJYfmQ4CFw2d*TUzwFY`+!(qR*z1 zbzAyDG*pu;aaxb%UH%Zmc?a~jsSt^Ov%&B@=O!HMz&Fu`)II4G+~52W#0uX-YS19I zd@>@!(}qzErAgL~7+n2vD#`5U8qY`GgV|Xl5b4yXVuq?T>yRZa@eJgg*K0Ay{RBwL z$kWq;dbH0|iWefIg%Gcda{}IAWa}ilQcr`vZ?wT@s&Couf#+D6VL;zk9%5Y^?a22n z6##xmFwfqct~S36N>wj$hPEY}mpKIPb1AdTXeK6G*MW!DTx0|LSn;@0*lF%g`tDfK znip4HUV?g+*^cy1lCWZ>Kh=<^6ixo9a8T3Ub| zAA1TZB61GcsTd(WU4zY7Wc1FBZoSDpnP^MMy?YfsA@y2H=o1Bom^k-w;y3d zrf^K6YV-)Yi3b8qU`Eg$Dl%TeCT_21OP&QYS2DeD(sqs&+>nBASBbNGXNZuU;uN#O z;!!9{md1Fn5UE=T?iojzaI*oZ>^3Ch4y|lUr33C4kf*ut%GnRU^H3^lCwMl^z?UI{ zq;&T}@ES;f$?aAsyXZU$WE`XCg^jsGV1eG+N%3jV77 zLzPcw@n&8x2JI@xH_PrZ|6Ucr9H`A6!t|Zz;6$lQ+2LSmY@3uqcvfFg zEnKo9;dEAo_03TB#o-N*y)GYa2&vOW2ATZmtvRUJzr4aPSEyp-x;H(|@n~A+hjU!& z#kBPDLoBJ3z?Q>~q{}>vZo0DvmK|oG`IREgT6q@rzs`e94>{ACwNrTkg8ZV zLu6fzz}sml74(98|HG1U4OR`G&%)gV`x3i^j4y4+6>8oWQJT9 z-$Do7xSoUlcUT|g4dI_A&`s8oWcKt{*5qkAE9|06%PgXpN;7M6c83V5VR&Hea~1wv z5~5i*RLGvEXYhO9TsAF7u)@tU0}_8cX0QLc3s+W8toRtJLU(Z)`L~xQ5rdvzIPk6o zS5MA{=9gjg@T_L0*C-uVYQAI@q>n)N!%(8E6I^jLS&oEN3eug^rjuougYQOv;}%b$ zid*_4Je3XS@!vur8uD@(eYNBUZeMp5&C_Dwwf{^P&3;Grwn+(CTkNZ7i`oEDnImNV z=~H;{!5HfIU1T=AX)ceCOn~D_5AiT>FMrXz^)UbFEOwAFhG@YArfT#!N^8|Z%?n4G zEz$}Di77k*sR8)V;zGsoBp6Qn37T_5%V$(+qoK=n5EwsH?tM6iSXXrN4b7wRZjL#9 zZ^z9RJtqPdu5-D*w1szP%Q@b%JctY5C5A$A4kWrDu0YC`mCqotTW;8Wsab0^l^1qWGY9it|}6bBSuv0>1JwQK9yWZ zQY5*%rqh*X0XQR*drmwIz%o^DOd!eBMfwlpnqK96(s&68%l_oz&&@zLHz>2lFH`6x zQvwsDX3;kpNAP3jYyNnFK8ERMky)Eb%i|}^>$i1apy&p2 zQY(?xgbBcMYu9dAZ&gqz*jb9LXNxOKZiWj&rOAsGu&{M&r`Uu>NRwwjp6+Ak9g|G0KB&~ zKVY7oc*=Dd-AUe)C14mZ zjmhKo7Qq~&5!mP8`QMFlFMb4;Di(6=2tGc(Yz}XR8^L6`Bw6eC1((V;GqVkJ(cZBW zV@n^y(u4jayjqPIN)JJ&-*!9{W(sp+ufjn!E)Q<0&pBQ6sY|O8BkQ;p`3rI&e~$`@ z{*%lrnpuc{j|_oHvM^=Z=ktHb8q(Y0zxWIMM2KA>xA(qOfX>6^xS-qzmLB+n25Oq7pSw(xr__djRb0k?zmH4=?SBS@0u{ZP^aB^OwM|UKPvB5TbdpSKwdC zdd}IL%IbUNG9Qj2Txq`zg$rzH$NC!Pa^!E=anFJsvsELd9X_};NrY;v8Iqq>`|!Xz z1tPAfOJ+g5xE^sZts}~Q$mhu{0FiV>5jtxS2 zA#GYS*1(EoInuEm1MG(kO?vH_B@Lb534@W=qOT=eqwO@9yY4-@v=3o_*DWs3l>0C$QlL=jx?aOxT zJHg+rzYgrhqw#`bG$X2{N`JZqL2rEuvMc-GyNCjLP^?4Ft2W`{zsa!Q{6602pJz0< z=eB&lG3|c=G`D2T1p_RZrdnazJM|JUU!TDhFMV9OaVjn9(uMSS&3OOP7nXMr@Z#!9 z_;0Zq6jn>o%YSreT=grQ^_b8btSakfgP2ox5Zn7Lsg|Q6SvB?%*OVu6ZtFgfuhhb3 zeFHq=!Ld=4Q*eRGH%PX=#r}@Wf$BqMyzQT^;?-+;=s0tTUv@kM$Mz#E4}T01rVr7; zX&Aq9IsSgGgC;P-lsDX!AiQk=PnOSR1DzCUe)l?d>9?P#)cPEQC!B-M-#r-E*o3QJ z>XC@AD^S!=oPHW@Vy7xqLX@#CO&V38@2=dS$6_Mcr0v2q{J?3bd|!{FuOBfMV!2fC z=r+74v|Q;+?h?^+2r zZYq%5TrSD%f;8Q-?_T+#Hg)0?^^&K$bTR}U79l4m$y2?h?xlF>l#eqRelD$_`iDmA-kI2nY_dqPsGsVK`o-&W>{gjqK0|UI0Kp- z9PsMPE|l4EljU>#6S0`(aAdkE8#Z1?=A_JpHQL7X{XO3C7WCGqY^?T7 z#5XO`@NKytr2R}qJB}xGwK=LHjQ<^s{z&5eVh>h7w2SW3FU8&c9S|2Ng`X_D;oajk zbi3~;zB^Ha`|=LJuiJZ|;?ybVo%a2JXD%UZDe{Uk_&C(&-Rbk-fT?@7|J7KY`4$VK808b{#pm1+0`Svb}UXAP^NJQ759w=%1@xR8Ohy*yjr_prYHB%YXL zK+etVL!U$Ik>_kr1ty9U{!2r;_Sz?x3l?u!Hfu7i+~&?)e>{QScaZ}#ZvG{5tBs8t zjfT@)8|&l!D(sfBB#)+wv&%3Pmn2D$?T^BstXz}M+2jRw-}m5noF1+%a-v<~>a4^1 zIQ$;$!kz!m;E237jk~16_8g6c5{@0y7S_U(OZ&_0SZa^gL`+Far4^j)ujF-GrBIQb zWw5*YI=-u$MATm`K=WblSr#@EoXfh)Z<&SDUCoLpa*yNHrafgI9cV+}9a8*1gMIK% zd>&bCw;LMT_QT3QC;79^YO;6!c#?~+v`DF73V6>s%HEPrCf;IKd105vp-o8_+^st!~HPE02QE52ez!uM0ltOX9H#AUagfHh5 zs8!$$+E%a-{65JNwazi@s^mDi=X8kWmE+KyF&R$#&%_)59bujam=o_aiTsfuZ`9wl z5Bja{F^{LLzyr%{NMgJOG7HqkIOAi}>| z@%|)X@?wn%&h6794?i}cR#QEjd8P$tZJJG!x7pJ8gzqRjPZR4TE1*hyF4g~03=d~M zgU>g2q3XvrzWDvQcrNQ(c|=w!uB~l@Yfll+zUzd4)sav!ra;1bMw!*uMK~Xg9L?Em ziDrf-_>>+&-b`6KBxyl3+)|j{m5*VU-%4iLnkuNS=240HraX@`SrC4H5ijlaNmM#K zlPQb%i2DC=F01?+MkDhG3UY4g;a7UpD4?J77=6GBA+A&QayxH%-&Fi|x0 zL(q|CMKDR2o>DnOOfD9a!51QA@=OCRN5J)f?w`iUxp=lHJ- z%5WLZ<=&;I;;zF3&uL_iXF9Ab+(~b@F&z8Z9~4U}*@>%KnHr)_=+S08AH?mIPE|q4 zg;~^Wu^g^z-piiBB1jR9X4YrSfWceVw4CLhogSLB@<0hsDXNf}DA&(8--=@U;${-@ z-aF_5y7X}UM6#R9zJylXV;f6~IOa|h2G?z6-_93=GrU~d#q}BvOFv*tqH1|+W*I23 zxQT5a7=ymth43g|muwg-0&kU_WJ$T2>qw9`$!MKIa`o5aztVD;WUfoj86Lui34Ziy z!Z<9Qw}3oX{sO;ZBjJkUGnAKAp%Gk`>)P*I^n1}A*l($X{tKL_XGIFD)98Uzoj_Z> zC|PF~f{Pd5;Bq@ZssiU)&4Ss1OG(GV znYjGmLQ=G>8uor|1%b7v;Gw4?$zP&MzE7#bD6$+H44dHNCuO=q^A>8GwZa|ED182S z0Ig?fh+wW2Vo5Dqj4fpw)#X?Xm#F$D2 zs?YoZf3Dx%QmR5FzV?IpDiiW}_GVbt@d{4(iBTKreheSbqR$dc=mA|rX1d)2wy60I zJ{h{k6ufv28@O5Pt>_%IJ0wgV)^g4d!iUD5H<(naL_c-Lv9DWom`)`v#%Rk+wsyB` zc@kfg8dvI&`GuAE2<%9lOFG)@`2!nuuVBk}Ga^xAN=i8Ysq0Pya>8&aNo_8}o!-Vs z=j)QmiZ^*iL00s;z8ozaB&4)(CV8Xyfq8%J1sH2vkfXk1_>bE&jkPNhp38L7yYw#( ze6^=OG7Svr8I`?6(2i;!}mwwSf>t~!R_Ij!msls-DeZ;Ap`PD zHx1)#ZnB3Re?p|`RN|>GNgh-sA!GUug0;or{JDH6AGc?p{p)602TiFCx05S-CPvJl z4`*s}96NJE8s8rQ!ihypSLa*!RyKhg<5-1FcK^ZKpR>uXjW&!<@JhC6_aJQZIm146 zUyWXIvuMf0Vs_z<0O+{-g`44tpfgvNs)T zHin-Q=99}G<>88mCj)}JFpAfKHSa7@=b|$nwGQW;PAWv?)DwJduR=P{E+KQcos0Zu z3;M(LBN*9h@Iq3B$;4_Ia;{-IMpo(5(;M@duXmRq^K+a%{_`T!;%82tr2WRLflc%%n{^;YtgS8?++xBbjQiQk}8lF$BZHzwZa zeegg>6O`G^C#U{M(ls4D$b3$QipXM|RyW8jzAeRZwxXbH%{69q?t7Gc+KoRIrHCNs zg$mNW4}m*0G3RU}cb0vNV)GP8)+J~3Go6PiX%?KDYzjRawi@>KsRGGJV}_HCGnqa5 zcz(rSwso%tf639!e6{9L&bQK5{!s85vMO4Tn^29~mk060fCX>O{0mTJT*L%C<-C+y zo_Ok~6NxcDgjA zqGPZD76po;Z0>PbK6L}=rrD5dTRWjBcNWT;i(;Ln78yv;q*IJ+VVj&4giJG`&!)^{ zZw-aPzncY6aQzh?N?xejbDQ6X-r}zGD%oORhb9%<&ve==>@7(X52?1$KTNMIt!d=rN;KUE@}70X5vX;$#TFbZg$p#k5#Nx*ZviWTF*OR|3MX8yIl0p{(f8nMq-qC2Nck&P~{bgj=cBDvp({@f={ zuNf4>qxU7SCp-~X`ngqzzPk_b&y1Xw)TbSL`tjdchD^Nm7;mjJ!`!-^y!?@N{j zXum;&NdJmt5-)3z2~Q^wbV|qEyD~8K{YpmL>nya7`@^3-YZ+7NC#>SNY?t4!H0ZN~ zHgx2|dtR`n9u6ASGulq!xNk)oQ$8e5ULD|e-T(nKnHU z^bDAD$Kb2A1vBDS&lFwW0e`r6=c>SWsF}}snG?5R+;tmD1P0jw=@zt9sb@Veo?;ko z=Cmi|1SW4VrE7CE07O_uR$QGP_7VcKDqWJCR*P{nCee`nZp@v8*PtvknJg%Jg3|L7pN1@7Hgf!2v zqBlKGVI4c-5?y9aXfd}tuld4{KRL$E#5ZUltWJo{Z#Hw>jQd_#6WV=7vdZ^;eFNzfsPqc}v zHDdDBQntG73U01^2RxZhR;NXhC&#_Vn+MlGVc-IoIQytez@PwiHRiZwbQ2a%OCmQT zN5T8CCrMr+O#`O1J^q)gY&kJ zSbxPd5`L+P)nx~8W1u7Zo7*v;4-}_4-+wT%ieixWxrndY@}6Bd*N{HhIhR&!5~HWO zvhm8OI1@HS@}PmFSaz#V{e{4_y*^JfSm7$uL;!+ZglYAO5?D!%k=;Sc2Q+b8~ zH3mec_dWa`8D>NtOvYCOKOi&EoCd!zqvup_z_fYJJnOF<1L=+t6<;e3r`LW#N3}u} ze?QL1z5a$0i{+^5S95H3mc)v+I@G5&0|W2mgRYJSc~r6uBa}Cy;d(CLTx)<G|w2W$kdGn;b)m(=P{SCx35!PssPKoJCm+X zEu_D0e`cLLHeh|OF3k~9fCUSIM4IhDV^pTY{Z$M+{p-8Nj2g*RdJ6Ua5*XybSbu&Ur1lceE!uj{_aO`!T~;tsLdi8O~2W^S4nWD&xZaP zKFTO9vx1BM&ru`sG_KGwCw!Gcu;Kdhzhx8Id2<=i>`H{ys^K8sdw>pYeCu+tRh@Gq zoks6DI)n|dq^<^?6?;Tvm{KW+${KHZm}WSEoLq4U4eHdu$MgpnA02|l1-2wHU=sZx zmCaWBoMg>rO<+o<9Ka7^hGg2MEhNlGf;PXH4lR=f;MwinuyA`S``_+*vduRZD~%Vp zcAwWGEhjg@;tUb&cwP(F*G-}Bf{XFsixGB3SQ0K`7LZ1#Wc1lNz&ySr&3yCJCpF6U zxJGdnl%47Tqro)n-=#ug+kD`}G)eNkb0P`go^iGuhwNHSA?xXT5uJ}+Wp<35Cl-7& zP^!1X4&}Kdb@hMvFjS4q8U2Kbe%$=*un<3R;$|8qqriEpJh9bKoSeI%$VvqnQF-eP zGm6q2uS7dwM5A049V&aLSE+_k*?UKujf4vQ*ZdG>y4%TBzXbV>Ox8VHwF0}utGWomcJAh>cvx_$k zx3~(>u!kH+@@`>;;Abg36D0;e4vQ0A??S9?DZrSbMMQ5xJTLk`L87?r5nX(J0UD=oy4#UF)cnhY)MAoXCf99oS{ zuq92JYROXE>ah|0#2cY&$3q5E#K`px6$rJTp*JuaRY#^mj-MY2N173ns3-W5W54Y& zKcf!YuIrMGUrW(=?={vcv=fY@t;pq)AF%nt zekd53K;_oYq6r<6tAE4Haj#d*XH8cKF5>?G7GFTTxtJaFh$9lO zBJh5_7TL6ib5`ZM(M5wVaX4=R`KzAK#=VM%ZY2}m*6a%~mi-?ylvsw_T7{V2YsWbL z$`JES-Gpvd1^ANl6OGlYQLHWjPM*7qb)UE343R2!>sHQXmu^a?&V7z*+&(b!h%heI z(55n~so3Cr0Y)|qz^+^o`tjXcFbkbQe_2dpgp4D&ZrBMF+3rq`KU&JTrBt&ocoXoi zLn0Kfe+1Eeq8P0mjSfHL$%hrapl1D+*?nLE6u*=p5jsjx{*&8{=y$*?>m}Ip^(-8V z79JD50vkX!fx>=Y|9s4rX;io;!HQuhwRPxrF9&#OnG#bPan#v+L7RG!sM>MHEF!_70;Xx zfmO%zp-Nnq{&xTe$qLL+j+T-p0wlr z1ZHcqKS&Eqq5Z`c^wF;nG$FyT|57odKO>xWYFfj1P7DsOs6vs3J@7~C7w$gf15#mJ z7w7s>h@NIguR#=1_N$XL~rwF8?7V150snlRP`TXY^n=&jz4xIT)1phQMQ@ZZ5 zZK3msb?!#qX2rXBPOcv(3vA)Rvpnn@_{zI-{Q(4ruuRy+Df}FX({NNuoOps>W1 zY`LaOsxAq;9=P)mk5t!#YxG^d-1n2r1D_$*GwlOt2B@kgqg@7&TiFb&CX$zRz*_wUSXqM3e5A{0+j_YLKAUKo>YRvmWoi;!STs_Dp&! zEDT7c7Vb8%bUcg+ZixlAOF|Vxdu6DUgdlxk8wvijjh$VUK*t7!h|iP+W+`h!==7Hu z$URRg`F*h7*piH&QlW1w^vR(A8@R$T)yF4YK_5=VD!tE+r2e=C`~`1eOgftHk%zp6w{dLKcPOt{qITv}$h0mEsyu9seGSfBZlM}v8h$~i%iMC& z-5;=1d=EY-5Fp@_3!{lGV6$Zx^axx9FJ~`S0ycqig9=&cDPQ(uQ!_Xlr{n8d9U7?A zf>skx1KO@<=MA-jRf!a|Dw`0il!@eQxC<`JYr%o&F03%W3u!;oa-@} zO2`$&H8kODd#L6`w0a>bsvQ{x3GK>kG)geUTKO?G}dIFDK z&cv7cEzG@+Ay6~mI?mI*8OJ$iapJNuh|Nt!onjl(P}k1LUpT;gcK-^#FSA&MUEN@8 z(+-v|wCV4TMi^m}ne9Z6yiC4=AGn>9PL~XwW!8mPa&$4$b^<6J=*6&ospY456RF&C z2k0(Rp#B5Lc}**1soFWW>HL6&# zcPhux(ItNp7O{&IZa}1mG0i`y#E*z9siQ`3Na={wY%Az96P#m*%oFbNR({abQIQ4Peq4`F?hdbBdDAaA%bG_ zAZx=;a-ZueUX;8H4gL{ax4{h-aW2{fMI9WytWEk91xX^u>YebP9_pD*a&31ngKz5} zpxmrhmd=zz<((GfxaSD2|G67Z@!rA|slPA~@tV*7F@#UN;<3s11?O2TWuJsuQOuoA z@-!ka-A7bOD**9fQ(6*#jPG3;g(VMCabvy${S+&UZ(bK;^-(T& zuKN~xBd#+cY&*_c{v2k>47>Pz9{|+{DlC=z!ulDC&^I$h$UggCoMSkfjOg4)AB{35 z`S*KpyfB%hmx__|iJXT!j>|^w(k3Ay5=dHE17+LD;bxP-%uOa0-odD`0YY5G;EW_^l4{c^YQQas%VhYn(7k0>jxSA zO;S)TmtVdww-udiRB0724t4g+leNb#z@$=JOx?N+yN=)Ga@Tz3PKG)Fcg7t^O2>09 zGW2(-B5B$5j6QsF4(%*P*b9LZ=+pNLm}$8NWcb)pGNn=ob7JqH;7~SuIO98%j=zD) zy;F(gssaektzvw5)+G71KXI-K0$$n%xRPo?(+bO(-+Efasn?M5u9jl|@;vzJD^5oS ze#6CBT^c5JllOj6C%dlBn1qbpV0sr6N{pjVhcRzl?GEPZ(&a^tgi==mKUfL z?*$L;R$+t?$Fy6njHx#iNbjix_D#WL`u%MVl)lwtmhM%jksIDKITbzVrf4#)o0!@Y2hL$Se&NixZR>3MNXc8orL*xnDQG7p70PkiMh zYjT18ffC=lKs*yKIQqU}FK|cOegTv=e#Fb zxoQkW9LiwY0byduufW%{(rEjo7O;{srAdc8$+<}%@cM^ReAv;9873TeuS0|c#0!FT z)M;|)O^Dn8yxh1|locu6h=)gLd0keiQiqV0DyA0I`Z zv3YcU^AUPynF{Wjy$>B;N`wEp7SzZbz~g~sXfR-nJ2zc{FU=`9=fMwjt4w8c51&TS z^FZ7xUrMS z3Oi#m7%fUyEkBL%6Ab97gsJpZTOK^{P64TZ>6ku}z(0}QkD_~8xeo1R>gSt+{vI;y z?EXvmHaV7V?)%Psex^lY|Hgv7cRak_T?8-pd*LtD7y$VP^v-9%*~>a{{CF3yXN@&e zx7dw{`W9eK*a0}$xq)ul`Hv_1g6qUL1~IR04S|=7NJZ0)STyj?LxojmVd2R}-1F}# zx8IQj8$HftrhA?B_8%p#eYK2geLeH^`a2qD(hG`ZzxeyKD!BaYD(1tPK5lkwPk$#a z!OiQq+?H!2#N=_T#(ke)&aY|oauWfQvk!qDf5@sITF1QpDom$FOl74%r1HWI<>~$S z5azS}W)MH)3LkiB5Oc8~S9x592f{i`Tc`|G9Zsb?ZvBEvyIWwSr8p&BpNLGc zqCBBb@ZtR_oFkG-Y&m^-|sgLxC6<4hHf-;t(HKTko*>Q5*;u34crVKVMnXGTBHF5{cV z1#ul;QR>)n7hlP!vT1_Rpf}J!hAzvI;(9IEb&cgAr!oEdS%9X?$8vWnU!3nyN~)8` z@p3^h{j*kxBu?iTPPg@_!%l!UXN zMFR~eDh)&`m84RV2I;^1{qDW5@Avz^_r6~D^jdjU=Q;bd_TFo+wVvnmdCyTMNl~Za z;fNCu;3P|Cbd)20u7Y71d!Tx~0@ZWah3ndCp<#0vs*WB4)muje@{JeRh@~Pl)8CfP z-cGQw>=70$kRqSdrZNj&`IDZvgV3S=h?O7Z%N~_oOEN-y@Pw)=Sz01bzPI|K4=}_d zMS^$@%VfNh)$r5#OIYXj3@-NO)2*QfsJ6njva{qS+a2{vu&7;@>KAy^g=hG5S&1=o zctr=B>&4X-wxr{@@^!F2@D139ArqW9~j;`nOlZ#{E*n z{H1i2w1@=F-XKChaNOXG4r}oD*p5jd9?)M@j#K;#psZLHt4F%gr(8bhGXDq2PFJSi zs4-FLGsn+$-lTrTU4N^9}ZgYLf%E(!f1j1{|gI$;gYj5TpEoU73Cq`gpG}E;tVLI>eZ}-2U|L zKsgdoFAK}#E+25W^c>#loWfVS z^b8b}2JoZH2r@+~lihYk9(zBx!qwCeuvOI}%Vu;yu%;!cs5K`{ZywAxI0II?;$)J- zQ#`m@2)fY|R1#l7(^Po~*Ouac;QWJkeLMgSpZDSQ`$9zCZ4w@e)CLu1V<>`v#_TF`n`D+18eSHI@H(bLzm5(4!m*YLBd}BTKOoYHIM&#L%@t}~I zf@jR6NuahR@hi5V`aiZYGZMtez|D5J6~y`5t#*W$TS8r5Z9k5gpWZ|0HyM(5(}mO7 zxueuEPB)PB5=VMnN7J<;G+wv_b+(^oGqIhdO&Ih4q!GmPBm2;MKaKWI(2pNfW?SI2_0i5z?R zixqjN@f*k6jwC$Z`pYdTOF(Iv8nKoPf^C)~IDX)8)KKq2+2S;a&*Sp?JtOf+RR=fU zIaYbEUy|IXYV_hxH%5a7!v}9UYL-B$-=doUXsYY$+6GYQDGifCW;PyHk30lB=9(~bRT;31cZNBFbZ~gfd2-a;#Ah?(;Fw=5>9BVu??Z0X%m`V@O9M232X?!!CeB|_7F@bx*s4&_?H z{7*4>=|M0|dAE{(^$sNs!mcE{*^ZvS7D^Nal7yQIGHQWuuqA9Rm5OEPub3xxpfiyjb4{2k*u+Dk z&RMh^m;gr;AHeS%D|Y7R4%g$0hT;3{DoAPMw1ng+OTK9lr~EE{y2CWw$obVO&KS!o znU2K@W;^IDx(0XdiBg+2M(8lGmtVK`J)6sUdpC|AMr(>BX~?fGn8UAT6_YmN;~i}n z82f@TdGQ7sCvg6obT^(JwwM)mJjEPx(qY(wD#(f03r{)T>zUdxX!@y2YtM<&IC(zN zTbar5UdGbI&>TFpXfdpB7pA4E6WDERCS+F(BagXxz4<#|3j5#SrM7guv8xDUZ{0_) zxRXrW^@EI;CZ|{bZbR$EEQ!W38FFIMHK^l{Bn_)X$j6hP!Twb$=Orjb-JY9Zp86Dw zU4I@F-6>`ns1Vt^d34G=1M!>^khgKBnJ7jvSjg zixvJgoXnIp1^J)`SnYBV8u{bdd*sP~i|^K~XTWQx+9gQwWx1tOTpt#@h?f8gzj zR*35!f!k>(7`$mp?54>H;KoGe(V1Nk88w_1-61gVoG=wh&qJMbGb&RV0*fP7(2|;N zLHjooY9~;JymS$CcjRgmx1?0+a=w`x=5V!N&&24nQ`#hStPbATXNOtJiqv)&;PH1Q zSaMjLRVoR^5IYHuj~hcaSsRlhCmyjGLzlsIn=Y1JJH{SdS;EYUss+)?EZBXGW3}!Y zLpj7F=)RXna=ZX{Cd<%};&!I+@oT&&Axy>y2zcLbW9~f#A|u7J;|uq2b!R;4*ve3$ zAYqcsF?WP!SkWH=Bguxk;ndfr9!99Ela)JsAoh9;d0e^(B$Eu7c9pXbwo#9~vks;y z(jvr+n@@RHX0YZSk5TCT2WHZHIZPQ8BlEc0432fqD#r+s;!^|M9A^~SZ)n5pIhhXP zU71YTUUQt@FrMsYkK_5Ea%^5S3HP0B$DVF^JiO~1Mk}}A1mA;vU1g3{(2~z2`SWO8 z)(K+rVI_0#{xxPsnmU=|@Q^Xu@sP>+Vo!8arch0VV;CAa4sPEhq%y7^XLRe6CkwSn z!T3}BHKPp3+v6=TbILEiw#g7Px2chp=XkBfV57F*Y0pdOiTgeLUB~hf6CguoGjShVoFV>-o!I6TTn$`ntuQK8eO=3o^g1TEQ@KfjtX9_t;UPuDfiZwHp)FqDxfo{bXNnya2A7%5k%{ z3~8J81ABIz6FjR(gg`FWz4+}>-1#cO9^7WA_scQ3%N<@tbn?z47aMeN%a zvigz@I4W74y8gU~<*Uqzrh^c+fKB@&o& z4L*f&92-d?+7zfj%nhYTQehUFpO1!T*&?KASSD>(OGNVg$wCEm%4Xt5|qu)LLH zmljSWCDV7pic1P~WzcY<5}Qno#uhLF4=MV*H4~y1PQl-E)mZZhoX3^2I3qu+1&{iT zBPv&Fpu#Wma>(gmLddc^-hy6Xzlekd6l zM$1pD(SzL{WWG-h+~uBmwg;4{%uo&Ab zM(B~@>b`3-P@RL=|N1%|ebtNcm*Q-qj=Pp+b?`{X&cZ3j4eN|Os0c(i(X zABMJq84KeI_ zMu|whkHDKdZZl^d>Jkkn$`*bxAX|!@aP%83$}2kx4CjaURq7r*nfMOZ9?@gNj&eTc zukDGjNFXY{dCcUNx)Q#&GVuvgpsjHg%$tMKaP(OR-sHxOEWs^yqwoiuF2?blsS#P- zkVeY(kB9rMXP`4q1I;Vzar9&>qS&iOM^2uOM^dfmLkThBmpz7Tz9~w@TQ-6BV=Gb` zr02S0>MYLVWhE(C;Yd5fjp6P%aq3r8fHwD7R^dVu6fL(RMv66f@q7nba(;l$6CSfh z+`h<|R~w0ofef7`eI9kfKEN%nF8pZ{jWX0jhhu$kw`BkUjhW=0=P=IXHQU2!_+AT{lc?Uw%z6hNVVzxRXC%jcJkM~x z`ALZ9^YHWx4V+iXu{Ohj7VnazK>`+JvrXaaJ{>|mr?Z>POlajwF}gxIiPoEDK=_KM zv~bH?SR1wv)12S3Mhfd-#JV^zx|2ZNIUnEYvs7q&6H6s6hLLKejm*J>Kbg=mG)X6mj#529>L)*nm!RFgoBzf0W)q z5=ikIQ6L?L<3V%!H#WWV68*|?(9QHca7&#Yn9AQq7AxR$aT^*3J5l9P!{{N-v#HFn z4U_g+&}5ZyC^=LIRrUv9Kun9&=j%X()o_}STn#&xMLuGlU{ z629uYp3`G7gv^J|$u$BtP?ZEHj37G__duld3sCfxCy$n02NTQ3ptxuQ6t!uirkXGg zs0$J6*lt`{9pjq6+LiHmX9{a1WVrdw3Fg6Ej!AzZ6Zki6P$P6PQU7`d_jpxc$xKP| z>Wx16ULTG5eWPewjTt@WU5yoThZ%PXF}hTG4NOf;V+-fof@_I1t#|Rq6wf|fYo$%C z&e@O^ZYyc%gvHpSBuUR!`;st6H<-xj9ST1rLQKOhoD^M-(ZL59zu<1>Ss&-i*K(gZ z99;(+;;svx^?hN_$?u2$vAfVtL620}iIRwx1ja#-gR>9cgBXM1#H?H#B}6QztI~Or@{m18fS#EpICN<#R>7>N1prBEfbmYsi#%Msf+F*(HvG=Z5$Y@}vXX;z5S z3C9D$rc;G9i?yY|!y54O1yLI5aSeXPN|1DgNl>_DC-j%9(hrwDf~b)vj{SH8 zAAV@2igHN+o5zw#_SKv=?jRjhGosVX7eKX;36XMb#lxl9@Strh{2Kip?nFNX&8y4F zJhfzSNKO>2xwH|MZFVF(x3#d^PkQ0!jr=On?v079IFP6ZeYU9I@E8_ zq`urZp|{bHK8W1QUbfIB?*>0$#*2ka?EGvvBhA%dcdmm^e>kkBv*^Z8tMJ6aLgGJ5 zk&d(wCOdwArxPcS5QuY}AzqqRRfhQ@93!Jn)_a#=|E@bk>c}N%OkIr$Ilg4i&%HEZ zR5iE%Cq@#l>rtoYs&1FQ-ep&?yXn;l#w71)AM;&7h=hKzr8O7&V4wVM+Wk-xR)@zh zcfUP`M1eACw_vHc_OUdM5;+D%nl6mucf``OYxs9el5ncl0pLZx!|Np{ah9Jg z>5Mj~W4wRE>shrpBB}-_-Q{Y<$5_yBSrO=Q^cUSbK?L>%rs7`9YWBkwxhknZb-Mi8 zZE$qF1bY|7v3BPqt5i9T^W?hmg3WygaBCMKF0UHdao@_|flvbqxHWv{Q&IBVv_;@5 zYfgMI5+9zL3(5MNE-OTmNVLwwX7(sFt!?HHe;>tk<10Z{;!{RQ!xf|M zXycq0!!dl50$CODgHcP!0Q*^vWY&NOHL6e|s!8ei^KdWTEN_AmSRhZPO5 z(x9bpWyx75QQC4P3gvX``TkAZ-u%5KY<0UL>2H$0S zaH{tm6l#80V$_xlvz8P z^UJlyirduPFs(KdA01XH7uhM?@1>WijYGMk<8ai`qc2y04RACu=|7k z@r?0xY@57~hRoC=ZVThV$)yx}FLQMf?Pt;C#C0@#*#$?raXTlv0iNy+!ioBypmvHa z`c#I&=d@^CI!lWzI`a{KrXxTp$01CxCw=AZ7#+x>&iKa|I(#^ZBc^1$9j8gI9!)zZ zN|0P%u5P4#0{b~R8#0@GsPy`;5WJ`yC036oC#_XTS*slVT-XZ@f{XadsTkd_oPhd* zC|Kpyk7WdN_5OYpeYBYvr28=QM#0#=)D zk+TbeaKF)N98A!p0YMMhvP?a?<;^49W_JeHO*@1SqS|rFxM-#schM2=X2WsYYp~;d zDtv0PB}>PM(B)so0$vdzKJRvuG1onbZtz}eKBJ9IRpVpqfjoTkN`~%RnL=+_ETP}R zwfN)ra;$6xF}IxElAy<2;&Sw&Rax@&&{J0p^;3qEwc*W>@uV4xj;u!En$<*p*92HR z>@hn{%#(Ayl7ZZg)9Cm{3X8WLWv`^BVu%XI@SHAB(WmisWmFa~Ag<8btb9c+rl zUW_ij0F{R3*w#6ZXxykm8s@tSJhMrVp)&_&bOG#?)IfiC` zK8Q2dBtXM84|u@7K6ViV|4 z>2Vx$)q+^0WufV7FV*u-PEaf2l)@VSMnecBxdj-uD#^%rUKyDm#`Y+N=hbTQ|A=Cb*}AAN(sqd%D$=M(W!-Y~K$ zpc39sD#C&{C+O#<2l&Z#GA$lxM<2IdOyJhsW`}ICcJMuh|H=I*>f@V`aB?GU6<@f$ z1HFH~WT)t>flVam1!-^0EcbIGu7cwae&#b~mBx;0lTS5NO>xeBA3cEf!;A=+$bi;~y!;Yzp+xt64b zPQRZ+#FZl$5}`}p>mOq^#WS6k73LmW$K_D6 ztarnHbi}uE?T{;wX1MRs0;Zo1b$!uud>mEP^FkGIa6E4Y-5z zDtnwFV9&pBB$_v+sq2PwKnicmsF`M}q9)gK+%PR_4d$hrpX;&1`OT2BkY3Yu)H3 zyY9ea&|e}&3kT*v*Rc{deAx&(@2U`OskqAJ8SF6lKF4g?$NA0jrl5I?9u<8kjTX~- zusr__v!U-IMs3z3!@F~^M6!qT((YsHlog1^25sV>U_xK^DA3Ru9{5dL4}ZjUqW5iX zPOj7mX%5$!dowS>l4VxVcIhalG`TQ^1zX`Nr6$WKtG z;}x{%seQZgNslba8I_C2_w*p0bQG#j&cybQwH(VYmX*>JB0TkMR>nRad1dzKxjYio z+BlZS8YPmHu@uzy52CnxFP=*>q|5UsgMO(9(=|s9*00hf(_X)Y(=T$_Zc{CeL-C-} zs7@Xq>i5GKd3k!=shshT_QrjW52NvxY}_m!4b`j$yKC|cFo@BkhZ@~6FIt+0-IpdC zHVWefO&zlGg&5sXG9L`yq~R)iD{7dM2$}H@FmtagT6HP8<-eIr@~qYAL|a$N`;k*M zPeqknZO`7UaeASW!yNHN2stFt_YK*Zfm&fa+L{{s4qwBK5ueM zWhC8vFAIyd&7tKSH!`Q>GP5!`m*Ic-37-0#cEVVi4n^mI{gPAk-{*uGI zP&X&%*SVqS(u2U6+T;DJg}D5*FN_rDSjMrPkZCrLoQMJ_^1c9Fr{u{u>3B|e_ZVtF znUVg;Ot>^Y2gOh9Vj2~BMEBYdt)j~Gz=Rwad}xU2vv$C}J8GoOVHrE)W-@5TT9J6k zZ4fxji)eJNN2eLDaX22Xd_RlhY{w6<3(7Ky&ue`mHl!#RF>fAy>Y>B=Mydj9=1_HQ z&`+RNn+N@gV{vO=Dq@W#4bPIS8YrAiR{O+ODR*+uJT3)n!FjKDSY+Wg4MTc!#1)VV zi-uYS9;(jO#Z60kLC<9pRDv3fIdKZ)r|XdNp=Y>WbqswtaWf1(k7q05Y>5Q3lJihk zBZe7gurZ7wUj2QHRJ9J2t@l8OoxNb>(#*H}sZ4^_NE1cVN6dO{SCSi10cIO=m}Bw* z;P0e|tsfongSi{C#g}6V2^qqH17&bRyc`s{HA+9CEH)kP?zMNY-GtwZROvI6d_eiYoYiiXZtawOJPn0l?M zfE}xx$kB^27y`3E$!aXk<&A>HD{}>!o1!3>*9@T=vh=~MN@hb!B0ghmsQ!=wJu_K` zM0e$Y`T=i}d_;{z{St!(GAwGGkRjHKbV(w6oVjp#CHE+A5PYdqCo1l82pYvql$|(P zSffleJ2vxQ91+GcXDJ#ylJo8L6Q|3R8yT_e0%okKCtkjIp8s}3GLssliC@Pm(S!SH zAphh5pI0@_)!^(gwm97k>y-=O#YAP&J$?%6Z-2>bC8AVRzKDIo;I+&qXz46<$euPX>br5 z7FM9m$Ndbys~;OT$w1VzUN|gkNSriHa9V{r_41s->CIKBU7Q(7H!-98esaE0!$;GH z@+0VxS5M)GmO0(A&x9D>Ovbg*qpAC(VdUO&&d=j|2~?_!(>vXquI|`qx?yQPyUa_E z`i{N}MFwV|uQi(94!8_A2QD!3^Iw9f{Y>;fsYpz#KQWO7A~fHZfT`IDW@>&Y@J?RA zIaOPrjpJB%U6Z47?@i%x%LvFf<;GI$6bu|S62Grgqd8l*;DOWI@%F|r9I+r7zKs2f zS%x`+$3r5ZJMNrdgh7yCfiE?P38DRn@Sw$FJm6TC0`)06EIO}zo2TzHC*ZxjXYf7g9h@G*I|{1ySdp;v2@v&7 z5@iMPoX6b)3>G?t`jxNPX9YsU_lP{yo-D6m*Z9LXuc>IOmkQmn*C6^!B=i~sJ7tv^ zyu7DG(tAqSQS($uW6WdR?{u2Aa4E-Ybqe^|%8X;d9){+Tro<#no5X%Bf>qB|7@u$* z>Rjte>+ZM{tAxs1bH^+?xCD^LoyrOORM z@ZoGta!4nDQJdz>cGo%+)4&<1^C=!L_Ee!)S{sJDCqmd3N$|_}z^`(uME==!)|GO# zgTD7*T-F}Jebe2rs{As(zm&^JHub~9GG$mEc7-Vkh=-z%2bl2V0IrKSAZg>cc^Q{8 zYR{vLD07cF>1e|-HT=j(&QEIEo=g2Cly%AAz4gpHiC572LJLh(&oQ3te*Wn85$rR* zGB!Wua(y;_knN^N=5gz^*1o6k&b1CMUf{g7gQaQF$G7F@!s5$ABl^bp8hs6}#btJ9l1uAot=zaf(6G(afArA6t^8u2eB?KlBQ)xhn+nRPd49$rK&)@ zYra6%1Vfx(b`w547)c}ctY+JKn!xVUIX20l1TQJN@eTA+pnT?G%x$(JJH{ea#mgnRcyTzNX`fkuKSRao7OyyRbNn*md~>%TQB$67t7nM5vMyz2 z+!B~HTUBjO?#Fg+?9NfBWbJO|(a_D$@yWaS(0JVwy$-HoC+Hs`_j*FeHosyxrjSl% zjMFCW18P;L`ify*>JWH0snKb#E1>;R6Do_#kTvaL;3N5-QP~H| zyiQAxSYYv>A#pieia#W3*wWZ+9IPEi9B!{9#sw!}a9<|fo?FFi)VHN3+@7}-<<>xk znM}Ht1#voIPWI&KqHxeml=ar6K~f`OlSwPi7A?h5zTffN2OB&)p#f@z6(E4~p?PS$ z87~bqFn4nn!-Dmi)P7P1mR>rCu630Ruh*Szz2pfs0(mGad52r3FTt?nC&ayz#rqq! zpx*Z)i05@P_go)>{B?p&ts{xOW;7jmCxcOymmr86CydeuS*P!(;IiQ(L8zoIjH$W; zLgk`F`;2O(*F_Pykr^Pc+wl~>4*JoFcSQhixHrd(jnvMR~W#$MF46ep1{9$?;z7);#K zg}+9IvMpxk*vW*p1jEI5JD{A3^crVed51_6}t)%~mEN$fVL5$Z1 z_Jdp{YL>cV5C1fO?$fXQMY+3~8|1n0STLF}W4B*@?%Ofk&Bt-n1`Ggy?g8!@EV=R3zf-C5=Ko_mlkI7|~P zEalC zWB~d#3$Wu@7e6{Sf~ro@A`xYkFrp)Y^n2ye)1d~$s%Rn^mF@*!cQ3*xX8z>s&~ezg z;UZTXs7DBw6ASF`feo53Q1bT=hUc(|^BJL##f<}%lByJz%F;LHTn-}d3 zLH<%}>Sy)})AaI~?1@tJ{3aO^X>-_>+r+~$t&h-mc`AxszK6%vjtHDXeQ?>#lfc`N z&3aVr#w981!7E&ztd)GjdR?%jV%s9IL)jY7%`XNwJ6RIS&GBvF5_zz~m9mKl?=%Do#X>sq~~GdQ~-R}xwaPEn9+NPwW+iCq9fHR7T;1FqB~lg~1%+1!G0jK?cL`0#h^C*IWmyD)`ZpnH zj5=mH=+GahSQtu31jYT^QC9y3=*nlqu_O^H`(ZZ}U%SBaK5XTCay8tWOKRCy%d&9K zYDelfDhN*P`hvMeV&v!%=VQnQ7cU$BeVr&?eTLuE{qhXXX#!l?A1! z<6j_fyxxjg!Vy5NyWm9LI@)lu9p2_S!(EFuh&Zo64<0sUl0zPXy>&R9GIavEdccc* z*6hY9rZ-TQ8|z+8{)A;K?THMP!k@3Nf|>YKs7NSdB3N0n$|DVWx8`yTu2am%;&Tw1 zl+GAVw;`uzInvdfhU3xq3g#!rbn3qN69>4O%z<`!^8E97vQT^q-&j2t&P_fBS4@EF z_l;&BXi1Rpo^o(_9R@x24ftuJ3fvqK3L~ae;xDUw!JMR@(7CFTneqEP+#Oa3*LNHQ zh5efJL9jASc;1gcHLYmX&SW?eG@4d6hk)=%W%7eBjqHa4*xqf(#8OT(&0mJ!XL`cj zSV{W2Mw+h4l%i*5B-5RGUNrKMCDu9zfanJq*9*=$BasBHn@;Xo|pF zlS$M<(i4onOB0g+0(LRI%;J02f`|mH0G!IwGxuKhe%q&|(U zSf@bN#Uz0EdvlVK`JT@#u13||v1E22k37iNqsd-jOzzixC|9pfotY4_Z1gBt+P)uB z)pO|9enWa(rxp9Xt%>{IAqZaa5GEQ7p{cJn$uT?xlTnmB{FR4$7Hd^`n6yH2?-P)n z-3#p@2Qfsv5r&Wd2CP&!D{*@QuJSvFYN`u~T%eF(=s2gjj9$fthn$8@lEwTf7k=Zh zA_>$rP^7OoUCNM}0i5vlrU74*8SR;TYWslmPBazZq~ZBE;~1ZL-lInbxIG-B!-`~U z6Q6O=Ean$}KZNJJ_d(b>BN~_?Od7{3qt~IcpgBj07G)sR9n;3)<4sBbR7-lUsRQh; z9%AxcpY!V?U2uySfjku}+Fx{=-}#f%-2LWJF)IVo#nsP|*J|WOX+9PQbfA5nEq&pz z9j->1!qTl{50kTUdok0ooZ#eb*(JGCU@$U6vXaQo(J1b z#Gqc5DS02~P6niwW2+WdpQ0#3M&I#4InPPN8fK!-Gb{2@`#cyPav^>`5|VyxZys|j zHQt}*=OZG@<4H>L{=e67o&;}6$huKBOBRMNv~l$G5aG?6H*aI?XWZ2_fa3>FLjK); zwvMj|d1kF}WwR#3_i6;|Oe@k~b{rQyk)ob1nS%aX{$RLSj)Kf@++uwVEN zJ@^jq-Mq)dhI78Z7cVnEMo(kE^DU_FyTds9_6>nBG)j z^x;@DfBJf^i)pQ;5Rdn#{cnA(^_RYO^7K&q)7SsqD|?`Y42YJ(l+z#ah~X%@dxkZg z#d&=;h>f8yHm##{T|K@y@dkJt7xkA-5-#U|#I->sZYzY3l3l+;$uHK14xYSDw~iU( zmU3V+$)2yvkiEl+^xYh?J}!n_^IT2)YwyxaCjcTUR*_E=IG;MK zdn}-494+V%qLl#=WP)Q2eZ$oV9o%gIB1wfXW1ceUd;J)E+>L0zHB$AhoG*UD3y_Uf z!$1B1ypvJ;zxMy=|E2$1$2-$MpZhFHJd?X|cz@ded|st_9{)bC8<(zIAF^h(rw93; zPV9eovj3aEvmf^7=uKI{GljV$|7rj8(Y1Jv|313)zi#36OV@=g3=P@3Fr51)3qqE7 zdf5Kozl^Pei>;%Bt*!lNC&y7P4i5GX4tArQ9G#sV99>*SJ386gx;Q#IINJaDmaT6s zqD;AO_^0{#2>to8|25LIhxA?;CBozVX)=HQ;cu`18h5PMt_=NWXe_+c5a-_hr^)^; zERy@Me}yjE625fJx+P23{WJEI?&aCewmjaSHvDg~5C1dvUwt*|pMku|8^c9{Ab{Q+;abn{Od)j^|#1rH~-_1|NZUa=OZp|`5ytgB3Azu`k#mY t&!1QSb)EBg-?)$a^Wx$p{`{=0`}d#!{O;!?_UG#5Nprvcy#L?t{eQJRUQYl3 literal 0 HcmV?d00001 diff --git a/dynamo/multivelo/neural_nets/dir1.pt b/dynamo/multivelo/neural_nets/dir1.pt new file mode 100644 index 0000000000000000000000000000000000000000..d67c03f0d8a5695d12a70cba7c866fdee64f4f2e GIT binary patch literal 25884 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 literal 0 HcmV?d00001 diff --git a/dynamo/multivelo/neural_nets/dir2_m1.pt b/dynamo/multivelo/neural_nets/dir2_m1.pt new file mode 100644 index 0000000000000000000000000000000000000000..7dc053f804d6fb8dada32423d03db038329f7aa6 GIT binary patch literal 238912 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 p90VJk=(Rx$+*Kg1mo2* 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�U3#ILeRf{nFfQSy72z>Up=<-#JXjUAVu0>eE86 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 literal 0 HcmV?d00001 diff --git a/dynamo/multivelo/old_MultiVelocity.py b/dynamo/multivelo/old_MultiVelocity.py new file mode 100644 index 00000000..44dc1356 --- /dev/null +++ b/dynamo/multivelo/old_MultiVelocity.py @@ -0,0 +1,1401 @@ +from anndata import AnnData +import matplotlib.pyplot as plt +from multiprocessing import Pool +from mudata import MuData +import numpy as np +import os +from os import PathLike +import pandas as pd +import scanpy as sc +from scipy.sparse import coo_matrix, csr_matrix, hstack, issparse +from scipy.sparse.linalg import svds + +from typing import ( + Dict, + List, + Literal, + Optional, + Tuple, + Union +) + +import warnings + +# Import from dynamo +from ..dynamo_logger import ( + LoggerManager, + main_exception, + main_info, +) + +# Imports from MultiDynamo +from .ChromatinVelocity import ChromatinVelocity +from .MultiConfiguration import MDKM +from .pyWNN import pyWNN + + +# Static function +# direction_cosine +def direction_cosine(args): + i, j, expression_mtx, velocity_mtx = args + + if i == j: + return i, j, -1 + + delta_ij = None + if isinstance(expression_mtx, csr_matrix): + delta_ij = (expression_mtx.getrow(j) - expression_mtx.getrow(i)).toarray().flatten() + elif isinstance(expression_mtx, np.ndarray): + delta_ij = (expression_mtx[j, :] - expression_mtx[i, :]).flatten() + else: + main_exception(f'Expression matrix is instance of class {type(expression_mtx)}') + + vel_i = velocity_mtx.getrow(i).toarray().flatten() + + dot_product = np.dot(delta_ij, vel_i) # vel_i.dot(delta_ij) + magnitude_vel_i = np.linalg.norm(vel_i) + magnitude_delta_ij = np.linalg.norm(delta_ij) + + if magnitude_vel_i != 0 and magnitude_delta_ij != 0: + cosine_similarity = dot_product / (magnitude_vel_i * magnitude_delta_ij) + else: + # One of velocity or delta_ij is zero, so can't compute a cosine, we'll just set to + # lowest possible value (-1) + cosine_similarity = -1 + + return i, j, cosine_similarity + + +# get_connectivities - patterned after function in scVelo +def get_connectivities(adata: AnnData, + mode: str = 'connectivities', + n_neighbors: int = None, + recurse_neighbors: bool = False + ) -> Union[csr_matrix, None]: + if 'neighbors' in adata.uns.keys(): + C = get_neighbors(adata=adata, mode=mode) + if n_neighbors is not None and n_neighbors < get_n_neighbors(adata=adata): + if mode == 'connectivities': + C = select_connectivities(C, n_neighbors) + else: + C = select_distances(C, n_neighbors) + connectivities = C > 0 + with warnings.catch_warnings(): + warnings.simplefilter('ignore') + connectivities.setdiag(1) + if recurse_neighbors: + connectivities += connectivities.dot(connectivities * 0.5) + connectivities.data = np.clip(connectivities.data, 0, 1) + connectivities = connectivities.multiply(1.0 / connectivities.sum(1)) + return connectivities.tocsr().astype(np.float32) + else: + return None + + +# get_n_neighbors - lifted from scVelo +def get_n_neighbors(adata: AnnData) -> int: + return adata.uns.get('neighbors', {}).get('params', {}).get('n_neighbors', 0) + + +def get_neighbors(adata: AnnData, + mode: str = 'distances'): + if hasattr(adata, 'obsp') and mode in adata.obsp: + return adata.obsp[mode] + elif 'neighbors' in adata.uns.keys() and mode in adata.uns['neighbors']: + return adata.uns['neighbors'][mode] + else: + main_exception(f'The selected mode {mode} is not valid.') + + +def lifted_chromatin_velocity(arg): + i, j, chromatin_state, cosines, expression_mtx, rna_velocity = arg + + if i == j: + main_exception('A cell should never be its own integral neighbor.') + + # Compute change in chromatin state + delta_c_ij = None + if isinstance(chromatin_state, csr_matrix): + delta_c_ij = (chromatin_state.getrow(j) - chromatin_state.getrow(i)).toarray().flatten() + elif isinstance(chromatin_state, np.ndarray): + delta_c_ij = (chromatin_state[j, :] - chromatin_state[i, :]).flatten() + else: + main_exception(f'Chromatin state matrix is instance of class {type(chromatin_state)}') + + # Retrieve cosine + cosine = cosines[i, j] + + # Compute change in RNA expression + delta_s_ij = None + if isinstance(expression_mtx, csr_matrix): + delta_s_ij = (expression_mtx.getrow(j) - expression_mtx.getrow(i)).toarray().flatten() + elif isinstance(expression_mtx, np.ndarray): + delta_s_ij = (expression_mtx[j, :] - expression_mtx[i, :]).flatten() + else: + main_exception(f'RNA expression matrix is instance of class {type(expression_mtx)}') + + # Compute norms + norm_delta_s_ij = np.linalg.norm(delta_s_ij) + norm_rna_velocity = np.linalg.norm(rna_velocity.toarray()) + + if norm_delta_s_ij != 0: + chromatin_velocity = (norm_rna_velocity * cosine / norm_delta_s_ij) * delta_c_ij + else: + chromatin_velocity = np.zeros(chromatin_state.shape[1]) + + return i, chromatin_velocity + + +def regression(c, + u, + s, + ss, + us, + uu, + fit_args, + mode, + gene): + c_90 = np.percentile(c, 90) + u_90 = np.percentile(u, 90) + s_90 = np.percentile(s, 90) + + low_quality = (c_90 == 0 or s_90 == 0 or u_90 == 0) + + if low_quality: + # main_info(f'Skipping low quality gene {gene}.') + return np.zeros(len(u)), np.zeros(len(u)), 0, 0, np.inf + + cvc = ChromatinVelocity(c, + u, + s, + ss, + us, + uu, + fit_args, + gene=gene) + + if cvc.low_quality: + return np.zeros(len(u)), np.zeros(len(u)), 0, 0, np.inf + + if mode == 'deterministic': + cvc.compute_deterministic() + elif mode == 'stochastic': + cvc.compute_stochastic() + velocity = cvc.get_velocity(mode=mode) + gamma = cvc.get_gamma(mode=mode) + r2 = cvc.get_r2(mode=mode) + loss = cvc.get_loss(mode=mode) + variance_velocity = (None if mode == 'deterministic' + else cvc.get_variance_velocity()) + return velocity, variance_velocity, gamma, r2, loss + + +def select_connectivities(connectivities, + n_neighbors=None): + C = connectivities.copy() + n_counts = (C > 0).sum(1).A1 if issparse(C) else (C > 0).sum(1) + n_neighbors = ( + n_counts.min() if n_neighbors is None else min(n_counts.min(), + n_neighbors) + ) + rows = np.where(n_counts > n_neighbors)[0] + cumsum_neighs = np.insert(n_counts.cumsum(), 0, 0) + dat = C.data + + for row in rows: + n0, n1 = cumsum_neighs[row], cumsum_neighs[row + 1] + rm_idx = n0 + dat[n0:n1].argsort()[::-1][n_neighbors:] + dat[rm_idx] = 0 + + C.eliminate_zeros() + return C + + +def select_distances(dist, + n_neighbors: int = None): + D = dist.copy() + n_counts = (D > 0).sum(1).A1 if issparse(D) else (D > 0).sum(1) + n_neighbors = ( + n_counts.min() if n_neighbors is None else min(n_counts.min(), n_neighbors) + ) + rows = np.where(n_counts > n_neighbors)[0] + cumsum_neighs = np.insert(n_counts.cumsum(), 0, 0) + dat = D.data + + for row in rows: + n0, n1 = cumsum_neighs[row], cumsum_neighs[row + 1] + rm_idx = n0 + dat[n0:n1].argsort()[n_neighbors:] + dat[rm_idx] = 0 + + D.eliminate_zeros() + return D + + +# smooth_scale - lifted from MultiVelo +def smooth_scale(conn, + vector): + max_to = np.max(vector) + min_to = np.min(vector) + v = conn.dot(vector.T).T + max_from = np.max(v) + min_from = np.min(v) + res = ((v - min_from) * (max_to - min_to) / (max_from - min_from)) + min_to + return res + + +# top_n_sparse - lifted from MultiVelo +def top_n_sparse(conn, n): + conn_ll = conn.tolil() + for i in range(conn_ll.shape[0]): + row_data = np.array(conn_ll.data[i]) + row_idx = np.array(conn_ll.rows[i]) + new_idx = row_data.argsort()[-n:] + top_val = row_data[new_idx] + top_idx = row_idx[new_idx] + conn_ll.data[i] = top_val.tolist() + conn_ll.rows[i] = top_idx.tolist() + conn = conn_ll.tocsr() + idx1 = conn > 0 + idx2 = conn > 0.25 + idx3 = conn > 0.5 + conn[idx1] = 0.25 + conn[idx2] = 0.5 + conn[idx3] = 1 + conn.eliminate_zeros() + return conn + + +class MultiVelocity: + def __init__(self, + mdata: MuData, + cosine_similarities: csr_matrix = None, + cre_dict: Dict = None, + include_gene_body: bool = False, + integral_neighbors: Dict = None, + linkage_fn: str = 'feature_linkage.bedpe', # in 'outs/analysis/feature_linkage' directory + linkage_method: Literal['cellranger', 'cicero', 'scenic+'] = 'cellranger', + max_peak_dist: int = 10000, + min_corr: float = 0.5, + neighbor_method: Literal['multivi', 'wnn'] = 'multivi', + nn_dist: csr_matrix = None, + nn_idx: csr_matrix = None, + peak_annot_fn: str = 'peak_annotation.tsv', # in 'outs' directory + promoter_dict: Dict = None + ): + # Initialize instance variables + self.mdata = mdata.copy() if mdata is not None else None + + self._cre_dict = cre_dict.copy() if cre_dict is not None else None + + self.cosine_similarities = cosine_similarities.copy() if cosine_similarities is not None else None + + self.include_gene_body = include_gene_body + + self.integral_neighbors = integral_neighbors.copy() if integral_neighbors is not None else None + + self.linkage_fn = linkage_fn + + self.linkage_method = linkage_method + + self.max_peak_dist = max_peak_dist + + self.min_corr = min_corr + + self.neighbor_method = neighbor_method + + self.nn_dist = nn_dist.copy() if nn_dist is not None else None + + self.nn_idx = nn_idx.copy() if nn_idx is not None else None + + self.peak_annot_fn = peak_annot_fn + + self._promoter_dict = promoter_dict.copy() if promoter_dict is not None else None + + def atac_elements(self): + return self.mdata['atac'].var_names.tolist() + + def compute_linkages(self) -> None: + if self.linkage_method == 'cellranger': + self.compute_linkages_via_cellranger() + elif self.linkage_method == 'cicero': + self.compute_linkages_via_cicero() + elif self.linkage_method == 'scenic+': + self.compute_linkages_via_scenicplus() + else: + main_exception(f'Unrecognized method to compute linkages ({self.linkage_method}) requested.') + + def compute_linkages_via_cellranger(self) -> None: + # This reads the cellranger-arc 'feature_linkage.bedpe' and 'peak_annotation.tsv' files + # to extract dictionaries attributing cis-regulatory elements with specific genes + main_info('Computing linkages via cellranger ...') + linkage_logger = LoggerManager.gen_logger('compute_linkages_via_cellranger') + linkage_logger.log_time() + + # Confirm that this is matched ATAC- and RNA-seq data + if not self.mdata.mod['atac'].uns[MDKM.MATCHED_ATAC_RNA_DATA_KEY]: + main_exception('Cannot use cellranger to compute CRE linkages for UNMATCHED data') + + outs_data_path = os.path.join(self.mdata.mod['atac'].uns['base_data_path'], 'outs') + # Confirm that the base path to the 'outs' directory exists + if not os.path.exists(outs_data_path): + main_exception(f'The path to the 10X outs directory ({outs_data_path}) does not exist.') + + # Read annotations + peak_annot_path = os.path.join(outs_data_path, self.peak_annot_fn) + if not os.path.exists(peak_annot_path): + main_exception(f'The path to the peak annotation file ({peak_annot_path}) does not exist.') + + corr_dict, distal_dict, gene_body_dict, promoter_dict = {}, {}, {}, {} + with open(peak_annot_path) as f: + # Scan the header to determine version of CellRanger used in making the peak annotation file + header = next(f) + fields = header.split('\t') + + # Peak annotation should contain 4 columns for version 1.X of CellRanger and 6 columns for + # version 2.X + if len(fields) not in [4, 6]: + main_exception('Peak annotation file should contain 4 columns (CellRanger ARC 1.0.0) ' + + 'or 6 columns (CellRanger ARC 2.0.0)') + else: + offset = 0 if len(fields) == 4 else 2 + + for line in f: + fields = line.rstrip().split('\t') + + peak = f'{fields[0]}:{fields[1]}-{fields[2]}' if offset else \ + f"{fields[0].split('_')[0]}:{fields[0].split('_')[1]}-{fields[0].split('_')[2]}" + + if fields[1 + offset] == '': + continue + + genes, dists, types = \ + fields[1 + offset].split(';'), fields[2 + offset].split(';'), fields[3 + offset].split(';') + + for gene, dist, annot in zip(genes, dists, types): + if annot == 'promoter': + promoter_dict.setdefault(gene, []).append(peak) + elif annot == 'distal': + if dist == '0': + gene_body_dict.setdefault(gene, []).append(peak) + else: + distal_dict.setdefault(gene, []).append(peak) + + # Read linkages + linkage_path = os.path.join(outs_data_path, 'analysis', 'feature_linkage', self.linkage_fn) + if not os.path.exists(linkage_path): + main_exception(f'The path to the linkage file ({linkage_path}) does not exist.') + with open(linkage_path) as f: + for line in f: + fields = line.rstrip().split('\t') + + # Form proper peak coordinates + peak_1, peak_2 = f'{fields[0]}:{fields[1]}-{fields[2]}', f'{fields[3]}:{fields[4]}-{fields[5]}' + + # Split the gene pairs + genes_annots_1, genes_annots_2 = \ + fields[6].split('><')[0][1:].split(';'), fields[6].split('><')[1][:-1].split(';') + + # Extract correlation + correlation = float(fields[7]) + + # Extract distance between peaks + dist = float(fields[11]) + + if fields[12] == 'peak-peak': + for gene_annot_1 in genes_annots_1: + gene_1, annot_1 = gene_annot_1.split('_') + for gene_annot_2 in genes_annots_2: + gene_2, annot_2 = gene_annot_2.split('_') + + if (((annot_1 == 'promoter') != (annot_2 == 'promoter')) and + ((gene_1 == gene_2) or (dist < self.max_peak_dist))): + gene = gene_1 if annot_1 == 'promoter' else gene_2 + + if (peak_2 not in corr_dict.get(gene, []) and annot_1 == 'promoter' and + (gene_2 not in gene_body_dict or peak_2 not in gene_body_dict.get(gene_2, []))): + corr_dict.setdefault(gene, [[], []])[0].append(peak_2) + corr_dict[gene][1].append(correlation) + + if (peak_1 not in corr_dict.get(gene, []) and annot_2 == 'promoter' and + (gene_1 not in gene_body_dict or peak_1 not in gene_body_dict.get(gene_1, []))): + corr_dict.setdefault(gene, [[], []])[0].append(peak_1) + corr_dict[gene][1].append(correlation) + + elif fields[12] == 'peak-gene': + gene_2 = genes_annots_2[0] + for gene_annot_1 in genes_annots_1: + gene_1, annot_1 = gene_annot_1.split('_') + + if (gene_1 == gene_2) or (dist < self.max_peak_dist): + gene = gene_1 + + if (peak_1 not in corr_dict.get(gene, []) and annot_1 != 'promoter' and + (gene_1 not in gene_body_dict or peak_1 not in gene_body_dict.get(gene_1, []))): + corr_dict.setdefault(gene, [[], []])[0].append(peak_1) + corr_dict[gene][1].append(correlation) + + elif fields[12] == 'gene-peak': + gene_1 = genes_annots_1[0] + for gene_annot_2 in genes_annots_2: + gene_2, annot_2 = gene_annot_2.split('_') + + if (gene_1 == gene_2) or (dist < self.max_peak_dist): + gene = gene_1 + + if (peak_2 not in corr_dict.get(gene, []) and annot_2 != 'promoter' and + (gene_2 not in gene_body_dict or peak_2 not in gene_body_dict.get(gene_2, []))): + corr_dict.setdefault(gene, [[], []])[0].append(peak_2) + corr_dict[gene][1].append(correlation) + + cre_dict = {} + gene_dict = promoter_dict + promoter_genes = list(promoter_dict.keys()) + + for gene in promoter_genes: + if self.include_gene_body: # add gene-body peaks + if gene in gene_body_dict: + for peak in gene_body_dict[gene]: + if peak not in gene_dict[gene]: + gene_dict[gene].append(peak) + cre_dict[gene] = [] + if gene in corr_dict: # add enhancer peaks + for j, peak in enumerate(corr_dict[gene][0]): + corr = corr_dict[gene][1][j] + if corr > self.min_corr: + if peak not in gene_dict[gene]: + gene_dict[gene].append(peak) + cre_dict[gene].append(peak) + + # Update the enhancer and promoter dictionaries + self._update_cre_and_promoter_dicts(cre_dict=cre_dict, + promoter_dict=promoter_dict) + + linkage_logger.finish_progress(progress_name='compute_linkages_via_cellranger') + + def compute_linkages_via_cicero(self) -> None: + # TODO: Use cicero to filter significant linkages + pass + + def compute_linkages_via_scenicplus(self) -> None: + # TODO: Use scenicplus to filter significant linkages + pass + + def compute_neighbors(self, + atac_lsi_key: str = MDKM.ATAC_OBSM_LSI_KEY, + lr: float = 0.0001, + max_epochs: int = 10, # 10 for debug mode 500 for release, + mv_algorithm: bool = True, + n_comps_atac: int = 20, + n_comps_rna: int = 20, + n_neighbors: int = 20, + pc_key: str = MDKM.ATAC_OBSM_PC_KEY, + random_state: int = 42, + rna_pca_key: str = MDKM.RNA_OBSM_PC_KEY, + scale_factor: float = 1e4, + use_highly_variable: bool = False + ) -> None: + if self.neighbor_method == 'multivi': + self.compute_neighbors_via_multivi( + lr=lr, + max_epochs=max_epochs) + elif self.neighbor_method == 'wnn': + self.weighted_nearest_neighbors( + atac_lsi_key=atac_lsi_key, + n_components_atac=n_comps_atac, + n_components_rna=n_comps_rna, + nn=n_neighbors, + random_state=random_state, + rna_pca_key=rna_pca_key, + use_highly_variable=use_highly_variable) + else: + main_exception(f'Unrecognized method to compute neighbors ({self.neighbor_method}) requested.') + + def compute_neighbors_via_multivi( + self, + lr: float = 0.0001, + max_epochs: int = 500, + n_comps: int = 20, + n_neighbors: int = 20, + ) -> None: + import scvi + main_info('Computing nearest neighbors in latent representation generated by MULTIVI ...', indent_level=1) + nn_logger = LoggerManager.gen_logger('compute_nn_via_mvi') + nn_logger.log_time() + + # Extract the ATAC-seq and RNA-seq portions + atac_adata, rna_adata = self.mdata.mod['atac'], self.mdata.mod['rna'] + n_peaks, n_genes = atac_adata.n_vars, rna_adata.n_vars + + # Ensure that the ATAC- and RNA-seq portions have same number of cells + assert (atac_adata.n_obs == rna_adata.n_obs) + + # Restructure the data into MULTIVI format - we do not perform TF-IDF transformation + # ... X - counts or normalized counts??? + tmp_adata_X = hstack([rna_adata.layers[MDKM.RNA_COUNTS_LAYER], atac_adata.layers[MDKM.ATAC_COUNTS_LAYER]]) + + # ... obs + tmp_adata_obs = rna_adata.obs.copy() + + # ... var + tmp_adata_var = pd.concat([rna_adata.var.copy(), atac_adata.var.copy()], join='inner', axis=0) + + tmp_adata = AnnData(X=tmp_adata_X.copy(), obs=tmp_adata_obs, var=tmp_adata_var) + tmp_adata.layers['counts'] = tmp_adata.X.copy() + + # Get the number of cells + num_cells = tmp_adata.n_obs + + # Generate a random permutation of cell indices + cell_indices = np.random.permutation(num_cells) + + # Determine the split point + split_point = num_cells // 2 + + # Split indices into two groups + cell_indices_1 = cell_indices[:split_point] + cell_indices_2 = cell_indices[split_point:] + + # Subset the AnnData object into two disjoint AnnData objects + tmp_adata_1 = tmp_adata[cell_indices_1].copy() + tmp_adata_1.obs['modality'] = 'first_set' + tmp_adata_2 = tmp_adata[cell_indices_2].copy() + tmp_adata_2.obs['modality'] = 'second_set' + + tmp_adata = scvi.data.organize_multiome_anndatas(tmp_adata_1, tmp_adata_2) + + # Run MULTIVI + # ... setup AnnData object for scvi-tools + main_info('Setting up combined data for MULTIVI', indent_level=2) + scvi.model.MULTIVI.setup_anndata(tmp_adata, batch_key='modality') + + # ... instantiate the SCVI model + main_info('Instantiating MULTIVI model', indent_level=2) + multivi_model = scvi.model.MULTIVI(adata=tmp_adata, n_genes=n_genes, n_regions=n_peaks, n_latent=n_comps) + multivi_model.view_anndata_setup() + + # ... train the model + main_info('Training MULTIVI model', indent_level=2) + multivi_model.train(max_epochs=max_epochs, lr=lr) + + # Extract latent representation + main_info('extracting latent representation for ATAC-seq', indent_level=3) + atac_adata.obsm['X_mvi_latent'] = multivi_model.get_latent_representation().copy() + rna_adata.obsm['X_mvi_latent'] = multivi_model.get_latent_representation().copy() + + # Compute nearest neighbors + main_info('Computing nearest neighbors in MVI latent representation', indent_level=2) + sc.pp.neighbors(rna_adata, n_neighbors=n_neighbors, n_pcs=n_comps, use_rep='X_mvi_latent') + + # Redundantly copy over to atac-seq modality + atac_adata.obsp['distances'] = rna_adata.obsp['distances'].copy() + atac_adata.obsp['connectivities'] = rna_adata.obsp['connectivities'].copy() + atac_adata.uns['neighbors'] = rna_adata.uns['neighbors'].copy() + + # Extract the matrix storing the distances between each cell and its neighbors + cx = coo_matrix(rna_adata.obsp['distances'].copy()) + + # the number of cells + cells = rna_adata.obsp['distances'].shape[0] + + # define the shape of our final results + # and make the arrays that will hold the results + new_shape = (cells, n_neighbors) + nn_dist = np.zeros(shape=new_shape) + nn_idx = np.zeros(shape=new_shape) + + # new_col defines what column we store data in our result arrays + new_col = 0 + + # loop through the distance matrices + for i, j, v in zip(cx.row, cx.col, cx.data): + # store the distances between neighbor cells + nn_dist[i][new_col % n_neighbors] = v + + # for each cell's row, store the row numbers of its neighbor cells + # (1-indexing instead of 0- is a holdover from R multimodalneighbors()) + nn_idx[i][new_col % n_neighbors] = int(j) + 1 + + new_col += 1 + + # Add index and distance to the MultiomeVelocity object + self.nn_idx = nn_idx + self.nn_dist = nn_dist + + # Copy the subset AnnData scRNA-seq and scATAC-seq objects back into the MultiomeVelocity object + self.mdata.mod['atac'] = atac_adata.copy() + self.mdata.mod['rna'] = rna_adata.copy() + + nn_logger.finish_progress(progress_name='compute_nn_via_mvi') + + def compute_second_moments( + self, + adjusted: bool = False + ) -> Tuple[csr_matrix, csr_matrix, csr_matrix]: + # Extract transcriptome + rna_adata = self.mdata.mod['rna'] + + # Obtain connectivities matrix + connectivities = get_connectivities(rna_adata) + + s, u = (csr_matrix(rna_adata.layers[MDKM.RNA_SPLICED_LAYER]), + csr_matrix(rna_adata.layers[MDKM.RNA_UNSPLICED_LAYER])) + if s.shape[0] == 1: + s, u = s.T, u.T + Mss = csr_matrix.dot(connectivities, s.multiply(s)).astype(np.float32).A + Mus = csr_matrix.dot(connectivities, s.multiply(u)).astype(np.float32).A + Muu = csr_matrix.dot(connectivities, u.multiply(u)).astype(np.float32).A + if adjusted: + Mss = 2 * Mss - rna_adata.layers[MDKM.RNA_FIRST_MOMENT_SPLICED_LAYER].reshape(Mss.shape) + Mus = 2 * Mus - rna_adata.layers[MDKM.RNA_FIRST_MOMENT_UNSPLICED_LAYER].reshape(Mus.shape) + Muu = 2 * Muu - rna_adata.layers[MDKM.RNA_FIRST_MOMENT_UNSPLICED_LAYER].reshape(Muu.shape) + return Mss, Mus, Muu + + def compute_velocities(self, + linkage_method: Optional[Literal['cellranger', 'cicero', 'scenic+']] = 'cellranger', + mode: Literal['deterministic', 'stochastic'] = 'deterministic', + neighbor_method: Literal['multivi', 'wnn'] = 'wnn', + num_processes: int = 6) -> None: + if linkage_method is not None: + self.linkage_method = linkage_method + + if neighbor_method is not None: + self.neighbor_method = neighbor_method + + if (self.linkage_method is None) or (self.neighbor_method is None): + main_exception('linkage_method and neighbor_method mus be specified.') + + # Compute linkages + self.compute_linkages() + + # Compute neighbors + self.compute_neighbors() + + # Compute smoother accessibility + self.knn_smoothed_chrom() + + # Compute transcriptomic velocity + self.transcriptomic_velocity(mode=mode, num_processes=num_processes) + + # Compute lift of transcriptomic velocity + self.lift_transcriptomic_velocity(num_processes=num_processes) + + def find_cell_along_integral_curve(self, + num_processes: int = 6, + plot_dir_cosines: bool = False): + # Extract the ATAC- and RNA-seq portions + atac_adata, rna_adata = self.mdata.mod['atac'], self.mdata.mod['rna'] + + expression_mtx = rna_adata.layers[MDKM.RNA_FIRST_MOMENT_SPLICED_LAYER] + velocity_mtx = rna_adata.layers[MDKM.RNA_SPLICED_VELOCITY_LAYER] + + # Extract connectivities + connectivities = get_connectivities(rna_adata) + + # Get non-zero indices from connectivities + nonzero_idx = connectivities.nonzero() + + # Prepare argument list for parallel processing + args_list = [(i, j, expression_mtx, velocity_mtx) + for i, j in zip(nonzero_idx[0], nonzero_idx[1])] + + # Use multiprocessing to compute the results + with Pool(processes=num_processes) as pool: + results = pool.map(direction_cosine, args_list) + + # Convert results to sparse matrix + data = [cosines for _, _, cosines in results] + i_indices = [i_idx for i_idx, _, _ in results] + j_indices = [j_idx for _, j_idx, _ in results] + direction_cosines = csr_matrix((data, (i_indices, j_indices)), shape=connectivities.shape) + + # Find nearest neighbor along integral curve + integral_neighbors = direction_cosines.argmax(axis=1).A.flatten() + + if plot_dir_cosines: + # Summarize statistics about the best direction cosines + max_dir_cosines = direction_cosines.max(axis=1).A.flatten() + plt.hist(max_dir_cosines, bins=25) + plt.title('Frequencies of direction cosines') + plt.xlabel('Direction Cosines') + plt.ylabel('Frequency') + plt.show() + + # Save the results in this class + # TODO: Consider whether to add to AnnData objects + self.cosine_similarities = direction_cosines + self.integral_neighbors = {int(idx): int(integral_neighbor) + for idx, integral_neighbor in enumerate(integral_neighbors)} + + @classmethod + def from_mdata(cls, + mdata: MuData): + # Deep copy MuData object for export + atac_adata, rna_adata = mdata.mod['atac'].copy(), mdata.mod['rna'].copy() + + # ... from atac + # ... bit of kludge: dictionaries appear to require type casting after deserialization + deser_cre_dict = atac_adata.uns['cre_dict'].copy() + cre_dict = {} + for gene, cre_list in deser_cre_dict.items(): + cre_dict[str(gene)] = [str(cre) for cre in cre_list] + # ... bit of kludge: dictionaries appear to require type casting after deserialization + deser_promoter_dict = atac_adata.uns['promoter_dict'] + promoter_dict = {} + for gene, promoter_list in deser_promoter_dict.items(): + promoter_dict[str(gene)] = [str(promoter) for promoter in promoter_list] + + multi_dynamo_kwargs = atac_adata.uns['multi_dynamo_kwargs'] + include_gene_body = multi_dynamo_kwargs.get('include_gene_body', False) + linkage_fn = multi_dynamo_kwargs.get('linkage_fn', 'feature_linkage.bedpe') + linkage_method = multi_dynamo_kwargs.get('linkage_method', 'cellranger') + max_peak_dist = multi_dynamo_kwargs.get('max_peak_dist', 10000) + min_corr = multi_dynamo_kwargs.get('min_corr', 0.5) + peak_annot_fn = multi_dynamo_kwargs.get('min_corr', 'peak_annotation.tsv') + + # ... from rna + nn_dist = rna_adata.obsm['multi_dynamo_nn_dist'] + nn_idx = rna_adata.obsm['multi_dynamo_nn_idx'] + + cosine_similarities = rna_adata.obsp['cosine_similarities'] + # ... bit of kludge: dictionaries appear to require type casting after deserialization + integral_neighbors = {int(k): int(v) for k,v in rna_adata.uns['integral_neighbors'].items()} + + multi_dynamo_kwargs = rna_adata.uns['multi_dynamo_kwargs'] + neighbor_method = multi_dynamo_kwargs.get('neighbor_method', 'multivi') + + multi_velocity = cls(mdata=mdata, + cre_dict=cre_dict, + cosine_similarities=cosine_similarities, + include_gene_body=include_gene_body, + integral_neighbors=integral_neighbors, + linkage_fn=linkage_fn, + linkage_method=linkage_method, + max_peak_dist=max_peak_dist, + min_corr=min_corr, + nn_dist=nn_dist, + nn_idx=nn_idx, + neighbor_method=neighbor_method, + peak_annot_fn=peak_annot_fn, + promoter_dict=promoter_dict) + + return multi_velocity + + def get_cre_dict(self): + return self._cre_dict + + def get_mdata(self): + return self.mdata + + def get_nn_dist(self): + return self.nn_dist + + def get_nn_idx(self): + return self.nn_idx + + def get_promoter_dict(self): + return self._promoter_dict + + # knn_smoothed_chrom - method adapted from MultiVelo + def knn_smoothed_chrom(self, + nn: int = 20 + ) -> None: + # Consistency checks + nn_idx = None + if self.nn_idx is None: + main_exception('Missing KNN index matrix. Try calling compute_neighbors first.') + else: + nn_idx = self.nn_idx + + nn_dist = None + if self.nn_dist is None: + main_exception('Missing KNN distance matrix. Try calling compute_neighbors first.') + else: + nn_dist = self.nn_dist + + atac_adata, rna_adata = self.mdata.mod['atac'], self.mdata.mod['rna'] + n_cells = atac_adata.n_obs + + if (nn_idx.shape[0] != n_cells) or (nn_dist.shape[0] != n_cells): + main_exception('Number of rows of KNN indices does not equal to number of cells.') + + X = coo_matrix(([], ([], [])), shape=(n_cells, 1)) + from umap.umap_ import fuzzy_simplicial_set + conn, sigma, rho, dists = fuzzy_simplicial_set(X=X, + n_neighbors=nn, + random_state=None, + metric=None, + knn_indices=nn_idx-1, + knn_dists=nn_dist, + return_dists=True) + + conn = conn.tocsr().copy() + n_counts = (conn > 0).sum(1).A1 + if nn is not None and nn < n_counts.min(): + conn = top_n_sparse(conn, nn) + conn.setdiag(1) + conn_norm = conn.multiply(1.0 / conn.sum(1)).tocsr() + + # Compute first moment of chromatin accessibility + atac_adata.layers[MDKM.RNA_FIRST_MOMENT_CHROM_LAYER] = \ + csr_matrix.dot(conn_norm, atac_adata.layers['counts']).copy() + + # Overwrite ATAC- and RNA-seq connectivities + atac_adata.obsp['connectivities'] = conn.copy() + rna_adata.obsp['connectivities'] = conn.copy() + + self.mdata.mod['atac'] = atac_adata.copy() + self.mdata.mod['rna'] = rna_adata.copy() + + def lift_transcriptomic_velocity(self, + num_processes: int = 6): + # Compute integral neighbors + main_info('Starting computation of integral neighbors ...') + self.find_cell_along_integral_curve(num_processes=num_processes) + + # Extract the ATAC- and RNA-seq data + atac_adata, rna_adata = self.mdata.mod['atac'], self.mdata.mod['rna'] + + # Retrieve specified layer for chromatin state + chromatin_state = atac_adata.layers[MDKM.ATAC_TFIDF_LAYER] + + cosine_similarities = None + if self.cosine_similarities is None: + main_exception('Please compute integral neighbors before calling lift_transcriptomic_velocity.') + else: + cosine_similarities = self.cosine_similarities + + # Retrieve specified layer for expression matrix + expression_mtx = rna_adata.layers[MDKM.RNA_FIRST_MOMENT_SPLICED_LAYER] + + integral_neighbors = None + if self.integral_neighbors is None: + main_exception('Please compute integral neighbors before calling lift_transcriptomic_velocity.') + else: + integral_neighbors = self.integral_neighbors + + # Retrieve specified layer for the velocity matrix + velocity_mtx = rna_adata.layers[MDKM.RNA_SPLICED_VELOCITY_LAYER] + + # Prepare argument list for parallel processing + args_list = [(i, j, chromatin_state, cosine_similarities, expression_mtx, velocity_mtx[i, :]) + for i, j in integral_neighbors.items()] + + # Use multiprocessing to compute the results + with Pool(processes=num_processes) as pool: + results = pool.map(lifted_chromatin_velocity, args_list) + + # Convert results to sparse matrix + chromatin_velocity_mtx = np.zeros(chromatin_state.shape) + for i, chromatin_velocity in results: + chromatin_velocity_mtx[i, :] = chromatin_velocity + + atac_adata.layers[MDKM.ATAC_CHROMATIN_VELOCITY_LAYER] = chromatin_velocity_mtx + + # Copy the scATAC-seq AnnData object into the MultiomeVelocity object + self.mdata.mod['atac'] = atac_adata.copy() + + def _restrict_dicts_to_gene_list(self, + gene_list: List[str], + cre_dict: Dict[str, List[str]] = None, + promoter_dict: Dict[str, List[str]] = None + ) -> Tuple[List[str], List[str], Dict[str, List[str]], Dict[str, List[str]]]: + # Elements present in scATAC-seq data + present_elements = self.atac_elements() + + if len(gene_list) == 0: + main_exception('Require non-trivial gene_list for _restrict_to_gene_list.') + + if len(cre_dict) == 0 or len(promoter_dict) == 0: + main_exception('Require non-trivial enhancer and promoter dicts for _restrict_to_gene_list.') + + # Elements associated to genes in gene_list and present in scATAC-seq data + shared_elements = [] + + # Dictionary from gene to element list for all genes present in gene_list and with + # corresponding elements in enhancer dicts + shared_cre_dict = {} + for gene, element_list in cre_dict.items(): + if gene in gene_list: + shared_elements_for_gene =\ + [element for element in element_list if element in present_elements] + shared_elements_for_gene = list(set(shared_elements_for_gene)) + + shared_elements += shared_elements_for_gene + shared_cre_dict[gene] = shared_elements_for_gene + + # Add all promoters for genes in gene_list + shared_promoter_dict = {} + for gene, element_list in promoter_dict.items(): + if gene in gene_list: + shared_elements_for_gene = \ + [element for element in element_list if element in present_elements] + shared_elements_for_gene = list(set(shared_elements_for_gene)) # Bit pedantic ... + + shared_elements += shared_elements_for_gene + shared_promoter_dict[gene] = shared_elements_for_gene + + # Make elements into unique list + shared_elements = list(set(shared_elements)) + + # Determine which genes actually have elements present in the scATAC-seq data + all_dict_genes = list(set(list(shared_cre_dict.keys()) + list(shared_promoter_dict.keys()))) + shared_genes = [] + for gene in all_dict_genes: + enhancers_for_gene = len(shared_cre_dict.get(gene, [])) > 0 + + promoters_for_gene = len(shared_promoter_dict.get(gene, [])) > 0 + + if enhancers_for_gene or promoters_for_gene: + shared_genes.append(gene) + + # Clean up trivial entries in dicts + if not enhancers_for_gene and gene in shared_cre_dict: + del shared_cre_dict[gene] + + if not promoters_for_gene and gene in shared_promoter_dict: + del shared_promoter_dict[gene] + + shared_genes = list(set(shared_genes)) + + return shared_elements, shared_genes, shared_cre_dict, shared_promoter_dict + + def restrict_to_gene_list(self, + gene_list: List[str] = None, + subset: bool = False) -> Tuple[List[str], List[str]]: + # Extract genes from scRNA-seq data + rna_genes = self.rna_genes() + + if gene_list is None: + # If no gene_list offered, then use the genes found in scRNA-seq dataset + gene_list = rna_genes + else: + # Otherwise ensure gene is contained within the shared list + if not set(gene_list).issubset(set(rna_genes)): + main_exception('gene_list is not a subset of genes found in scRNA-seq dataset.') + + shared_elements, shared_genes, shared_enhancer_dict, shared_promoter_dict = \ + self._restrict_dicts_to_gene_list(gene_list=gene_list, + cre_dict=self._cre_dict, + promoter_dict=self._promoter_dict) + + if subset: + # Subset the scATAC-seq data to shared elements + self.mdata.mod['atac'] = self.mdata.mod['atac'][:, shared_elements].copy() + + # Subset the scRNA_seq data to shared genes + self.mdata.mod['rna'] = self.mdata.mod['rna'][:, shared_genes].copy() + + return shared_elements, shared_genes + + def rna_genes(self): + return self.mdata.mod['rna'].var_names.tolist() + + def to_mdata(self) -> MuData: + # Deep copy MuData object for export + atac_adata, rna_adata = self.mdata.mod['atac'].copy(), self.mdata.mod['rna'].copy() + + # ... embellish atac + atac_adata.uns['cre_dict'] = self._cre_dict.copy() + atac_adata.uns['promoter_dict'] = self._promoter_dict.copy() + atac_adata.uns['multi_dynamo_kwargs'] = {'include_gene_body': self.include_gene_body, + 'linkage_fn': self.linkage_fn, + 'linkage_method': self.linkage_method, + 'max_peak_dist': self.max_peak_dist, + 'min_corr': self.min_corr, + 'peak_annot_fn': self.peak_annot_fn} + + # ... embellish rna + rna_adata.obsm['multi_dynamo_nn_dist'] = self.nn_dist.copy() + rna_adata.obsm['multi_dynamo_nn_idx'] = self.nn_idx.copy() + + rna_adata.obsp['cosine_similarities'] = self.cosine_similarities.copy() + rna_adata.uns['integral_neighbors'] = {str(k): str(v) for k,v in self.integral_neighbors.items()}.copy() + rna_adata.uns['multi_dynamo_kwargs'] = {'neighbor_method': self.neighbor_method} + + return MuData({'atac': atac_adata, 'rna': rna_adata}) + + # transcriptomic_velocity: this could really be any of the many methods that already exist, including those in + # dynamo and we plan to add this capability later. + def transcriptomic_velocity(self, + adjusted: bool = False, + min_r2: float = 1e-2, + mode: Literal['deterministic', 'stochastic'] = 'deterministic', + n_neighbors: int = 20, + n_pcs: int = 20, + num_processes: int = 6, + outlier: float = 99.8): + # Extract transcriptome and chromatin accessibility + atac_adata, rna_adata = self.mdata.mod['atac'], self.mdata.mod['rna'] + + # Assemble dictionary of arguments for fits + fit_args = {'min_r2': min_r2, + 'mode': mode, + 'n_pcs': n_pcs, + 'n_neighbors': n_neighbors, + 'outlier': outlier} + + # Obtain connectivities from the scRNA-seq object + rna_conn = rna_adata.obsp['connectivities'] + + # Compute moments for transcriptome data + main_info('computing moments for transcriptomic data ...') + rna_adata.layers[MDKM.RNA_FIRST_MOMENT_SPLICED_LAYER] = ( + csr_matrix.dot(rna_conn, csr_matrix(rna_adata.layers[MDKM.RNA_SPLICED_LAYER])) + .astype(np.float32) + .toarray() + ) + rna_adata.layers[MDKM.RNA_FIRST_MOMENT_UNSPLICED_LAYER] = ( + csr_matrix.dot(rna_conn, csr_matrix(rna_adata.layers[MDKM.RNA_UNSPLICED_LAYER])) + .astype(np.float32) + .toarray() + ) + + # Initialize select second moments for the transcriptomic data + Mss, Mus, Muu = None, None, None + if mode == 'stochastic': + main_info('computing second moments', indent_level=2) + Mss, Mus, Muu = self.compute_second_moments(adjusted=adjusted) + + rna_adata.layers[MDKM.RNA_SECOND_MOMENT_SS_LAYER] = Mss.copy() + rna_adata.layers[MDKM.RNA_SECOND_MOMENT_US_LAYER] = Mus.copy() + rna_adata.layers[MDKM.RNA_SECOND_MOMENT_UU_LAYER] = Muu.copy() + + if 'highly_variable' in rna_adata.var: + main_info('using highly variable genes', indent_level=2) + rna_gene_list = rna_adata.var_names[rna_adata.var['highly_variable']].values + else: + rna_gene_list = rna_adata.var_names.values[ + (~np.isnan(np.asarray(rna_adata.layers[MDKM.RNA_FIRST_MOMENT_UNSPLICED_LAYER].sum(0)) + .reshape(-1) + if issparse(rna_adata.layers[MDKM.RNA_FIRST_MOMENT_UNSPLICED_LAYER]) + else np.sum(rna_adata.layers[MDKM.RNA_FIRST_MOMENT_UNSPLICED_LAYER], axis=0))) + & (~np.isnan(np.asarray(rna_adata.layers[MDKM.RNA_FIRST_MOMENT_SPLICED_LAYER].sum(0)) + .reshape(-1) + if issparse(rna_adata.layers[MDKM.RNA_FIRST_MOMENT_SPLICED_LAYER]) + else np.sum(rna_adata.layers[MDKM.RNA_FIRST_MOMENT_SPLICED_LAYER], axis=0)))] + + # Restrict to genes with corresponding peaks in scATAC-seq data + shared_elements, shared_genes = self.restrict_to_gene_list(gene_list=rna_gene_list, + subset=True) + + n_fitted_genes = len(shared_genes) + if n_fitted_genes: + main_info(f'{n_fitted_genes} genes will be fitted') + else: + main_exception('None of the genes specified are in the adata object') + + velo_s = np.zeros((rna_adata.n_obs, n_fitted_genes)) + variance_velo_s = np.zeros((rna_adata.n_obs, n_fitted_genes)) + gammas = np.zeros(n_fitted_genes) + r2s = np.zeros(n_fitted_genes) + losses = np.zeros(n_fitted_genes) + + u_mat = (rna_adata[:, shared_genes].layers[MDKM.RNA_FIRST_MOMENT_UNSPLICED_LAYER].A + if issparse(rna_adata.layers[MDKM.RNA_FIRST_MOMENT_UNSPLICED_LAYER]) + else rna_adata[:, shared_genes].layers[MDKM.RNA_FIRST_MOMENT_UNSPLICED_LAYER]) + s_mat = (rna_adata[:, shared_genes].layers[MDKM.RNA_FIRST_MOMENT_SPLICED_LAYER].A + if issparse(rna_adata.layers[MDKM.RNA_FIRST_MOMENT_SPLICED_LAYER]) + else rna_adata[:, shared_genes].layers[MDKM.RNA_FIRST_MOMENT_SPLICED_LAYER]) + + M_c = csr_matrix(atac_adata[:, shared_elements].layers[MDKM.RNA_FIRST_MOMENT_CHROM_LAYER]) \ + if issparse(atac_adata.layers[MDKM.RNA_FIRST_MOMENT_CHROM_LAYER]) else \ + atac_adata[:, shared_elements].layers[MDKM.RNA_FIRST_MOMENT_CHROM_LAYER] + c_mat = M_c.toarray() if issparse(M_c) else M_c + + # Create dictionary from gene to index + gene_to_idx_dict = {gene: idx for idx, gene in enumerate(shared_genes)} + + # Create dictionary from peak to index + peak_to_idx_dict = {element: idx for idx, element in enumerate(shared_elements)} + + # Create unified gene to list of elements dict + tmp_elements_for_gene_dict = {} + for gene, element_list in self._cre_dict.items(): + tmp_elements_for_gene_dict[gene] = tmp_elements_for_gene_dict.setdefault(gene, []) + element_list + + for gene, element_list in self._promoter_dict.items(): + tmp_elements_for_gene_dict[gene] = tmp_elements_for_gene_dict.setdefault(gene, []) + element_list + + elements_for_gene_dict = {} + for gene, element_list in tmp_elements_for_gene_dict.items(): + elements_for_gene_dict[gene] = list(set(element_list)) + + # Create dictionary from gene indices to list of peaks by indices + gene_idx_to_peak_idx = {gene_to_idx_dict[gene]: [peak_to_idx_dict[peak] for peak in peak_list] + for gene, peak_list in elements_for_gene_dict.items()} + + # Define batch arguments + batches_of_arguments = [] + for i in range(n_fitted_genes): + gene = shared_genes[i] + peak_idx = gene_idx_to_peak_idx[i] + + batches_of_arguments.append( + (c_mat[:, peak_idx], + u_mat[:, i], + s_mat[:, i], + None if mode == 'deterministic' else Mss[:, i], + None if mode == 'deterministic' else Mus[:, i], + None if mode == 'deterministic' else Muu[:, i], + fit_args, + mode, + gene)) + + # Carry out fits in parallel + with Pool(processes=num_processes) as pool: + results = pool.starmap(regression, batches_of_arguments) + + # Reformat the results + for idx, (velocity, velocity_variance, gamma, r2, loss) in enumerate(results): + gammas[idx] = gamma + r2s[idx] = r2 + losses[idx] = loss + velo_s[:, idx] = smooth_scale(rna_conn, velocity) + + if mode == 'stochastic': + variance_velo_s[:, idx] = smooth_scale(rna_conn, + velocity_variance) + + # Determine which fits failed + kept_genes = [gene for gene, loss in zip(shared_genes, losses) if loss != np.inf] + if len(kept_genes) == 0: + main_exception('None of the genes were fit due to low quality.') + + # Subset the transcriptome to the genes for which the fits were successful + rna_copy = rna_adata[:, kept_genes].copy() + + # Add the fit results + keep = [loss != np.inf for loss in losses] + + # ... layers + rna_copy.layers[MDKM.RNA_SPLICED_VELOCITY_LAYER] = csr_matrix(velo_s[:, keep]) + if mode == 'stochastic': + rna_copy.layers['variance_velo_s'] = csr_matrix(variance_velo_s[:, keep]) + + # ... .obsp + rna_copy.obsp['_RNA_conn'] = rna_conn + + # ... .uns + # ... ... augment the dynamical and normalization information + dyn_and_norm_info = rna_copy.uns['pp'].copy() + dyn_and_norm_info['experiment_total_layers'] = None + dyn_and_norm_info['layers_norm_method'] = None + dyn_and_norm_info['tkey'] = None + rna_copy.uns['pp'] = dyn_and_norm_info.copy() + + dynamics = {'filter_gene_mode': 'final', + 't': None, + 'group': None, + 'X_data': None, + 'X_fit_data': None, + 'asspt_mRNA': 'ss', + 'experiment_type': dyn_and_norm_info.get('experiment_type', 'conventional'), + 'normalized': True, + 'model': mode, + 'est_method': 'gmm', # Consider altering + 'has_splicing': dyn_and_norm_info.get('has_splicing', True), + 'has_labeling': dyn_and_norm_info.get('has_labeling', False), + 'splicing_labeling': dyn_and_norm_info.get('splicing_labeling', False), + 'has_protein': dyn_and_norm_info.get('has_protein', False), + 'use_smoothed': True, + 'NTR_vel': False, + 'log_unnormalized': True, + # Ensure X is indeed log normalized (compute exp1m, sum and check rowsums) + 'fraction_for_deg': False} + rna_copy.uns['dynamics'] = dynamics.copy() + + rna_copy.uns['velo_s_params'] = {'mode': mode, + 'fit_offset': False, + 'perc': outlier} + rna_copy.uns['velo_s_params'].update(fit_args) + + # ... ... These are the column names for the array in .varm['vel_params'] + rna_copy.uns['vel_params_names'] = ['beta', 'gamma', 'half_life', 'alpha_b', 'alpha_r2', 'gamma_b', + 'gamma_r2', 'gamma_logLL', 'delta_b', 'delta_r2', 'bs', 'bf', + 'uu0', 'ul0', 'su0', 'sl0', 'U0', 'S0', 'total0'] + + # ... .var + rna_copy.var['fit_gamma'] = gammas[keep] + rna_copy.var['fit_loss'] = losses[keep] + rna_copy.var['fit_r2'] = r2s[keep] + + # Introduce var['use_for_dynamics'] for dynamo + v_gene_ind = rna_copy.var['fit_r2'] >= min_r2 + rna_copy.var['use_for_dynamics'] = v_gene_ind + rna_copy.var['velo_s_genes'] = v_gene_ind + + # ... .varm + vel_params_array = np.full((rna_copy.shape[1], len(rna_copy.uns['vel_params_names'])), np.nan) + + # ... ... ... transfer 'gamma' + gamma_index = np.where(np.array(rna_copy.uns['vel_params_names']) == 'gamma')[0][0] + vel_params_array[:, gamma_index] = rna_copy.var['fit_gamma'] + + # ... ... ... transfer 'gamma_r2' + gamma_r2_index = np.where(np.array(rna_copy.uns['vel_params_names']) == 'gamma_r2')[0][0] + vel_params_array[:, gamma_r2_index] = rna_copy.var['fit_r2'] + + rna_copy.varm['vel_params'] = vel_params_array + + # Copy the subset AnnData scRNA-seq and scATAC-seq objects back into the MultiomeVelocity object + self.mdata.mod['rna'] = rna_copy.copy() + + # Filter the scATAC-seq peaks to retain only those corresponding to fit genes + shared_elements, shared_genes = self.restrict_to_gene_list(gene_list=kept_genes, + subset=True) + + # Confer same status to element corresponding to genes declared as 'use_for_dynamics' + v_genes = [gene for gene, v_ind in zip(shared_genes, v_gene_ind) if v_ind] + # v_elements, v_genes = self.restrict_to_gene_list(gene_list=v_genes, subset=False) + # v_element_ind = [element in v_elements for element in shared_elements] + # TODO: Need to special case when no genes rise to significance + v_element_ind = [True for _ in range(atac_adata.n_vars)] + + # Introduce var['use_for_dynamics'] for dynamo + # TODO: This does NOT appear to work properly yet - so left permissive + atac_adata.var['use_for_dynamics'] = v_element_ind + + self.mdata.mod['atac'] = atac_adata.copy() + + def _update_cre_and_promoter_dicts(self, + cre_dict: Dict[str, List[str]] = None, + promoter_dict: Dict[str, List[str]] = None): + if cre_dict is not None or promoter_dict is not None: + # Should only have exogenous enhancer and promoter dicts if none are present in object + if self._cre_dict is not None or self._promoter_dict is not None: + main_exception('Should only specify exogenous CRE and promoter dicts if none are present in object.') + else: + # Extract the dictionaries + cre_dict = self._cre_dict + promoter_dict = self._promoter_dict + + # Extract the RNA genes + rna_genes = self.rna_genes() + + # ... determine which genes are actually present in the scATAC-seq data and for these + # which elements are present + shared_elements, shared_genes, shared_cre_dict, shared_promoter_dict = \ + self._restrict_dicts_to_gene_list(gene_list=rna_genes, + cre_dict=cre_dict, + promoter_dict=promoter_dict) + + if len(shared_genes) == 0: + main_exception('scATAC-seq data and scRNA-seq data do NOT share any genes.') + + # Subset the scATAC-seq data to shared elements + self.mdata.mod['atac'] = self.mdata.mod['atac'][:, shared_elements].copy() + + # Subset the scRNA_seq data to shared genes + self.mdata.mod['rna'] = self.mdata.mod['rna'][:, shared_genes].copy() + + # Initialize the original enhancer and promoter dicts + self._cre_dict = shared_cre_dict + self._promoter_dict = shared_promoter_dict + + def weighted_nearest_neighbors( + self, + atac_lsi_key: str = MDKM.ATAC_OBSM_LSI_KEY, + n_components_atac: int = 20, + n_components_rna: int = 20, + nn: int = 20, + random_state: int = 42, + rna_pca_key: str = MDKM.RNA_OBSM_PC_KEY, + use_highly_variable: bool = False): + main_info('Starting computation of weighted nearest neighbors ...', indent_level=1) + nn_logger = LoggerManager.gen_logger('weighted_nearest_neighbors') + nn_logger.log_time() + + # Restrict to shared genes and their elements - as tied together by the attribution of CRE to genes + shared_elements, shared_genes = self.restrict_to_gene_list(subset=True) + + # Extract scATAC-seq and scRNA-seq data + atac_adata = self.mdata.mod['atac'][:, shared_elements].copy() + rna_adata = self.mdata.mod['rna'][:, shared_genes].copy() + + if rna_pca_key not in rna_adata.obsm: + # TODO: Consider normalizing counts here, if needed + + # Carry out PCA on scRNA-seq data + main_info('computing PCA on normalized and scaled scRNA-seq data', indent_level=2) + sc.tl.pca(rna_adata, + n_comps=n_components_rna, + random_state=random_state, + use_highly_variable=use_highly_variable) + + if atac_lsi_key not in atac_adata.obsm: + # Carry out singular value decomposition on the scATAC-seq data + main_info('computing latent semantic indexing of scATAC-seq data ...') + lsi = svds(atac_adata.X, k=n_components_atac) + + # get the lsi result + atac_adata.obsm[atac_lsi_key] = lsi[0] + + # Cross copy the LSI decomposition + rna_adata.obsm[atac_lsi_key] = atac_adata.obsm[atac_lsi_key] + + # Use Dylan Kotliar's python implementation of + # TODO: As alternative to PCA could use the latent space from variational autoencoder. + WNNobj = pyWNN(rna_adata, + reps=[rna_pca_key, atac_lsi_key], + npcs=[n_components_rna, n_components_atac], + n_neighbors=nn, + seed=42) + + adata_seurat = WNNobj.compute_wnn(rna_adata) + + # extract the matrix storing the distances between each cell and its neighbors + cx = coo_matrix(adata_seurat.obsp["WNN_distance"]) + + # the number of cells + cells = adata_seurat.obsp['WNN_distance'].shape[0] + + # define the shape of our final results + # and make the arrays that will hold the results + new_shape = (cells, nn) + nn_dist = np.zeros(shape=new_shape) + nn_idx = np.zeros(shape=new_shape) + + # new_col defines what column we store data in + # our result arrays + new_col = 0 + + # loop through the distance matrices + for i, j, v in zip(cx.row, cx.col, cx.data): + + # store the distances between neighbor cells + nn_dist[i][new_col % nn] = v + + # for each cell's row, store the row numbers of its neighbor cells + # (1-indexing instead of 0- is a holdover from R multimodalneighbors()) + nn_idx[i][new_col % nn] = int(j) + 1 + + new_col += 1 + + # Add index and distance to the MultiomeVelocity object + self.nn_idx = nn_idx + self.nn_dist = nn_dist + + # Revert to canonical naming of connectivities and distances + # ... .uns['neighbors'] + atac_adata.uns['neighbors'] = adata_seurat.uns['WNN'].copy() + rna_adata.uns['neighbors'] = adata_seurat.uns['WNN'].copy() + del adata_seurat.uns['WNN'] + + # ... .obsp['connectivities'] + atac_adata.obsp['connectivities'] = adata_seurat.obsp['WNN'].copy() + rna_adata.obsp['connectivities'] = adata_seurat.obsp['WNN'].copy() + del adata_seurat.obsp['WNN'] + + # ... .obsp['distances'] + atac_adata.obsp['distances'] = adata_seurat.obsp['WNN_distance'].copy() + rna_adata.obsp['distances'] = adata_seurat.obsp['WNN_distance'].copy() + del adata_seurat.obsp['WNN_distance'] + + # Copy the subset AnnData scRNA-seq and scATAC-seq objects back into the MultiomeVelocity object + self.mdata.mod['atac'] = atac_adata.copy() + self.mdata.mod['rna'] = rna_adata.copy() + + def write(self, + filename: Union[PathLike, str]) -> None: + export_mdata = self.to_mdata() + export_mdata.write_h5mu(filename) diff --git a/dynamo/multivelo/old_MultiomicVectorField.py b/dynamo/multivelo/old_MultiomicVectorField.py new file mode 100644 index 00000000..8e21443b --- /dev/null +++ b/dynamo/multivelo/old_MultiomicVectorField.py @@ -0,0 +1,445 @@ +import anndata as ad +from anndata import AnnData +import matplotlib.pyplot as plt +from mudata import MuData +import numpy as np +import pandas as pd +from scipy.sparse import csr_matrix +from typing import ( + Dict, + List, + Literal, + Optional, + Tuple, + Union, +) + +# Imports from MultiDynamo +from .MultiConfiguration import MDKM +from .old_MultiVelocity import MultiVelocity + +from ..pl import cell_wise_vectors, streamline_plot, topography +from ..pd import fate, perturbation +from ..mv import animate_fates +from ..pp import pca +from ..tl import reduceDimension, cell_velocities +from ..vf import VectorField + + +# Helper functions +def compute_animations(adata, + cell_type_key: str, + cores: int = 6, + delta_epsilon: float = 0.25, + epsilon: float = 1.0, + max_tries: int = 10, + n_cells: int = 100, + n_earliest: int = 30, + prefix: str = None, + skip_cell_types: List = [] + ) -> None: + # Extract cell metadata + cell_metadata = adata.obs.copy() + + # Add UMAP + cell_metadata['umap_1'] = adata.obsm['X_umap'][:, 0] + cell_metadata['umap_2'] = adata.obsm['X_umap'][:, 1] + + # Group by cell_type_key and find the rows with the maximal 'rotated_umap_1' + grouped = cell_metadata.groupby(cell_type_key) + + # Find the mean locations of cell types + top_indices_1, top_indices_2 = {}, {} + for cell_type, celltype_data in grouped: + subset_df = celltype_data.nsmallest(n_cells, 'umap_1') + top_indices_1[cell_type] = subset_df['umap_1'].mean() + subset_df = celltype_data.nlargest(n_cells, 'umap_2') + top_indices_2[cell_type] = subset_df['umap_2'].mean() + + cell_types = cell_metadata[cell_type_key].cat.categories.tolist() + progenitor_list = [] + + for cell_type in cell_types: + if (skip_cell_types is not None) and (cell_type in skip_cell_types): + continue + + print(f'Computing animation for cell type {cell_type}') + + # Find the progenitors + n_tries, progenitors = 1, [] + while len(progenitors) < n_cells and n_tries < max_tries + 1: + progenitors = adata.obs_names[adata.obs.celltype.isin([cell_type]) & + (abs(cell_metadata['umap_1'] - top_indices_1[cell_type]) < ( + epsilon + n_tries * delta_epsilon)) & + (abs(cell_metadata['umap_2'] - top_indices_2[cell_type]) < ( + epsilon + n_tries * delta_epsilon))] + n_tries += 1 + + if len(progenitors) >= n_earliest: + # Progenitors for all subset simulation + print(f'Adding {n_earliest} cells of type {cell_type}.') + progenitor_list.extend(progenitors[0:min(len(progenitors), n_earliest)]) + + # Progenitors for this animation + # progenitors = progenitors[0:min(len(progenitors), n_cells)] + + # Determine their fate + # dyn.pd.fate(adata, basis='umap_perturbation', init_cells=progenitors, interpolation_num=100, + # direction='forward', inverse_transform=False, average=False, cores=6) + + # Compute the animation + # animation_fn = cell_type + '_perturbed_fate_ani.mp4' + # animation_fn = animation_fn.replace('/', '-') + # dyn.mv.animate_fates(adata, basis='umap_perturbation', color='celltype', n_steps=100, + # interval=100, save_show_or_return='save', + # save_kwargs={'filename': animation_fn, + # 'writer': 'ffmpeg'}) + + # Determine fate of progenitor_list + fate(adata, basis='umap_perturbation', init_cells=progenitor_list, interpolation_num=100, + direction='forward', inverse_transform=False, average=False, cores=cores) + + # Compute the animation + file_name = prefix + '_perturbation.mpeg' + file_name = file_name.replace(':', '-') + file_name = file_name.replace('/', '-') + animate_fates(adata, basis='umap_perturbation', color='celltype', n_steps=100, + interval=100, save_show_or_return='save', + save_kwargs={'filename': file_name, + 'writer': 'ffmpeg'}) + +def genes_and_elements_for_dynamics(atac_adata: AnnData, + rna_adata: AnnData, + cre_dict: Dict[str, List[str]], + promoter_dict: Dict[str, List[str]], + min_r2: float = 0.01) -> List[bool]: + # Get fit parameters + vel_params_array = rna_adata.varm['vel_params'] + + # Extract 'gamma_r2' + gamma_r2_index = np.where(np.array(rna_adata.uns['vel_params_names']) == 'gamma_r2')[0][0] + r2 = vel_params_array[:, gamma_r2_index] + + # Set genes for dynamics + genes_for_dynamics = rna_adata.var_names[r2 > min_r2].to_list() + use_for_dynamics = [gene in genes_for_dynamics for gene in rna_adata.var_names.to_list()] + + # Compute elements for dynamics + cre_for_dynamics = [] + for gene, cre_list in cre_dict.items(): + if gene in genes_for_dynamics: + cre_for_dynamics += cre_list + + for gene, promoter_list in promoter_dict.items(): + if gene in genes_for_dynamics: + cre_for_dynamics += promoter_list + + use_for_dynamics += [element in cre_for_dynamics for element in atac_adata.var_names] + + return use_for_dynamics + + +class MultiomicVectorField: + def __init__(self, + multi_velocity: Union[MultiVelocity, MuData], + min_gamma: float = None, + min_r2: float = 0.01, + rescale_velo_c: float = 1.0): + # This is basically an adapter from multiomic data to format where we can borrow tools previously developed + # in dynamo. + if isinstance(multi_velocity, MuData): + multi_velocity = MultiVelocity.from_mdata(multi_velocity) + + # ... mdata + mdata = multi_velocity.get_mdata() + atac_adata, rna_adata = mdata.mod['atac'], mdata.mod['rna'] + + # ... CRE dictionary + cre_dict = multi_velocity.get_cre_dict() + + # ... promoter dictionary + promoter_dict = multi_velocity.get_promoter_dict() + + # To estimate the multi-omic velocity field, we assemble a single AnnData object from the following components + # NOTE: In our descriptions below *+* signifies the directo sum of two vector spaces + # ... .layers + # ... ... counts: counts => rna counts *+* atac counts + rna_counts = rna_adata.layers[MDKM.RNA_COUNTS_LAYER].toarray().copy() + atac_counts = atac_adata.layers[MDKM.ATAC_COUNTS_LAYER].toarray().copy() + counts = np.concatenate((rna_counts, atac_counts), axis=1) + + # ... ... raw: spliced, unspliced ==> spliced *+* chromatin, unspliced *+* 0 + chromatin_state = atac_adata.layers[MDKM.ATAC_COUNTS_LAYER].toarray().copy() + spliced = rna_adata.layers[MDKM.RNA_SPLICED_LAYER].toarray().copy() + unspliced = rna_adata.layers[MDKM.RNA_UNSPLICED_LAYER].toarray().copy() + + spliced = np.concatenate((spliced, chromatin_state), axis=1) + unspliced = np.concatenate((unspliced, np.zeros(chromatin_state.shape)), axis=1) + del chromatin_state + + # ... ... first moments: M_s, M_u => M_s *+* Mc, M_u *+* 0 + Mc = atac_adata.layers[MDKM.RNA_FIRST_MOMENT_CHROM_LAYER].toarray().copy() + Ms = rna_adata.layers[MDKM.RNA_FIRST_MOMENT_SPLICED_LAYER].copy() + Mu = rna_adata.layers[MDKM.RNA_FIRST_MOMENT_UNSPLICED_LAYER].copy() + + Ms = np.concatenate((Ms, Mc), axis=1) + Mu = np.concatenate((Mu, np.zeros(Mc.shape)), axis=1) + del Mc + + # ... ... velocity_S ==> velocity_S + lifted_velo_c + velocity_C = atac_adata.layers[MDKM.ATAC_CHROMATIN_VELOCITY_LAYER].copy() + velocity_S = rna_adata.layers[MDKM.RNA_SPLICED_VELOCITY_LAYER].toarray().copy() + + velocity_S = np.concatenate((velocity_S, rescale_velo_c * velocity_C), axis=1) + del velocity_C + + # ... .obs + # ... ... carry over entire obs for now + obs_df = rna_adata.obs.copy() + + # ... .obsp + # ... ... connectivities ==> connectivities + connectivities = rna_adata.obsp['connectivities'].copy() + + # ... ... distances ==> distances + distances = rna_adata.obsp['distances'].copy() + + # ... .uns + # ... ... dynamics ==> dynamics + dynamics = rna_adata.uns['dynamics'].copy() + + # ... ... neighbors ==> neighbors + neighbors = rna_adata.uns['neighbors'].copy() + + # ... ... pp ==> pp + pp = rna_adata.uns['pp'].copy() + + # ... ... vel_params_names ==> vel_params_names + vel_params_names = rna_adata.uns['vel_params_names'].copy() + + # ... .var + # ... ... var_names ==> (rna) var_names + (atac) var_names + var_names = rna_adata.var_names.tolist() + atac_adata.var_names.tolist() + + # ... ... feature_type ==> n_genes * 'gene', n_elements * 'CRE' + feature_type = rna_adata.n_vars * ['gene'] + atac_adata.n_vars * ['CRE'] + + # ... ... use_for_pca + use_for_dynamics = genes_and_elements_for_dynamics(atac_adata=atac_adata, + rna_adata=rna_adata, + cre_dict=cre_dict, + promoter_dict=promoter_dict, + min_r2=min_r2) + + # ... ... use_for_pca + use_for_pca = genes_and_elements_for_dynamics(atac_adata=atac_adata, + rna_adata=rna_adata, + cre_dict=cre_dict, + promoter_dict=promoter_dict, + min_r2=min_r2) + + var_df = pd.DataFrame(data={'feature_type': feature_type, + 'use_for_dynamics': use_for_dynamics, + 'use_for_pca': use_for_pca}, + index=var_names) + + # ... .varm + # ... ... vel_params => vel_params + (1,1) + vel_params_array = rna_adata.varm['vel_params'] + + chrom_vel_params_array = np.full((atac_adata.n_vars, len(vel_params_names)), np.nan) + + # ... ... create vacuous 'gamma' for chromatin data + gamma_index = np.where(np.array(vel_params_names) == 'gamma')[0][0] + chrom_vel_params_array[:, gamma_index] = np.ones(atac_adata.n_vars) + + # ... ... create vacuous 'gamma_r2' for chromatin data + gamma_r2_index = np.where(np.array(vel_params_names) == 'gamma_r2')[0][0] + chrom_vel_params_array[:, gamma_r2_index] = np.ones(atac_adata.n_vars) + + # ... ... concatenate the arrays + vel_params_array = np.concatenate((vel_params_array, chrom_vel_params_array), axis=0) + + # X ==> X + X + X = np.concatenate((rna_adata.X.toarray().copy(), atac_adata.X.toarray().copy()), axis=1) + + # Instantiate the multiomic AnnData object + adata_multi = AnnData(obs=obs_df, + var=var_df, + X=X) + # ... add .layers + # ... ... counts + adata_multi.layers[MDKM.RNA_COUNTS_LAYER] = counts + + # ... ... raw + adata_multi.layers[MDKM.RNA_SPLICED_LAYER] = spliced + adata_multi.layers[MDKM.RNA_UNSPLICED_LAYER] = unspliced + + # ... ... first moments + adata_multi.layers[MDKM.RNA_FIRST_MOMENT_SPLICED_LAYER] = Ms + adata_multi.layers[MDKM.RNA_FIRST_MOMENT_UNSPLICED_LAYER] = Mu + + # ... ... rna velocity + adata_multi.layers[MDKM.RNA_SPLICED_VELOCITY_LAYER] = velocity_S + + # ... add .obsp + adata_multi.obsp['connectivities'] = connectivities + adata_multi.obsp['distances'] = distances + + # ... add .uns + adata_multi.uns['dynamics'] = dynamics + adata_multi.uns['neighbors'] = neighbors + adata_multi.uns['pp'] = pp + adata_multi.uns['vel_params_names'] = vel_params_names + + # ... add varm + adata_multi.varm['vel_params'] = vel_params_array + + # Set instance variables + + self.multi_adata = adata_multi.copy() + + def cell_velocities(self, + cores: int = 6, + min_r2: float = 0.5, + n_neighbors: int = 30, + n_pcs: int = 30, + random_seed: int = 42, + trans_matrix_method: Literal["kmc", "fp", "cosine", "pearson", "transform"] = "pearson", + ) -> AnnData: + # We'll save ourselves some grief and just compute both the PCA and UMAP representations + # of the vector field up front + # ... extract the multiomic AnnData object + adata_multi = self.multi_adata.copy() + + # ... compute PCA + adata_multi = pca(adata=adata_multi, + n_pca_components=n_pcs, + random_state=random_seed) + + # ... compute the appropriate dimensional reduction + reduceDimension(adata_multi, + basis='pca', + cores=cores, + n_pca_components=n_pcs, + n_components=2, + n_neighbors=n_neighbors, + reduction_method='umap') + + # ... project high dimensional velocities onto PCA embeddings and compute cell transitions + cell_velocities(adata_multi, + basis='pca', + method=trans_matrix_method, + min_r2=min_r2, + other_kernels_dict={'transform': 'sqrt'}) + + # ... project high dimensional velocities onto PCA embeddings and compute cell transitions + cell_velocities(adata_multi, + basis='umap', + method=trans_matrix_method, + min_r2=min_r2, + other_kernels_dict={'transform': 'sqrt'}) + + self.multi_adata = adata_multi.copy() + + return self.multi_adata + + def compute_vector_field(self, + cores: int = 6, + restart_num: int = 5 + ): + VectorField(self.multi_adata, + basis='pca', + cores=cores, + grid_num=100, + M=1000, + pot_curl_div=True, + restart_num=restart_num, + restart_seed=[i * 888888888 for i in range(1, restart_num + 1)]) + ''' + dyn.vf.VectorField(self.multi_adata, + basis='umap', + cores=cores, + grid_num=100, + M=1000, + pot_curl_div=True, + restart_num=restart_num, + restart_seed=[i * 888888888 for i in range(1, restart_num + 1)]) + ''' + + def plot_cell_wise_vectors(self, + color: str = 'cell_type', + figsize: Tuple[float, float] = (9, 6), + **save_kwargs + ) -> None: + fig, ax = plt.subplots(figsize=figsize) + cell_wise_vectors(self.multi_adata, + basis='umap', + color=[color], + pointsize=0.1, + quiver_length=6, + quiver_size=6, + save_kwargs=save_kwargs, + save_show_or_return='show', + show_arrowed_spines=False, + show_legend='on_data', + ax = ax) + plt.show() + + def plot_streamline_plot(self, + color: str = 'cell_type', + figsize: Tuple[float, float] = (9, 6), + **save_kwargs + ) -> None: + fig, ax = plt.subplots(figsize=figsize) + streamline_plot(self.multi_adata, + basis='umap', + color=[color], + show_arrowed_spines=True, + show_legend='on_data', + ax = ax) + plt.show() + + def plot_topography(self, + color: str = 'cell_type', + figsize: Tuple[float, float] = (9, 6), + **save_kwargs + ) -> None: + fig, ax = plt.subplots(figsize=figsize) + topography(self.multi_adata, + basis='pca', + background='white', + color=color, + frontier=True, + n = 200, + show_legend='on data', + streamline_color='black', + ax = ax) + + def predict_perturbation(self, + gene: str, + expression: float, + cell_type_key: str = 'cell_type', + compute_animation: bool = False, + emb_basis: str = 'umap', + skip_cell_types: List = None + ) -> AnnData: + + perturbed_multi_adata = perturbation(self.multi_adata, + genes=gene, + expression=expression, + emb_basis='umap') + streamline_plot(self.multi_adata, color=["cell_type", gene], + basis="umap_perturbation") + + if compute_animation: + # Fit analytic vector field + VectorField(self.multi_adata, + basis='umap_perturbation') + + compute_animations(adata=self.multi_adata, + cell_type_key=cell_type_key, + prefix=gene, + skip_cell_types=skip_cell_types) + + return perturbed_multi_adata diff --git a/dynamo/multivelo/pyWNN.py b/dynamo/multivelo/pyWNN.py new file mode 100644 index 00000000..763fef4f --- /dev/null +++ b/dynamo/multivelo/pyWNN.py @@ -0,0 +1,270 @@ +# This has been taken and lightly modified from Dylan's Kotliar's github repository +from anndata import AnnData +import numpy as np +import scanpy as sc +from sklearn import preprocessing +from scipy.sparse import csr_matrix, lil_matrix, diags +import sys +import time +from typing import List + +# Import from dynamo +from ..dynamo_logger import ( + LoggerManager, + main_debug, + main_exception, + main_finish_progress, + main_info, + main_info_insert_adata, + main_warning, +) + + +def compute_bw(knn_adj, embedding, n_neighbors=20): + intersect = knn_adj.dot(knn_adj.T) + indices = intersect.indices + indptr = intersect.indptr + data = intersect.data + data = data / ((n_neighbors * 2) - data) + bandwidth = [] + num = 0 + for i in range(intersect.shape[0]): + cols = indices[indptr[i]:indptr[i + 1]] + rowvals = data[indptr[i]:indptr[i + 1]] + idx = np.argsort(rowvals) + valssort = rowvals[idx] + numinset = len(cols) + if numinset < n_neighbors: + sys.exit('Fewer than 20 cells with Jacard sim > 0') + else: + curval = valssort[n_neighbors] + for num in range(n_neighbors, numinset): + if valssort[num] != curval: + break + else: + num += 1 + minjacinset = cols[idx][:num] + if num < n_neighbors: + main_exception('compute_bw method failed.') + sys.exit(-1) + else: + euc_dist = ((embedding[minjacinset, :] - embedding[i, :]) ** 2).sum(axis=1) ** .5 + euc_dist_sorted = np.sort(euc_dist)[::-1] + bandwidth.append(np.mean(euc_dist_sorted[:n_neighbors])) + return np.array(bandwidth) + +def compute_affinity(dist_to_predict, dist_to_nn, bw): + affinity = dist_to_predict - dist_to_nn + affinity[affinity < 0] = 0 + affinity = affinity * -1 + affinity = np.exp(affinity / (bw - dist_to_nn)) + return affinity + +def dist_from_adj(adjacency, embed1, embed2, nndist1, nndist2): + dist1 = lil_matrix(adjacency.shape) + dist2 = lil_matrix(adjacency.shape) + + indices = adjacency.indices + indptr = adjacency.indptr + ncells = adjacency.shape[0] + + tic = time.perf_counter() + for i in range(ncells): + for j in range(indptr[i], indptr[i + 1]): + col = indices[j] + a = (((embed1[i, :] - embed1[col, :]) ** 2).sum() ** .5) - nndist1[i] + if a == 0: + dist1[i, col] = np.nan + else: + dist1[i, col] = a + b = (((embed2[i, :] - embed2[col, :]) ** 2).sum() ** .5) - nndist2[i] + if b == 0: + dist2[i, col] = np.nan + else: + dist2[i, col] = b + + if (i % 2000) == 0: + toc = time.perf_counter() + main_info('%d out of %d %.2f seconds elapsed' % (i, ncells, toc - tic), indent_level=3) + + return csr_matrix(dist1), csr_matrix(dist2) + +def get_nearestneighbor(knn, neighbor=1): + # For each row of knn, returns the column with the lowest value i.e. the nearest neighbor + indices = knn.indices + indptr = knn.indptr + data = knn.data + nn_idx = [] + for i in range(knn.shape[0]): + cols = indices[indptr[i]:indptr[i + 1]] + rowvals = data[indptr[i]:indptr[i + 1]] + idx = np.argsort(rowvals) + nn_idx.append(cols[idx[neighbor - 1]]) + return np.array(nn_idx) + +def select_topK(dist, n_neighbors=20): + indices = dist.indices + indptr = dist.indptr + data = dist.data + nrows = dist.shape[0] + + final_data = [] + final_col_ind = [] + + for i in range(nrows): + cols = indices[indptr[i]:indptr[i + 1]] + rowvals = data[indptr[i]:indptr[i + 1]] + idx = np.argsort(rowvals) + final_data.append(rowvals[idx[(-1 * n_neighbors):]]) + final_col_ind.append(cols[idx[(-1 * n_neighbors):]]) + + final_data = np.concatenate(final_data) + final_col_ind = np.concatenate(final_col_ind) + final_row_ind = np.tile(np.arange(nrows), (n_neighbors, 1)).reshape(-1, order='F') + + result = csr_matrix((final_data, (final_row_ind, final_col_ind)), shape=(nrows, dist.shape[1])) + + return result + +class pyWNN(): + + def __init__(self, + adata: AnnData, + reps: List[str] = None, + n_neighbors: int = 20, + npcs: List[int] = None, + seed: int = 14, + distances: csr_matrix = None + ) -> None: + """\ + Class for running weighted nearest neighbors analysis as described in Hao + et al 2021. + """ + # Set default arguments + if npcs is None: + npcs = [20, 20] + + if reps is None: + reps = ['X_pca', 'X_apca'] + + self.seed = seed + np.random.seed(seed) + + if len(reps) > 2: + sys.exit('WNN currently only implemented for 2 modalities') + + self.adata = adata.copy() + self.reps = [r + '_norm' for r in reps] + self.npcs = npcs + for (i, r) in enumerate(reps): + self.adata.obsm[self.reps[i]] = preprocessing.normalize(adata.obsm[r][:, 0:npcs[i]]) + + self.n_neighbors = n_neighbors + if distances is None: + main_info('Computing KNN distance matrices using default Scanpy implementation') + # ... n_neighbors in each modality + sc.pp.neighbors(self.adata, n_neighbors=n_neighbors, n_pcs=npcs[0], use_rep=self.reps[0], + metric='euclidean', key_added='1') + sc.pp.neighbors(self.adata, n_neighbors=n_neighbors, n_pcs=npcs[1], use_rep=self.reps[1], + metric='euclidean', key_added='2') + + # ... top 200 nearest neighbors in each modality + sc.pp.neighbors(self.adata, n_neighbors=200, n_pcs=npcs[0], use_rep=self.reps[0], metric='euclidean', + key_added='1_200') + sc.pp.neighbors(self.adata, n_neighbors=200, n_pcs=npcs[1], use_rep=self.reps[1], metric='euclidean', + key_added='2_200') + self.distances = ['1_distances', '2_distances', '1_200_distances', '2_200_distances'] + else: + main_info('Using pre-computed KNN distance matrices') + self.distances = distances + + for d in self.distances: + # Convert to sparse CSR matrices as needed + if type(self.adata.obsp[d]) is not csr_matrix: + self.adata.obsp[d] = csr_matrix(self.adata.obsp[d]) + + self.NNdist = [] + self.NNidx = [] + self.NNadjacency = [] + self.BWs = [] + + for (i, r) in enumerate(self.reps): + nn = get_nearestneighbor(self.adata.obsp[self.distances[i]]) + dist_to_nn = ((self.adata.obsm[r] - self.adata.obsm[r][nn, :]) ** 2).sum(axis=1) ** .5 + nn_adj = (self.adata.obsp[self.distances[i]] > 0).astype(int) + nn_adj_wdiag = csr_matrix(nn_adj.copy()) + nn_adj_wdiag.setdiag(1) + bw = compute_bw(nn_adj_wdiag, self.adata.obsm[r], n_neighbors=self.n_neighbors) + self.NNidx.append(nn) + self.NNdist.append(dist_to_nn) + self.NNadjacency.append(nn_adj) + self.BWs.append(bw) + + self.cross = [] + self.weights = [] + self.within = [] + self.WNN = None + self.WNNdist = None + + def compute_weights(self) -> None: + cmap = {0: 1, 1: 0} + affinity_ratios = [] + self.within = [] + self.cross = [] + for (i, r) in enumerate(self.reps): + within_predict = self.NNadjacency[i].dot(self.adata.obsm[r]) / (self.n_neighbors - 1) + cross_predict = self.NNadjacency[cmap[i]].dot(self.adata.obsm[r]) / (self.n_neighbors - 1) + + within_predict_dist = ((self.adata.obsm[r] - within_predict) ** 2).sum(axis=1) ** .5 + cross_predict_dist = ((self.adata.obsm[r] - cross_predict) ** 2).sum(axis=1) ** .5 + within_affinity = compute_affinity(within_predict_dist, self.NNdist[i], self.BWs[i]) + cross_affinity = compute_affinity(cross_predict_dist, self.NNdist[i], self.BWs[i]) + affinity_ratios.append(within_affinity / (cross_affinity + 0.0001)) + self.within.append(within_predict_dist) + self.cross.append(cross_predict_dist) + + self.weights.append(1 / (1 + np.exp(affinity_ratios[1] - affinity_ratios[0]))) + self.weights.append(1 - self.weights[0]) + + def compute_wnn( + self, + adata: AnnData + ) -> AnnData: + main_info('Computing modality weights', indent_level=2) + self.compute_weights() + union_adj_mat = ((self.adata.obsp[self.distances[2]] + self.adata.obsp[self.distances[3]]) > 0).astype(int) + + main_info('Computing weighted distances for union of 200 nearest neighbors between modalities', indent_level=2) + full_dists = dist_from_adj(union_adj_mat, self.adata.obsm[self.reps[0]], self.adata.obsm[self.reps[1]], + self.NNdist[0], self.NNdist[1]) + weighted_dist = csr_matrix(union_adj_mat.shape) + for (i, dist) in enumerate(full_dists): + dist = diags(-1 / (self.BWs[i] - self.NNdist[i]), format='csr').dot(dist) + dist.data = np.exp(dist.data) + ind = np.isnan(dist.data) + dist.data[ind] = 1 + dist = diags(self.weights[i]).dot(dist) + weighted_dist += dist + + main_info('Selecting top K neighbors', indent_level=2) + self.WNN = select_topK(weighted_dist, n_neighbors=self.n_neighbors) + WNNdist = self.WNN.copy() + x = (1 - WNNdist.data) / 2 + x[x < 0] = 0 + x[x > 1] = 1 + WNNdist.data = np.sqrt(x) + self.WNNdist = WNNdist + + adata.obsp['WNN'] = self.WNN + adata.obsp['WNN_distance'] = self.WNNdist + adata.obsm[self.reps[0]] = self.adata.obsm[self.reps[0]] + adata.obsm[self.reps[1]] = self.adata.obsm[self.reps[1]] + adata.uns['WNN'] = {'connectivities_key': 'WNN', + 'distances_key': 'WNN_distance', + 'params': {'n_neighbors': self.n_neighbors, + 'method': 'WNN', + 'random_state': self.seed, + 'metric': 'euclidean', + 'use_rep': self.reps[0], + 'n_pcs': self.npcs[0]}} + return (adata) diff --git a/dynamo/multivelo/settings.py b/dynamo/multivelo/settings.py new file mode 100644 index 00000000..5d07355a --- /dev/null +++ b/dynamo/multivelo/settings.py @@ -0,0 +1,27 @@ +import os + +"""Settings +""" + +# the desired verbosity +global VERBOSITY + +# cwd: The current working directory +global CWD + +# the name of the file to which we're writing the log files +global LOG_FOLDER + +# the name of the file to which we're writing the logs +# (If left to the default value of None, we don't write to a file) +global LOG_FILENAME + +# the name of the gene the code is processing +global GENE + +VERBOSITY = 1 +CWD = os.path.abspath(os.getcwd()) +LOG_FOLDER = os.path.join(CWD, "../logs") +LOG_FILENAME = None +GENE = None + diff --git a/dynamo/multivelo/sparse_matrix_utils.py b/dynamo/multivelo/sparse_matrix_utils.py new file mode 100644 index 00000000..d64aa276 --- /dev/null +++ b/dynamo/multivelo/sparse_matrix_utils.py @@ -0,0 +1,94 @@ +import os +import warnings + +if "NVCC" not in os.environ: + os.environ["NVCC"] = "/usr/local/cuda-11.5/bin/nvcc" + warnings.warn( + "NVCC Path not found, set to : /usr/local/cuda-11.5/bin/nvcc . \nPlease set NVCC as appropitate to your environment" + ) + +import cupy as cp +from numba import cuda +import math + +## Cuda JIT +code = """ +#include +extern "C" __global__ +void sort_sparse_array(double *data, int*indices, int *indptr, int n_rows) +{ + int tid = blockDim.x * blockIdx.x + threadIdx.x; + if(tid >= n_rows) return; + thrust::sort_by_key(thrust::seq, data+ indptr[tid], data + indptr[tid+1], indices + indptr[tid]); +} +""" + +kernel = cp.RawModule(code=code, backend="nvcc") +sort_f = kernel.get_function("sort_sparse_array") + +## Numba function +@cuda.jit +def find_top_k_values( + data, indices, indptr, output_values_ar, output_idx_ar, k, n_rows +): + gid = cuda.grid(1) + + if gid >= n_rows: + return + + row_st_ind = indptr[gid] + row_end_ind = indptr[gid + 1] - 1 + + k = min(k, 1 + row_end_ind - row_st_ind) + for i in range(0, k): + index = row_st_ind + i + if data[index] != 0: + output_values_ar[gid][i] = data[index] + output_idx_ar[gid][i] = indices[index] + + +def find_top_k_values_sparse_matrix(X, k): + + X = X.copy() + + ### Output arrays to save the top k values + values_ar = cp.full(fill_value=0, shape=(X.shape[0], k), dtype=cp.float64) + idx_ar = cp.full(fill_value=-1, shape=(X.shape[0], k), dtype=cp.int32) + + ### sort in decreasing order + X.data = X.data * -1 + sort_f( + (math.ceil(X.shape[0] / 32),), (32,), (X.data, X.indices, X.indptr, X.shape[0]) + ) + X.data = X.data * -1 + + ## configure kernel based on number of tasks + find_top_k_values_k = find_top_k_values.forall(X.shape[0]) + + find_top_k_values_k(X.data, X.indices, X.indptr, values_ar, idx_ar, k, X.shape[0]) + + return idx_ar, values_ar + + +def top_n_sparse(X, n): + """Return indices,values of top n values in each row of a sparse matrix + Args: + X: The sparse matrix from which to get the + top n indices and values per row + n: The number of highest values to extract from each row + Returns: + indices: The top n indices per row + values: The top n values per row + """ + value_ls, idx_ls = [], [] + batch_size = 500 + for s in range(0, X.shape[0], batch_size): + e = min(s + batch_size, X.shape[0]) + idx_ar, value_ar = find_top_k_values_sparse_matrix(X[s:e], n) + value_ls.append(value_ar) + idx_ls.append(idx_ar) + + indices = cp.concatenate(idx_ls) + values = cp.concatenate(value_ls) + + return indices, values \ No newline at end of file diff --git a/dynamo/tools/utils.py b/dynamo/tools/utils.py index f39ec026..0dd9e3b0 100755 --- a/dynamo/tools/utils.py +++ b/dynamo/tools/utils.py @@ -2718,6 +2718,7 @@ def get_ekey_vkey_from_adata(adata: AnnData) -> Tuple[str, str, str]: mapper = get_mapper() layer = [] + if has_splicing: if has_labeling: if "X_new" not in adata.layers.keys(): # unlabel spliced: S diff --git a/requirements.txt b/requirements.txt index 4cf7fc3d..b1e42bf0 100644 --- a/requirements.txt +++ b/requirements.txt @@ -25,3 +25,4 @@ openpyxl typing-extensions session-info>=1.0.0 adjustText +mudata diff --git a/setup.cfg b/setup.cfg index 37db253e..80760654 100644 --- a/setup.cfg +++ b/setup.cfg @@ -6,3 +6,6 @@ tag = True [bumpversion:file:setup.py] [bumpversion:file:docs/source/conf.py] + +[options.package_data] +* = multivelo/neural_nets/*