def pcr_noise( read_counts: np.ndarray, pcr_betas: Union[float, np.ndarray], n_cycles: int, copy: bool = True, ) -> np.ndarray: """PCR noise model: every read has an affinity for PCR, and for every round of PCR we do a ~binomial doubling of each count. :param read_counts: array of shape (n_samples, n_features) representing unique molecules (e.g. genes or gene fragments). If a sparse matrix is provided, the output will be sparse :param pcr_betas: PCR efficiency for each feature, either constant or per-feature :param n_cycles: number of rounds of PCR to simulate :param copy: if True, return a copy of the read_counts array, else modify in-place :return: int array of shape (n_samples, n_features) with amplified counts """ if np.any(pcr_betas < 0): raise ValueError("pcr_betas must be non-negative") if copy: read_counts = read_counts.copy() pcr_betas = np.broadcast_to(pcr_betas, (1, read_counts.shape[1])) if sp.issparse(read_counts): d = read_counts.data[None, :] pcr_betas = pcr_betas[:, read_counts.nonzero()[1]] else: d = read_counts # for each round of pcr, each gene increases according to its affinity factor for i in range(n_cycles): d += np.random.binomial(n=d, p=pcr_betas, size=d.shape) if sp.issparse(read_counts): read_counts.data = d.flatten() return read_counts