Skip to content

Commit

Permalink
Work regarding salt con
Browse files Browse the repository at this point in the history
  • Loading branch information
David O'Connor committed Aug 30, 2024
1 parent f690fd6 commit ec76406
Show file tree
Hide file tree
Showing 8 changed files with 419 additions and 33 deletions.
7 changes: 6 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ sequence on this end. The end point can then be adjusted to optimize primer qual
When both ends are marked as tunable, PlasCAD assumes this primer is for attaching two DNA fragments together, as in SLIC and FC
cloning.

![Primer view screenshot](screenshots/primers_aug_24.png)


### Primer generation for SLIC and FastCloning
Given the sequences for an insert, a vector, and insertion point, it will generate primers suitable for SLIC and FastCloning.
Expand Down Expand Up @@ -150,6 +152,9 @@ a fast, lightweight program that's as easy to use as possible, without sacrifici

Also of note, the native file format this program uses is more compact, including for DNA sequences, where each nucleotide only takes up 2 bits, as opposed to 8 in common formats.

Performance is a top priority; compared to other tools, this has a small program size, small memory footprint, and low CPU use.
It starts fast, and responds instantly.


## Near-term plans
- QCing plasmids for toxic proteins, hydrophobic regions, and aggregation-prone regions
Expand All @@ -167,7 +172,7 @@ primer Molar concentration:

$$ (1000 * ΔH) / (ΔS + R \times ln(\frac{C_T}{4})) - 273.15 $$

The calculation also includes salt correction, derived from BioPython, using concentrations of $K^+$, $Na^+$, $Mg^{2+}$, and dntp concentration. These are provided by the user, or initiated with defaults.
The calculation also includes salt correction, derived from BioPython, using concentrations of $K^+$, $Na^+$, $Mg^{2+}$, and dntp concentration. (SantaLucia, 1998) These ion concentrations are provided by the user, or initiated with defaults.

We score primer length compared to an ideal of 18-24 nucleotides. Note that for cloning primers that join sequences (eg SLIC insert primers), length is scored between each end and the anchor (The point the two sequences are joined at, towards the middle of the primer.) We expect these primers to be about twice the length of normal primers.

Expand Down
Binary file added screenshots/primers_aug_24.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
13 changes: 10 additions & 3 deletions src/gui/circle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ use core::f32::consts::TAU;

use eframe::{
egui::{
pos2, vec2, Align2, Color32, FontFamily, FontId, Frame, Pos2, Rect, RichText, ScrollArea,
Sense, Shape, Slider, Stroke, Ui,
pos2, vec2, Align2, Color32, CursorIcon, FontFamily, FontId, Frame, Pos2, Rect, RichText,
ScrollArea, Sense, Shape, Slider, Stroke, Ui,
},
emath::RectTransform,
epaint::{CircleShape, PathShape},
Expand Down Expand Up @@ -63,7 +63,7 @@ const CIRCLE_SIZE_RATIO: f32 = 0.42;

// The maximum distance the cursor can be from the circle for various tasks like selection, finding
// the cursor position etc.
const SELECTION_MAX_DIST: f32 = 100.;
const SELECTION_MAX_DIST: f32 = 80.;

const CENTER_TEXT_ROW_SPACING: f32 = 20.;

Expand Down Expand Up @@ -1000,6 +1000,13 @@ pub fn circle_page(state: &mut State, ui: &mut Ui) {
});
}

ui.ctx()
.set_cursor_icon(if state.ui.feature_hover.is_some() {
CursorIcon::PointingHand
} else {
CursorIcon::Default
});

