Skip to content

Commit

Permalink
clean up print_clonotypes (#409)
Browse files Browse the repository at this point in the history
Tidies up the implementation of print_clonotypes in preparation for additional refactoring.
* Replace mutable arguments with a return data structure.
* Refactor orbit traversal to use Result and a struct.
* Clean up field naming and remove needless level of Vec nesting.
* Remove unused gex_low value.
* Collect most gene_scan args to simplify calling.
* Excise some of the gene_scan stuff into enclone proper.
* Replace mutability with itertools.
* Use an option for the loupe clonotype.
  • Loading branch information
macklin-10x authored Mar 28, 2024
1 parent 1c48410 commit 9ff82a8
Show file tree
Hide file tree
Showing 11 changed files with 242 additions and 342 deletions.
2 changes: 1 addition & 1 deletion enclone_args/src/load_gex.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ pub fn get_gex_info(ctl: &mut EncloneControl) -> Result<GexInfo, String> {
&mut json_metrics,
&mut metrics,
)?;
if ctl.gen_opt.gene_scan_test.is_some() && !ctl.gen_opt.accept_inconsistent {
if ctl.gen_opt.gene_scan.is_some() && !ctl.gen_opt.accept_inconsistent {
let mut allf = gex_features.clone();
unique_sort(&mut allf);
if allf.len() != 1 {
Expand Down
16 changes: 4 additions & 12 deletions enclone_args/src/proc_args_post.rs
Original file line number Diff line number Diff line change
Expand Up @@ -425,7 +425,7 @@ pub fn proc_args_post(
"\nIf you use ALIGN_JALIGN_CONSISTENCY, you should also use PLAIN.\n".to_string(),
);
}
if ctl.gen_opt.gene_scan_exact && ctl.gen_opt.gene_scan_test.is_none() {
if ctl.gen_opt.gene_scan_exact && ctl.gen_opt.gene_scan.is_none() {
return Err(
"\nIt doesn't make sense to specify SCAN_EXIT unless SCAN is also specified.\n"
.to_string(),
Expand Down Expand Up @@ -680,17 +680,9 @@ pub fn proc_args_post(
for i in 0..ctl.clono_filt_opt.bounds.len() {
ctl.clono_filt_opt.bounds[i].require_valid_variables(ctl)?;
}
if ctl.gen_opt.gene_scan_test.is_some() {
ctl.gen_opt
.gene_scan_test
.as_ref()
.unwrap()
.require_valid_variables(ctl)?;
ctl.gen_opt
.gene_scan_control
.as_ref()
.unwrap()
.require_valid_variables(ctl)?;
if let Some(gene_scan_opts) = &ctl.gen_opt.gene_scan {
gene_scan_opts.test.require_valid_variables(ctl)?;
gene_scan_opts.control.require_valid_variables(ctl)?;
}
Ok(())
}
10 changes: 6 additions & 4 deletions enclone_args/src/process_special_arg2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
// Process a special argument, i.e. one that does not fit into a neat bucket.

use crate::proc_args2::{is_f64_arg, is_usize_arg};
use enclone_core::defs::EncloneControl;
use enclone_core::defs::{EncloneControl, GeneScanOpts};
use enclone_core::linear_condition::LinearCondition;
use enclone_core::{require_readable_file, tilde_expand_me};
use evalexpr::build_operator_tree;
Expand Down Expand Up @@ -363,15 +363,17 @@ pub fn process_special_arg2(
if x.len() != 3 {
return Err("\nArgument to SCAN must have three components.\n".to_string());
}
ctl.gen_opt.gene_scan_test = Some(LinearCondition::new(x[0])?);
ctl.gen_opt.gene_scan_control = Some(LinearCondition::new(x[1])?);
let threshold = LinearCondition::new(x[2])?;
for i in 0..threshold.var.len() {
if threshold.var[i] != *"t" && threshold.var[i] != *"c" {
return Err("\nIllegal variable in threshold for scan.\n".to_string());
}
}
ctl.gen_opt.gene_scan_threshold = Some(threshold);
ctl.gen_opt.gene_scan = Some(GeneScanOpts {
test: LinearCondition::new(x[0])?,
control: LinearCondition::new(x[1])?,
threshold,
});
} else if arg.starts_with("PLOT=") {
*using_plot = true;
let x = arg.after("PLOT=").split(',').collect::<Vec<&str>>();
Expand Down
11 changes: 8 additions & 3 deletions enclone_core/src/defs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -158,9 +158,7 @@ pub struct GeneralOpt {
pub summary_csv: bool,
pub cr_version: String,
pub nwarn: bool,
pub gene_scan_test: Option<LinearCondition>,
pub gene_scan_control: Option<LinearCondition>,
pub gene_scan_threshold: Option<LinearCondition>,
pub gene_scan: Option<GeneScanOpts>,
pub gene_scan_exact: bool,
pub clonotype_group_names: Option<String>,
pub origin_color_map: HashMap<String, String>,
Expand Down Expand Up @@ -251,6 +249,13 @@ pub struct GeneralOpt {
pub session_narrative: String,
}

#[derive(Clone, PartialEq)]
pub struct GeneScanOpts {
pub test: LinearCondition,
pub control: LinearCondition,
pub threshold: LinearCondition,
}

// Some plot options. Note that plot options are not allowed to affect intermediate computation.

#[derive(Clone, Default)]
Expand Down
15 changes: 9 additions & 6 deletions enclone_print/src/finish_table.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ pub struct Sr {
pub subrows: Vec<Vec<String>>,
}

/// Return the string "picture" of this clonotype.
/// Also mutates out_data in some unclear way.
pub fn finish_table(
n: usize,
ctl: &EncloneControl,
Expand All @@ -31,7 +33,6 @@ pub fn finish_table(
dref: &[DonorReferenceItem],
peer_groups: &[Vec<(usize, u8, u32)>],
mlog: &mut Vec<u8>,
logz: &mut String,
stats: &[(String, Vec<String>)],
sr: Vec<Sr>,
extra_args: &[String],
Expand All @@ -40,7 +41,7 @@ pub fn finish_table(
rord: &[usize],
pass: usize,
cdr3_con: &[Vec<u8>],
) {
) -> String {
// Fill in exact_subclonotype_id, reorder.

let nexacts = exacts.len();
Expand Down Expand Up @@ -242,7 +243,8 @@ pub fn finish_table(
justify.push(justification(&rsi.cvars[cx][m]));
}
}
make_table(ctl, &mut rows, &justify, mlog, logz);
let mut logz = String::new();
make_table(ctl, &mut rows, &justify, mlog, &mut logz);

// Add phylogeny.

Expand Down Expand Up @@ -304,9 +306,9 @@ pub fn finish_table(
}
if (d1 == 0) ^ (d2 == 0) {
if d1 == 0 {
write!(*logz, "{} ==> {}", u1 + 1, u2 + 1).unwrap();
write!(logz, "{} ==> {}", u1 + 1, u2 + 1).unwrap();
} else {
write!(*logz, "{} ==> {}", u2 + 1, u1 + 1).unwrap();
write!(logz, "{} ==> {}", u2 + 1, u1 + 1).unwrap();
}
let s = format!(
"; u1 = {}, u2 = {}, d1 = {}, d2 = {}, d = {}\n",
Expand All @@ -316,9 +318,10 @@ pub fn finish_table(
d2,
d
);
*logz += &s;
logz += &s;
}
}
}
}
logz
}
183 changes: 84 additions & 99 deletions enclone_print/src/gene_scan.rs
Original file line number Diff line number Diff line change
@@ -1,121 +1,106 @@
// Copyright (c) 2021 10X Genomics, Inc. All rights reserved.

use enclone_core::defs::EncloneControl;
use enclone_core::{defs::GeneScanOpts, linear_condition::LinearCondition};

pub struct InSet {
pub test: bool,
pub control: bool,
}

pub fn gene_scan_test(
ctl: &EncloneControl,
opts: &GeneScanOpts,
exact: bool,
stats: &[(String, Vec<String>)],
stats_orig: &[(String, Vec<String>)],
nexacts: usize,
n: usize,
in_test: &mut Vec<bool>,
in_control: &mut Vec<bool>,
) {
) -> Vec<InSet> {
// See if we're in the test and control sets for gene scan (non-exact case).

if let Some(ref scan_test) = ctl.gen_opt.gene_scan_test {
if !ctl.gen_opt.gene_scan_exact {
let x = scan_test;
let means = x
.var
.iter()
.take(x.n())
.map(|xn| {
stats
.iter()
.find_map(|stat| {
if stat.0 == *xn {
Some(
stat.1
.iter()
.filter_map(|k| k.parse::<f64>().ok())
.sum::<f64>(),
)
} else {
None
}
})
.unwrap_or_default()
/ n as f64
})
.collect::<Vec<_>>();

in_test.push(x.satisfied(&means));
let x = ctl.gen_opt.gene_scan_control.as_ref().unwrap();
let means = x
.var
.iter()
.take(x.n())
.map(|xn| {
stats
.iter()
.find_map(|stat| {
if stat.0 == *xn {
Some(
stat.1
.iter()
.filter_map(|k| k.parse::<f64>().ok())
.sum::<f64>(),
)
} else {
None
}
})
.unwrap_or_default()
/ n as f64
})
.collect::<Vec<_>>();
in_control.push(x.satisfied(&means));
}
}

// See if we're in the test and control sets for gene scan (exact case).

if ctl.gen_opt.gene_scan_test.is_some() && ctl.gen_opt.gene_scan_exact {
let x = ctl.gen_opt.gene_scan_test.clone().unwrap();
for k in 0..nexacts {
let mut means = Vec::<f64>::new();
for xn in x.var.iter().take(x.n()) {
let mut vals = Vec::<f64>::new();
let mut count = 0;
for stat in stats_orig {
if stat.0 == *xn {
if count == k {
for k in &stat.1 {
if let Ok(v) = k.parse::<f64>() {
vals.push(v);
if !exact {
let in_set = |cond: &LinearCondition| {
cond.satisfied(
&cond
.var
.iter()
.take(cond.n())
.map(|xn| {
stats
.iter()
.find_map(|stat| {
if stat.0 == *xn {
Some(
stat.1
.iter()
.filter_map(|k| k.parse::<f64>().ok())
.sum::<f64>(),
)
} else {
None
}
})
.unwrap_or_default()
/ n as f64
})
.collect::<Vec<_>>(),
)
};
vec![InSet {
test: in_set(&opts.test),
control: in_set(&opts.control),
}]
} else {
// See if we're in the test and control sets for gene scan (exact case).
(0..nexacts)
.map(|k| {
let x = &opts.test;
let mut means = Vec::<f64>::new();
for xn in x.var.iter().take(x.n()) {
let mut vals = Vec::<f64>::new();
let mut count = 0;
for stat in stats_orig {
if stat.0 == *xn {
if count == k {
for k in &stat.1 {
if let Ok(v) = k.parse::<f64>() {
vals.push(v);
}
}
break;
}
break;
count += 1;
}
count += 1;
}
let n = vals.len() as f64;
means.push(vals.into_iter().sum::<f64>() / n);
}
let n = vals.len() as f64;
means.push(vals.into_iter().sum::<f64>() / n);
}
in_test.push(x.satisfied(&means));
let x = ctl.gen_opt.gene_scan_control.clone().unwrap();
let mut means = Vec::<f64>::new();
for xn in x.var.iter().take(x.n()) {
let mut vals = Vec::<f64>::new();
let mut count = 0;
for stat in stats_orig {
if stat.0 == *xn {
if count == k {
for k in &stat.1 {
if let Ok(v) = k.parse::<f64>() {
vals.push(v);
let in_test = x.satisfied(&means);
let x = &opts.control;
let mut means = Vec::<f64>::new();
for xn in x.var.iter().take(x.n()) {
let mut vals = Vec::<f64>::new();
let mut count = 0;
for stat in stats_orig {
if stat.0 == *xn {
if count == k {
for k in &stat.1 {
if let Ok(v) = k.parse::<f64>() {
vals.push(v);
}
}
break;
}
break;
count += 1;
}
count += 1;
}
means.push(vals.into_iter().sum::<f64>() / n as f64);
}
let in_control = x.satisfied(&means);
InSet {
test: in_test,
control: in_control,
}
means.push(vals.into_iter().sum::<f64>() / n as f64);
}
in_control.push(x.satisfied(&means));
}
})
.collect()
}
}
Loading

0 comments on commit 9ff82a8

Please sign in to comment.