use std::ffi::{CStr, CString};
use std::io::Write;
use std::os::raw::c_int;
use std::sync::Mutex;
use crate::error::{Primer3Error, Result};
use crate::init::ensure_initialized;
use crate::results::{DesignResult, OligoStats, OligoType, PairStats, PrimerPair, PrimerRecord};
use crate::seq_lib::SequenceLibrary;
use crate::sequence::{Interval, SequenceArgs};
use crate::settings::{PrimerSettings, PrimerTask};
use crate::utils::reverse_complement;
static DESIGN_MUTEX: Mutex<()> = Mutex::new(());
const MAX_INTERVALS: usize = 200;
pub fn design_primers(
seq_args: &SequenceArgs,
settings: &PrimerSettings,
misprime_lib: Option<&SequenceLibrary>,
mishyb_lib: Option<&SequenceLibrary>,
) -> Result<DesignResult> {
ensure_initialized()?;
let _lock = DESIGN_MUTEX
.lock()
.map_err(|e| Primer3Error::Library(format!("mutex poisoned: {e}").into_boxed_str()))?;
design_primers_locked(seq_args, settings, misprime_lib, mishyb_lib)
}
fn make_cstring(s: &str, field_name: &str) -> Result<CString> {
CString::new(s).map_err(|_| {
Primer3Error::InvalidSequence(format!("{field_name} contains null byte").into_boxed_str())
})
}
unsafe fn read_pr_append_str(s: &primer3_sys::pr_append_str) -> Option<String> {
if s.data.is_null() {
return None;
}
let msg = unsafe { CStr::from_ptr(s.data) }.to_string_lossy();
if msg.is_empty() { None } else { Some(msg.into_owned()) }
}
fn copy_intervals(dest: &mut primer3_sys::interval_array_t2, intervals: &[Interval]) {
let count = intervals.len().min(MAX_INTERVALS);
dest.count = count as c_int;
for (i, iv) in intervals.iter().take(MAX_INTERVALS).enumerate() {
dest.pairs[i][0] = iv.start as c_int;
dest.pairs[i][1] = iv.length as c_int;
}
}
fn build_c_seq_lib(
lib: Option<&SequenceLibrary>,
errfrag: &str,
) -> Result<*mut primer3_sys::seq_lib> {
let lib = match lib {
Some(l) if !l.is_empty() => l,
_ => return Ok(std::ptr::null_mut()),
};
let mut tmpfile = tempfile::NamedTempFile::new().map_err(|e| {
Primer3Error::Library(format!("failed to create temp file for seq_lib: {e}").into())
})?;
for entry in lib.iter() {
if (entry.weight - 1.0).abs() < f64::EPSILON {
writeln!(tmpfile, ">{}\n{}", entry.name, entry.sequence)
} else {
writeln!(tmpfile, ">{}*{}\n{}", entry.name, entry.weight, entry.sequence)
}
.map_err(|e| {
Primer3Error::Library(format!("failed to write seq_lib temp file: {e}").into())
})?;
}
tmpfile.flush().map_err(|e| {
Primer3Error::Library(format!("failed to flush seq_lib temp file: {e}").into())
})?;
let path_cstring = make_cstring(
tmpfile
.path()
.to_str()
.ok_or_else(|| Primer3Error::Library("temp file path is not valid UTF-8".into()))?,
"seq_lib path",
)?;
let errfrag_cstring = make_cstring(errfrag, "seq_lib errfrag")?;
let c_lib = unsafe {
primer3_sys::read_and_create_seq_lib(path_cstring.as_ptr(), errfrag_cstring.as_ptr())
};
if c_lib.is_null() {
return Err(Primer3Error::Library(
format!("failed to allocate seq_lib for {errfrag}").into(),
));
}
let error_msg = unsafe { read_pr_append_str(&(*c_lib).error) };
if let Some(msg) = error_msg {
unsafe { primer3_sys::destroy_seq_lib(c_lib) };
return Err(Primer3Error::Library(format!("{errfrag}: {msg}").into_boxed_str()));
}
Ok(c_lib)
}
#[allow(clippy::too_many_lines)]
fn design_primers_locked(
seq_args: &SequenceArgs,
settings: &PrimerSettings,
misprime_lib: Option<&SequenceLibrary>,
mishyb_lib: Option<&SequenceLibrary>,
) -> Result<DesignResult> {
let seq_cstring = make_cstring(&seq_args.sequence, "sequence")?;
let name_cstring =
seq_args.sequence_id.as_deref().map(|n| make_cstring(n, "sequence_id")).transpose()?;
let left_cstring =
seq_args.left_primer.as_deref().map(|s| make_cstring(s, "left_primer")).transpose()?;
let right_cstring =
seq_args.right_primer.as_deref().map(|s| make_cstring(s, "right_primer")).transpose()?;
let internal_cstring = seq_args
.internal_oligo
.as_deref()
.map(|s| make_cstring(s, "internal_oligo"))
.transpose()?;
let overhang_left_cstring =
seq_args.overhang_left.as_deref().map(|s| make_cstring(s, "overhang_left")).transpose()?;
let overhang_right_cstring = seq_args
.overhang_right
.as_deref()
.map(|s| make_cstring(s, "overhang_right"))
.transpose()?;
let primer_must_match_5 = settings
.primer
.must_match_five_prime
.as_deref()
.map(|s| make_cstring(s, "primer must_match_five_prime"))
.transpose()?;
let primer_must_match_3 = settings
.primer
.must_match_three_prime
.as_deref()
.map(|s| make_cstring(s, "primer must_match_three_prime"))
.transpose()?;
let internal_must_match_5 = settings
.internal_oligo
.must_match_five_prime
.as_deref()
.map(|s| make_cstring(s, "internal must_match_five_prime"))
.transpose()?;
let internal_must_match_3 = settings
.internal_oligo
.must_match_three_prime
.as_deref()
.map(|s| make_cstring(s, "internal must_match_three_prime"))
.transpose()?;
let gs = unsafe { primer3_sys::p3_create_global_settings() };
if gs.is_null() {
return Err(Primer3Error::Library("failed to allocate p3_global_settings".into()));
}
configure_global_settings(gs, settings);
unsafe {
if let Some(ref c) = primer_must_match_5 {
(*gs).p_args.must_match_five_prime = c.as_ptr().cast_mut();
}
if let Some(ref c) = primer_must_match_3 {
(*gs).p_args.must_match_three_prime = c.as_ptr().cast_mut();
}
if let Some(ref c) = internal_must_match_5 {
(*gs).o_args.must_match_five_prime = c.as_ptr().cast_mut();
}
if let Some(ref c) = internal_must_match_3 {
(*gs).o_args.must_match_three_prime = c.as_ptr().cast_mut();
}
}
let c_misprime_lib = build_c_seq_lib(misprime_lib, "mispriming library").inspect_err(|_| {
unsafe { primer3_sys::p3_destroy_global_settings(gs) };
})?;
let c_mishyb_lib =
build_c_seq_lib(mishyb_lib, "mishybridization library").inspect_err(|_| {
if !c_misprime_lib.is_null() {
unsafe { primer3_sys::destroy_seq_lib(c_misprime_lib) };
}
unsafe { primer3_sys::p3_destroy_global_settings(gs) };
})?;
unsafe {
(*gs).p_args.repeat_lib = c_misprime_lib;
(*gs).o_args.repeat_lib = c_mishyb_lib;
}
let sa = unsafe { primer3_sys::create_seq_arg() };
if sa.is_null() {
unsafe { primer3_sys::p3_destroy_global_settings(gs) };
return Err(Primer3Error::Library("failed to allocate seq_args".into()));
}
unsafe {
primer3_sys::p3_set_sa_sequence(sa, seq_cstring.as_ptr());
if let Some(ref c) = name_cstring {
primer3_sys::p3_set_sa_sequence_name(sa, c.as_ptr());
}
primer3_sys::p3_set_sa_incl_s(sa, seq_args.included_region_start as c_int);
let incl_len = seq_args.included_region_length.unwrap_or(seq_args.sequence.len());
primer3_sys::p3_set_sa_incl_l(sa, incl_len as c_int);
}
configure_seq_args_intervals(sa, seq_args);
unsafe {
if let Some(ref c) = left_cstring {
primer3_sys::p3_set_sa_left_input(sa, c.as_ptr());
}
if let Some(ref c) = right_cstring {
primer3_sys::p3_set_sa_right_input(sa, c.as_ptr());
}
if let Some(ref c) = internal_cstring {
primer3_sys::p3_set_sa_internal_input(sa, c.as_ptr());
}
(*sa).force_left_start = seq_args.force_left_start.map_or(-1, |v| v as c_int);
(*sa).force_left_end = seq_args.force_left_end.map_or(-1, |v| v as c_int);
(*sa).force_right_start = seq_args.force_right_start.map_or(-1, |v| v as c_int);
(*sa).force_right_end = seq_args.force_right_end.map_or(-1, |v| v as c_int);
}
if let Some(ref quality) = seq_args.quality {
unsafe {
primer3_sys::p3_set_sa_empty_quality(sa);
for &q in quality {
primer3_sys::p3_sa_add_to_quality_array(sa, q);
}
(*sa).n_quality = quality.len() as c_int;
}
}
for &pos in seq_args.primer_overlap_junctions.iter().take(MAX_INTERVALS) {
unsafe {
primer3_sys::p3_sa_add_to_overlap_junctions_array(sa, pos as c_int);
}
}
unsafe {
let count = seq_args.internal_overlap_junctions.len().min(MAX_INTERVALS);
(*sa).intl_overlap_junctions_count = count as c_int;
for (i, &pos) in seq_args.internal_overlap_junctions.iter().take(MAX_INTERVALS).enumerate()
{
(*sa).intl_overlap_junctions[i] = pos as c_int;
}
}
unsafe {
if let Some(ref c) = overhang_left_cstring {
(*sa).overhang_left = c.as_ptr().cast_mut();
}
if let Some(ref c) = overhang_right_cstring {
(*sa).overhang_right = c.as_ptr().cast_mut();
}
}
let retval = unsafe { primer3_sys::choose_primers(gs, sa) };
unsafe {
(*gs).p_args.must_match_five_prime = std::ptr::null_mut();
(*gs).p_args.must_match_three_prime = std::ptr::null_mut();
(*gs).o_args.must_match_five_prime = std::ptr::null_mut();
(*gs).o_args.must_match_three_prime = std::ptr::null_mut();
(*sa).overhang_left = std::ptr::null_mut();
(*sa).overhang_right = std::ptr::null_mut();
}
if retval.is_null() {
unsafe {
primer3_sys::destroy_seq_args(sa);
primer3_sys::p3_destroy_global_settings(gs);
}
return Err(Primer3Error::Library("choose_primers returned null".into()));
}
let result = extract_results(retval, &seq_args.sequence);
unsafe {
primer3_sys::destroy_p3retval(retval);
primer3_sys::destroy_seq_args(sa);
primer3_sys::p3_destroy_global_settings(gs);
}
result
}
fn configure_global_settings(gs: *mut primer3_sys::p3_global_settings, s: &PrimerSettings) {
unsafe {
(*gs).primer_task = match s.task {
PrimerTask::PickPcrPrimers => primer3_sys::task_pick_pcr_primers,
PrimerTask::PickPcrPrimersAndHybProbe => {
primer3_sys::task_pick_pcr_primers_and_hyb_probe
}
PrimerTask::PickLeftOnly => primer3_sys::task_pick_left_only,
PrimerTask::PickRightOnly => primer3_sys::task_pick_right_only,
PrimerTask::PickHybProbeOnly => primer3_sys::task_pick_hyb_probe_only,
PrimerTask::Generic => primer3_sys::task_generic,
PrimerTask::PickCloningPrimers => primer3_sys::task_pick_cloning_primers,
PrimerTask::PickDiscriminativePrimers => primer3_sys::task_pick_discriminative_primers,
PrimerTask::PickSequencingPrimers => primer3_sys::task_pick_sequencing_primers,
PrimerTask::PickPrimerList => primer3_sys::task_pick_primer_list,
PrimerTask::CheckPrimers => primer3_sys::task_check_primers,
};
(*gs).pick_left_primer = i32::from(s.pick_left_primer);
(*gs).pick_right_primer = i32::from(s.pick_right_primer);
(*gs).pick_internal_oligo = i32::from(s.pick_internal_oligo);
primer3_sys::p3_set_gs_primer_num_return(gs, s.num_return as c_int);
primer3_sys::p3_set_gs_primer_pick_anyway(gs, i32::from(s.pick_anyway));
primer3_sys::p3_set_gs_primer_liberal_base(gs, i32::from(s.liberal_base));
primer3_sys::p3_set_gs_primer_first_base_index(gs, s.first_base_index as c_int);
primer3_sys::p3_set_gs_primer_tm_santalucia(gs, s.tm_method.to_c());
primer3_sys::p3_set_gs_primer_salt_corrections(gs, s.salt_correction_method.to_c());
(*gs).max_end_stability = s.max_end_stability;
(*gs).max_end_gc = s.max_end_gc as c_int;
(*gs).gc_clamp = s.gc_clamp as c_int;
(*gs).thermodynamic_oligo_alignment = i32::from(s.thermodynamic_oligo_alignment);
(*gs).thermodynamic_template_alignment = i32::from(s.thermodynamic_template_alignment);
(*gs).lowercase_masking = i32::from(s.lowercase_masking);
let num_ranges = s.product_size_ranges.len().min(MAX_INTERVALS);
(*gs).num_intervals = num_ranges as c_int;
for (i, &(min, max)) in s.product_size_ranges.iter().take(MAX_INTERVALS).enumerate() {
primer3_sys::p3_set_gs_prmin(gs, min as c_int, i as c_int);
primer3_sys::p3_set_gs_prmax(gs, max as c_int, i as c_int);
}
primer3_sys::p3_set_gs_primer_product_opt_size(gs, s.product_opt_size as c_int);
primer3_sys::p3_set_gs_primer_product_max_tm(gs, s.product_max_tm);
primer3_sys::p3_set_gs_primer_product_min_tm(gs, s.product_min_tm);
primer3_sys::p3_set_gs_primer_product_opt_tm(gs, s.product_opt_tm);
(*gs).pair_compl_any = s.pair_max_compl_any;
(*gs).pair_compl_end = s.pair_max_compl_end;
(*gs).pair_compl_any_th = s.pair_max_compl_any_th;
(*gs).pair_compl_end_th = s.pair_max_compl_end_th;
(*gs).max_diff_tm = s.max_diff_tm;
(*gs).pair_repeat_compl = s.pair_repeat_compl;
(*gs).min_left_three_prime_distance = s.min_left_three_prime_distance;
(*gs).min_right_three_prime_distance = s.min_right_three_prime_distance;
(*gs).annealing_temp = s.annealing_temp;
(*gs).sequencing.lead = s.sequencing.lead as c_int;
(*gs).sequencing.spacing = s.sequencing.spacing as c_int;
(*gs).sequencing.interval = s.sequencing.interval as c_int;
(*gs).sequencing.accuracy = s.sequencing.accuracy as c_int;
(*gs).pr_pair_weights.primer_quality = s.pair_weights.primer_quality;
(*gs).pr_pair_weights.io_quality = s.pair_weights.io_quality;
(*gs).pr_pair_weights.diff_tm = s.pair_weights.diff_tm;
(*gs).pr_pair_weights.compl_any = s.pair_weights.compl_any;
(*gs).pr_pair_weights.compl_any_th = s.pair_weights.compl_any_th;
(*gs).pr_pair_weights.compl_end = s.pair_weights.compl_end;
(*gs).pr_pair_weights.compl_end_th = s.pair_weights.compl_end_th;
(*gs).pr_pair_weights.product_tm_lt = s.pair_weights.product_tm_lt;
(*gs).pr_pair_weights.product_tm_gt = s.pair_weights.product_tm_gt;
(*gs).pr_pair_weights.product_size_lt = s.pair_weights.product_size_lt;
(*gs).pr_pair_weights.product_size_gt = s.pair_weights.product_size_gt;
(*gs).pr_pair_weights.repeat_sim = s.pair_weights.repeat_sim;
(*gs).pr_pair_weights.template_mispriming = s.pair_weights.template_mispriming;
(*gs).pr_pair_weights.template_mispriming_th = s.pair_weights.template_mispriming_th;
configure_oligo_settings(&mut (*gs).p_args, &s.primer);
configure_oligo_settings(&mut (*gs).o_args, &s.internal_oligo);
}
}
fn configure_oligo_settings(
args: &mut primer3_sys::args_for_one_oligo_or_primer,
s: &crate::settings::OligoSettings,
) {
args.opt_size = s.opt_size as c_int;
args.min_size = s.min_size as c_int;
args.max_size = s.max_size as c_int;
args.opt_tm = s.opt_tm;
args.min_tm = s.min_tm;
args.max_tm = s.max_tm;
args.opt_bound = s.opt_bound;
args.min_bound = s.min_bound;
args.max_bound = s.max_bound;
args.opt_gc_content = s.opt_gc_content;
args.min_gc = s.min_gc;
args.max_gc = s.max_gc;
args.salt_conc = s.conditions.mv_conc;
args.divalent_conc = s.conditions.dv_conc;
args.dntp_conc = s.conditions.dntp_conc;
args.dna_conc = s.conditions.dna_conc;
args.dmso_conc = s.conditions.dmso_conc;
args.dmso_fact = s.conditions.dmso_fact;
args.formamide_conc = s.conditions.formamide_conc;
args.max_self_any = s.max_self_any;
args.max_self_end = s.max_self_end;
args.max_self_any_th = s.max_self_any_th;
args.max_self_end_th = s.max_self_end_th;
args.max_hairpin_th = s.max_hairpin_th;
args.max_repeat_compl = s.max_repeat_compl;
args.num_ns_accepted = s.num_ns_accepted as c_int;
args.max_poly_x = s.max_poly_x as c_int;
let w = &s.weights;
args.weights.temp_gt = w.temp_gt;
args.weights.temp_lt = w.temp_lt;
args.weights.bound_gt = w.bound_gt;
args.weights.bound_lt = w.bound_lt;
args.weights.gc_content_gt = w.gc_content_gt;
args.weights.gc_content_lt = w.gc_content_lt;
args.weights.length_gt = w.size_gt;
args.weights.length_lt = w.size_lt;
args.weights.compl_any = w.compl_any;
args.weights.compl_any_th = w.compl_any_th;
args.weights.compl_end = w.compl_end;
args.weights.compl_end_th = w.compl_end_th;
args.weights.hairpin_th = w.hairpin_th;
args.weights.num_ns = w.num_ns;
args.weights.repeat_sim = w.repeat_sim;
args.weights.seq_quality = w.seq_quality;
args.weights.end_quality = w.end_quality;
args.weights.pos_penalty = w.pos_penalty;
args.weights.end_stability = w.end_stability;
args.weights.template_mispriming = w.template_mispriming;
args.weights.template_mispriming_th = w.template_mispriming_th;
args.weights.failure_rate = w.failure_rate;
}
fn configure_seq_args_intervals(sa: *mut primer3_sys::seq_args, seq_args: &SequenceArgs) {
unsafe {
copy_intervals(&mut (*sa).tar2, &seq_args.targets);
copy_intervals(&mut (*sa).excl2, &seq_args.excluded_regions);
copy_intervals(&mut (*sa).excl_internal2, &seq_args.internal_excluded_regions);
let ok_count = seq_args.ok_regions.len().min(MAX_INTERVALS);
(*sa).ok_regions.count = ok_count as c_int;
(*sa).ok_regions.any_left = 0;
(*sa).ok_regions.any_right = 0;
(*sa).ok_regions.any_pair = 0;
for (i, ok) in seq_args.ok_regions.iter().take(MAX_INTERVALS).enumerate() {
let (ls, ll) = ok.left.map_or((-1, -1), |iv| (iv.start as i32, iv.length as i32));
let (rs, rl) = ok.right.map_or((-1, -1), |iv| (iv.start as i32, iv.length as i32));
(*sa).ok_regions.left_pairs[i][0] = ls;
(*sa).ok_regions.left_pairs[i][1] = ll;
(*sa).ok_regions.right_pairs[i][0] = rs;
(*sa).ok_regions.right_pairs[i][1] = rl;
if ls == -1 && ll == -1 {
(*sa).ok_regions.any_left = 1;
}
if rs == -1 && rl == -1 {
(*sa).ok_regions.any_right = 1;
}
if (ls == -1 && ll == -1) || (rs == -1 && rl == -1) {
(*sa).ok_regions.any_pair = 1;
}
}
}
}
fn extract_results(retval: *const primer3_sys::p3retval, template: &str) -> Result<DesignResult> {
unsafe {
if let Some(msg) = read_pr_append_str(&(*retval).glob_err) {
return Err(Primer3Error::DesignGlobalError(msg.into_boxed_str()));
}
if let Some(msg) = read_pr_append_str(&(*retval).per_sequence_err) {
return Err(Primer3Error::DesignSequenceError(msg.into_boxed_str()));
}
let warnings = read_pr_append_str(&(*retval).warnings);
let pairs = extract_pairs(retval, template);
let left_primers = extract_oligo_array(&(*retval).fwd, OligoType::Left, template);
let right_primers = extract_oligo_array(&(*retval).rev, OligoType::Right, template);
let internal_oligos = extract_oligo_array(&(*retval).intl, OligoType::Internal, template);
let left_stats = convert_oligo_stats(&(*retval).fwd.expl);
let right_stats = convert_oligo_stats(&(*retval).rev.expl);
let internal_stats = convert_oligo_stats(&(*retval).intl.expl);
let pair_stats = convert_pair_stats(&(*retval).best_pairs);
Ok(DesignResult::new(
pairs,
left_primers,
right_primers,
internal_oligos,
warnings,
left_stats,
right_stats,
internal_stats,
pair_stats,
))
}
}
fn extract_pairs(retval: *const primer3_sys::p3retval, template: &str) -> Vec<PrimerPair> {
unsafe {
let pair_array = &(*retval).best_pairs;
let num_pairs = pair_array.num_pairs as usize;
let mut pairs = Vec::with_capacity(num_pairs);
for i in 0..num_pairs {
let c_pair = &*pair_array.pairs.add(i);
let left = extract_primer_rec(&*c_pair.left, OligoType::Left, template);
let right = extract_primer_rec(&*c_pair.right, OligoType::Right, template);
let internal = if c_pair.intl.is_null() {
None
} else {
Some(extract_primer_rec(&*c_pair.intl, OligoType::Internal, template))
};
pairs.push(PrimerPair::new(
left,
right,
internal,
c_pair.pair_quality,
c_pair.product_size as usize,
c_pair.product_tm,
c_pair.diff_tm,
c_pair.compl_any,
c_pair.compl_end,
));
}
pairs
}
}
fn extract_primer_rec(
rec: &primer3_sys::primer_rec,
oligo_type: OligoType,
template: &str,
) -> PrimerRecord {
let start = rec.start as usize;
let length = rec.length as usize;
let sequence = match oligo_type {
OligoType::Left | OligoType::Internal => {
template.get(start..start + length).unwrap_or("").to_uppercase()
}
OligoType::Right => {
let left_pos = (start + 1).saturating_sub(length);
#[allow(clippy::range_plus_one)]
let fwd = template.get(left_pos..start + 1).unwrap_or("");
reverse_complement(&fwd.to_uppercase())
}
};
PrimerRecord::new(
oligo_type,
sequence,
start,
length,
rec.temp,
rec.bound,
rec.gc_content,
rec.self_any,
rec.self_end,
rec.hairpin_th,
rec.end_stability,
rec.quality,
None,
)
}
fn extract_oligo_array(
array: &primer3_sys::oligo_array,
oligo_type: OligoType,
template: &str,
) -> Vec<PrimerRecord> {
let count = array.num_elem as usize;
let mut records = Vec::with_capacity(count);
for i in 0..count {
let rec = unsafe { &*array.oligo.add(i) };
records.push(extract_primer_rec(rec, oligo_type, template));
}
records
}
fn convert_oligo_stats(stats: &primer3_sys::oligo_stats) -> OligoStats {
OligoStats {
considered: stats.considered as usize,
ns: stats.ns as usize,
target: stats.target as usize,
excluded: stats.excluded as usize,
gc: stats.gc as usize,
gc_clamp: stats.gc_clamp as usize,
gc_end_high: stats.gc_end_high as usize,
temp_min: stats.temp_min as usize,
temp_max: stats.temp_max as usize,
bound_min: stats.bound_min as usize,
bound_max: stats.bound_max as usize,
size_min: stats.size_min as usize,
size_max: stats.size_max as usize,
compl_any: stats.compl_any as usize,
compl_end: stats.compl_end as usize,
hairpin: stats.hairpin_th as usize,
repeat_score: stats.repeat_score as usize,
poly_x: stats.poly_x as usize,
seq_quality: stats.seq_quality as usize,
stability: stats.stability as usize,
template_mispriming: stats.template_mispriming as usize,
ok: stats.ok as usize,
masked: stats.gmasked as usize,
must_match_fail: stats.must_match_fail as usize,
not_in_any_ok_region: stats.not_in_any_left_ok_region as usize,
does_not_overlap_required_point: stats.does_not_overlap_a_required_point as usize,
}
}
fn convert_pair_stats(pair_array: &primer3_sys::pair_array_t) -> PairStats {
let s = &pair_array.expl;
PairStats {
considered: s.considered as usize,
product: s.product as usize,
target: s.target as usize,
temp_diff: s.temp_diff as usize,
compl_any: s.compl_any as usize,
compl_end: s.compl_end as usize,
internal: s.internal as usize,
repeat_sim: s.repeat_sim as usize,
high_tm: s.high_tm as usize,
low_tm: s.low_tm as usize,
template_mispriming: s.template_mispriming as usize,
does_not_overlap_required_point: s.does_not_overlap_a_required_point as usize,
not_in_any_ok_region: s.not_in_any_ok_region as usize,
ok: s.ok as usize,
}
}