Frame::canvas(ui.style())
.fill(BACKGROUND_COLOR)
.show(ui, |ui| {
Expand Down
13 changes: 11 additions & 2 deletions src/gui/feature_table.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
//! GUI code for the features editor and related.
use eframe::egui::{Color32, ComboBox, Frame, RichText, ScrollArea, Stroke, TextEdit, Ui};
use eframe::egui::{
Color32, ComboBox, CursorIcon, Frame, RichText, ScrollArea, Sense, Stroke, TextEdit, Ui,
};

use crate::{
gui::{int_field, COL_SPACING, ROW_SPACING},
Expand Down Expand Up @@ -94,7 +96,14 @@ pub fn feature_table(state: &mut State, ui: &mut Ui) {
.stroke(Stroke::new(border_width, Color32::LIGHT_RED))
.inner_margin(border_width)
.show(ui, |ui| {
ui.heading(RichText::new(&feature.label).color(Color32::GOLD));
if ui
.heading(RichText::new(&feature.label).color(Color32::GOLD))
.on_hover_cursor(CursorIcon::PointingHand)
.clicked()
{
state.ui.selected_item = Selection::Feature(i);
}

ui.horizontal(|ui| {
int_field(&mut feature.range.start, "Start:", ui);
int_field(&mut feature.range.end, "End:", ui);
Expand Down
3 changes: 2 additions & 1 deletion src/gui/sequence.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,9 +71,10 @@ fn primer_text(i: usize, primers: &[Primer], seq_len: usize, ui: &mut Ui) {

ui.label(&primer.name);
ui.label(&primer.location_descrip());
ui.label(&primer.description.clone().unwrap_or_default());
// todo: Rev color A/R
ui.label(RichText::new(seq_to_str(&primer.sequence)).color(PRIMER_FWD_COLOR));

ui.label(&primer.description.clone().unwrap_or_default());
}

/// Add a toolbar to create a feature from selection, if appropriate.
Expand Down
49 changes: 23 additions & 26 deletions src/melting_temp_calcs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ use crate::{
sequence::Nucleotide::{self, A, C, G, T},
};

const R: f32 = 1.987; // Universal gas constant (Cal/C * Mol)

/// Enthalpy (dH) and entropy (dS) tables based on terminal missmatch
fn _dH_dS_tmm(nts: (Nucleotide, Nucleotide)) -> Option<(f32, f32)> {
match nts {
Expand Down Expand Up @@ -160,18 +162,23 @@ fn _dH_dS_de(nts: (Nucleotide, Nucleotide)) -> Option<(f32, f32)> {
/// SantaLucia & Hicks, 2004, Table 1. Value are in kcal/Mol.
///
/// `neighbors` refers to the values between adjacent pairs of NTs.
/// To interpret this table, and come up with this result, search it in these
/// two manners:
/// A: Left to right, left of the slash
/// B: Right to left, right of the slash
/// You will find exactly one match using this approach.
fn dH_dS_neighbors(neighbors: (Nucleotide, Nucleotide)) -> (f32, f32) {
match neighbors {
(A, A) | (T, T) => (-7.6, -21.3),
(A, T) => (-7.2, -20.4),
(T, A) => (-7.2, -21.3),
(T, G) | (C, A) => (-8.5, -22.7),
(A, C) | (G, T) => (-8.4, -22.4),
(A, G) | (C, T) => (-7.8, -21.0),
(T, C) | (G, A) => (-8.2, -22.2),
(C, A) | (T, G) => (-8.5, -22.7),
(G, T) | (A, C) => (-8.4, -22.4),
(C, T) | (A, G) => (-7.8, -21.0),
(G, A) | (T, C) => (-8.2, -22.2),
(C, G) => (-10.6, -27.2),
(G, C) => (-9.8, -24.4),
(C, C) | (G, G) => (-8.0, -19.9),
(G, G) | (C, C) => (-8.0, -19.9),
}
}

Expand All @@ -180,7 +187,7 @@ fn dH_dS_neighbors(neighbors: (Nucleotide, Nucleotide)) -> (f32, f32) {
fn salt_correction(seq: &[Nucleotide], ion: &IonConcentrations) -> Option<f32> {
// todo: Using Some casual defaults for now.
// These are millimolar concentration of respective ions.
let method = 4; // todo?
let method = 5; // todo?

let tris = 0.; // todo: Do we want this?

Expand Down Expand Up @@ -271,7 +278,7 @@ pub fn calc_tm(seq: &[Nucleotide], ion_concentrations: &IonConcentrations) -> Op
return None;
}

// Inititial values. (Table 1)
// Inititial values. (S&H, Table 1)
let mut dH = 0.2;
let mut dS = -5.7;

Expand All @@ -285,7 +292,7 @@ pub fn calc_tm(seq: &[Nucleotide], ion_concentrations: &IonConcentrations) -> Op
{
let term_pair = vec![seq[0], seq[seq.len() - 1]];
let mut at_term_count = 0;
//C onstants for the CG term are 0, so we don't need it.
// Constants for the CG term are 0, so we don't need it.
for nt in term_pair {
if nt == A || nt == T {
at_term_count += 1;
Expand All @@ -307,33 +314,23 @@ pub fn calc_tm(seq: &[Nucleotide], ion_concentrations: &IonConcentrations) -> Op
dS += dS_nn;
}

const R: f32 = 1.987; // Universal gas constant (Cal/C * Mol)

// We are multiplying by two, as it's one per strand.
let C_T = (ion_concentrations.primer * 2.) * 1.0e-9;
let C_T = ion_concentrations.primer * 1.0e-9;

// SantaLucia and Hicks, Equation 3.
let mut result = (1_000. * dH) / (dS + R * ((C_T / 4.).ln())) - 273.15;
// println!("\n\ndH: {dH} dS: {dS}");

// if saltcorr == 5:
// delta_s += corr
// melting_temp = (1000 * delta_h) / (delta_s + (R * (math.log(k)))) - 273.15
// if saltcorr in (1, 2, 3, 4):
// melting_temp += corr
// if saltcorr in (6, 7):
// # Tm = 1/(1/Tm + corr)
// melting_temp = 1 / (1 / (melting_temp + 273.15) + corr) - 273.15

// We will assume saltcorr method 1-4 for now.
if let Some(sc) = salt_correction(seq, ion_concentrations) {
// todo: Put back! Evaluating our method.
// Hard-coded for salt-correction method 5.
dS += sc;
// result += sc;

// for saltcorr 6/7:
// result = 1. / (1. / (result + 273.15) + sc) - 273.15
} else {
println!("Error on SC")
eprintln!("Error calculating salt correction.");
}

// SantaLucia and Hicks, Equation 3. Note the C_T / 2 vice / 4, due to double-stranded concentration.
let result = (1_000. * dH) / (dS + R * (C_T / 2.).ln()) - 273.15;

Some(result)
}
Loading

0 comments on commit ec76406

Please sign in to comment.