Team:EPF-Lausanne/PrimerDesignHelper.js

From 2012.igem.org

Revision as of 20:10, 12 July 2012 by Sander.kromwijk (Talk | contribs)

window.tails = [ { name: "Biobrick (gene)", value: "biobrickGene", primer1Tail: "GTT TCT TCG AAT TCG CGG CCG CTT CTA G", primer2Tail: "GTT TCT TCC TGC AGC GGC CGC TAC TAG TA TTA TTA", messages: [ "Don't forget to include the first ATG and to exclude the stop codon!", "The recommended minimum length of a BioBrick primer is 20bp", "The recommended Tm of a BioBrick primer is between 55 and 65 °C", "Check that the following restriction sites are absent: EcoRI, SpeI, XbaI, PstI, NotI", "Check out <a href='http://openwetware.org/wiki/Synthetic_Biology:BioBricks/Part_fabrication'>OpenWetWare's reference page</a>" ], validate: function(primer1, primer2){ var start = primer1.substr(0, 3).toUpperCase();

if(start != "ATG"){ return "The starting ATG seems to be absent." } var end = primer2.substr(0, 3).toUpperCase(); // TAA TGA TAG if(end == "TTA" || end == "TCA" || end == "CTA"){ return "Remove the stop codons from the sequence!" } } }, { name: "Biobrick (other)", value: "biobrickOther", primer1Tail: "GTT TCT TCG AAT TCG CGG CCG CTT CTA GAG", primer2Tail: "GTT TCT TCC TGC AGC GGC CGC TAC TAG TA", messages: [ "The recommended minimum length of a BioBrick primer is 20bp", "The recommended Tm of a BioBrick primer is between 55 and 65 °C", "Check that the following restriction sites are absent: EcoRI, SpeI, XbaI, PstI, NotI", "Check out <a href='http://openwetware.org/wiki/Synthetic_Biology:BioBricks/Part_fabrication'>OpenWetWare's reference page</a>" ] } ]; window.calc = function(){ var data = document.getElementById("data").value; var isNormal = document.getElementById("primerTypeNormal").checked; var isCustom = document.getElementById("primerTypeCustom").checked; var isRS = document.getElementById("primerTypeRestrictionSites").checked; var isKnown = document.getElementById("primerTypeKnownTail").checked; var isNumbered = document.getElementById("toggleLines").checked; var goalTm = document.getElementById("goalTm").value;

try{ var messages = []; var toAdd = {}; var p = new Primer(data); var validate = function(primer1, primer2){};

if(isCustom){ p.setTails(Sequence.fromString(document.getElementById("primer1Tail").value), Sequence.fromString(document.getElementById("primer2Tail").value)); }

if(isRS){ var rs1 = Sequence.fromString(document.getElementById("primer1RS").value); var rs2 = Sequence.fromString(document.getElementById("primer2RS").value);

var match1 = rs1.findRS(); var match2 = rs2.findRS(); toAdd.primer1RSMatch = match1.length > 0 ? "- "+match1 : ""; toAdd.primer2RSMatch = match2.length > 0 ? "- "+match2 : "";

p.setTails(rs1, rs2.reverseComplement()); p.addRandomEdges(document.getElementById("primerRandBases").value);

messages.push("Click the 'Calculate' button to regenerate the random bases added on the end of the primers."); messages.push( "In the case of a plasmid where the piece between A and B is cut out, and A is used for the primer 1 RS (and B for the second). " + "The generated primers will put the RS such that the given DNA starts at the A side and ends at the B side (in short, " + "it will probably work as you expected, but still check what happens with some other tool)." ); }

if(isKnown){ var selected = $("#knownTailSelect").val();

var tail1 = ""; var tail2 = "";

for(var i = 0; i < window.tails.length; i++){ if(window.tails[i].value == selected){ tail1 = window.tails[i].primer1Tail; tail2 = window.tails[i].primer2Tail; if(window.tails[i].messages) messages = [].concat(window.tails[i].messages); if(window.tails[i].validate) validate = window.tails[i].validate; } }

p.setTails(Sequence.fromString(tail1), Sequence.fromString(tail2)); }

//if(!p.isSameAsLast(data, goalTm) || window.isCurrentlyNumbered != isNumbered){ window.isCurrentlyNumbered = isNumbered; var primers = p.generate(goalTm);

for(var key in toAdd){ primers[key] = toAdd[key]; }

var result = validate(primers.primer1NoTail, primers.primer2NoTail);

if(result) messages.push(result);

if(messages.length > 0){ window.output({ primers: primers, messages: messages, numbered: isNumbered }); }else{ window.output({ primers: primers, numbered: isNumbered }); } //} }catch(e){ window.output({ error: e.message, numbered: isNumbered }); } }; window.isCurrentlyNumbered = -1; window.output = function(options){ var output = { message: [], primer1: "No data", primer1Temp: "NaN", primer1bp: "NaN", primer1Tailbp: "NaN", primer1RSMatch: "", primer2: "No data", primer2Temp: "NaN", primer2bp: "NaN", primer2Tailbp: "NaN", primer2RSMatch: "", full: "", numbered: false }; if(options.numbered){ output.numbered = options.numbered; }

if(options.error){ output.message.push(options.error); } if(options.messages){ output.message = output.message.concat(options.messages); } if(options.primers){ for(var key in options.primers){ if(key == "message"){ output[key].push(options.primers[key]); }else{ output[key] = options.primers[key]; } } }

if(Math.abs(output.primer1Temp - output.primer2Temp) > 5){ output.message.push("The difference is more than 5 °C"); }

if(Math.min(output.primer1Temp, output.primer2Temp) > 72){ output.message.push("Consider using the two-step PCR protocol"); }

if(Math.min(output.primer1Temp, output.primer2Temp) < 45){ output.message.push("The annealing temperature should be more than 45 °C"); }

document.getElementById("primer1RSMatch").innerHTML = output.primer1RSMatch; document.getElementById("primer2RSMatch").innerHTML = output.primer2RSMatch;

document.getElementById("messages").innerHTML = output.message.length > 0 ? "
  • "+output.message.join("
  • ")+"
" : "";

document.getElementById("primer1").innerHTML = output.primer1; document.getElementById("primer1Temp").innerHTML = output.primer1Temp; document.getElementById("primer1bp").innerHTML = output.primer1Tailbp != 0 && output.primer1Tailbp != "NaN" ? output.primer1Tailbp+" + "+output.primer1bp : output.primer1bp; document.getElementById("primer2").innerHTML = output.primer2; document.getElementById("primer2Temp").innerHTML = output.primer2Temp; document.getElementById("primer2bp").innerHTML = output.primer2Tailbp != 0 && output.primer2Tailbp != "NaN" ? output.primer2Tailbp+" + "+output.primer2bp : output.primer2bp; document.getElementById("annealingTemp").innerHTML = Math.min(output.primer1Temp, output.primer2Temp);

var width = 80; var lines = window.wrap(output.full, width); var outputLines = []; if(output.numbered){ for(var i = 0; i < lines.length; i++){ outputLines[i] = window.pad(i*width+1, 6, true)+" - "+window.pad((i+1)*width, 6)+" "+lines[i]; } }else{ outputLines = lines; } document.getElementById("finalResult").innerHTML = outputLines.join("\n"); };

window.pad = function(str, width, right){ str += ""; while(str.length < width){ if(right){ str = " "+str; }else{ str += " "; } } return str; }

window.wrap = function(str, width){ var chunks = []; var i = 0, l = str.length; while(i < l){ if(i + width >= l){ chunks.push(str.substr(i, l-i)); }else{ chunks.push(str.substr(i, width)); } i += width; } return chunks; };

window.updateRadioElements = function(){ if(document.getElementById('primerTypeRestrictionSites').checked){ $('#restrictionSiteRow').show(); }else{ $('#restrictionSiteRow').hide(); } if(document.getElementById('primerTypeCustom').checked){ $('#customRow').show(); }else{ $('#customRow').hide(); } if(document.getElementById('primerTypeKnownTail').checked){ $('#knownTailRow').show(); }else{ $('#knownTailRow').hide(); } };var Primer;

Primer = (function() {

 Primer.minLength = 8;
 Primer.maxLength = 50;
 Primer.maxDiff = 8;
 Primer.lastData = "";
 Primer.lastTail1 = "";
 Primer.lastTail2 = "";
 Primer.lastTm = 0;
 Primer.lastRandEdgeSize = -1;
 Primer.reset = function() {
   this.lastData = "";
   this.lastTm = 0;
   this.lastTail1 = "";
   this.lastTail2 = "";
   return this.lastRandEdgeSize = -1;
 };
 function Primer(data) {
   this.seq = Sequence.fromString(data);
   this.tail1 = new Sequence([]);
   this.tail2 = new Sequence([]);
   this.randEdgeSize = 0;
 }
 Primer.prototype.setTails = function(tail1, tail2) {
   this.tail1 = tail1;
   this.tail2 = tail2;
 };
 Primer.prototype.addRandomEdges = function(randEdgeSize) {
   this.randEdgeSize = randEdgeSize;
 };
 Primer.prototype.isSameAsLast = function(data, tm) {
   return Primer.lastTail1.toString() === this.tail1.toString() && Primer.lastTail2.toString() === this.tail2.toString() && Primer.lastData === Sequence.fromString(data).toString() && Primer.lastTm === tm && Primer.lastRandEdgeSize === this.randEdgeSize;
 };
 Primer.prototype.generate = function(goal) {
   var diff, l1, l2, maxLength, primer1Length, primer1Temp, primer2Length, primer2Temp, rand1, rand2, revCompl, temp, _i, _j, _ref, _ref1;
   if (2 * Primer.minLength > this.seq.length()) {
     throw new Error("The given sequence is too short");
   }
   Primer.lastData = this.seq.toString();
   Primer.lastTm = goal;
   Primer.lastTail1 = this.tail1;
   Primer.lastTail2 = this.tail2;
   Primer.lastRandEdgeSize = this.randEdgeSize;
   maxLength = Math.min(Math.floor(this.seq.length() / 2), Primer.maxLength);
   revCompl = this.seq.reverseComplement();
   primer1Length = -1;
   primer1Temp = 0;
   primer2Length = -1;
   primer2Temp = 0;
   for (l1 = _i = _ref = Primer.minLength; _ref <= maxLength ? _i <= maxLength : _i >= maxLength; l1 = _ref <= maxLength ? ++_i : --_i) {
     temp = this.seq.snip(l1).tm();
     diff = Math.abs(temp - goal);
     if (diff <= Primer.maxDiff && (primer1Length === -1 || Math.abs(primer1Temp - goal) > diff)) {
       primer1Length = l1;
       primer1Temp = temp;
     }
     if (temp >= goal) {
       break;
     }
   }
   for (l2 = _j = _ref1 = Primer.minLength; _ref1 <= maxLength ? _j <= maxLength : _j >= maxLength; l2 = _ref1 <= maxLength ? ++_j : --_j) {
     temp = revCompl.snip(l2).tm();
     diff = Math.abs(temp - goal);
     if (diff <= Primer.maxDiff && (primer2Length === -1 || Math.abs(primer2Temp - goal) > diff)) {
       primer2Length = l2;
       primer2Temp = temp;
     }
     if (temp >= goal) {
       break;
     }
   }
   if (primer1Length < 0 || primer2Length < 0) {
     throw new Error("Couldn't find a primer of correct length that satisfies the conditions");
   }
   rand1 = Sequence.fromRandom(this.randEdgeSize);
   rand2 = Sequence.fromRandom(this.randEdgeSize);
   return {
     primer1: rand1.toString() + " " + this.tail1.toString() + " " + this.seq.snip(primer1Length).toString(),
     primer1NoTail: this.seq.snip(primer1Length).toString(),
     primer1Temp: primer1Temp,
     primer1bp: primer1Length,
     primer1Tailbp: rand1.length() + this.tail1.length(),
     primer2: rand2.toString() + " " + this.tail2.toString() + " " + revCompl.snip(primer2Length).toString(),
     primer2NoTail: revCompl.snip(primer2Length).toString(),
     primer2Temp: primer2Temp,
     primer2bp: primer2Length,
     primer2Tailbp: rand2.length() + this.tail2.length(),
     full: rand1.toString() + this.tail1.toString() + this.seq.toString() + this.tail2.reverseComplement().toString() + rand2.reverseComplement().toString()
   };
 };
 return Primer;

})(); /* Immutable class that makes it possible to work with (very simplified) DNA sequences

  • /

var Sequence,

 __indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; };

Sequence = (function() {

 Sequence.allowed = ["A", "C", "G", "T"];
 Sequence.extendedAllowed = ["A", "C", "G", "T", "R", "Y", "K", "M", "S", "W", "B", "D", "H", "V", "N"];
 Sequence.RS = {
   AatI: "AGGCCT",
   AatII: "GACGTC",
   AccI: "GTMKAC",
   AccIII: "TCCGGA",
   Acc65I: "GGTACC",
   AcsI: "RAATTY",
   AcyI: "GRCGYC",
   AflI: "GGWCC",
   AflII: "CTTAAG",
   AflIII: "ACRYGT",
   AgeI: "ACCGGT",
   AhaII: "GRCGYC",
   AhaIII: "TTTAAA",
   AluI: "AGCT",
   Alw44I: "GTGCAC",
   AlwNI: "CAGNNNCTG",
   AocI: "CCTNAGG",
   AosI: "TGCGCA",
   ApaI: "GGGCCC",
   ApaLI: "GTGCAC",
   ApoI: "RAATTY",
   ApyI: "CCWGG",
   AscI: "GGCGCGCC",
   AseI: "ATTAAT",
   AsnI: "ATTAAT",
   AspI: "GACNNNGTC",
   Asp700: "GAANNNNTTC",
   Asp718: "GGTACC",
   AspEI: "GACNNNNNGTC",
   AspHI: "GWGCWC",
   AsuII: "TTCGAA",
   AvaI: "CYCGRG",
   AvaII: "GGWCC",
   AviII: "TGCGCA",
   AvrII: "CCTAGG",
   BalI: "TGGCCA",
   BamHI: "GGATCC",
   BanI: "GGYRCC",
   BanII: "GRGCYC",
   BbrPI: "CACGTG",
   BclI: "TGATCA",
   BfaI: "CTAG",
   BfrI: "CTTAAG",
   BglI: "GCCNNNNNGGC",
   BglII: "AGATCT",
   BinI: "CCTAGG",
   BmyI: "GDGCHC",
   Bpu1102I: "GCTNAGC",
   BsaAI: "YACGTR",
   BsaBI: "GATNNNNATC",
   BsaHI: "GRCGYC",
   BsaJI: "CCNNGG",
   BseAI: "TCCGGA",
   BsePI: "GCGCGC",
   BsiEI: "CGRYCG",
   BsiWI: "CGTACG",
   BsiYI: "CCNNNNNNNGG",
   BslI: "CCNNNNNNNGG",
   Bsp1286I: "GDGCHC",
   Bsp1407I: "TGTACA",
   BspDI: "ATCGAT",
   BspEI: "TCCGGA",
   BspHI: "TCATGA",
   BspLU11I: "ACATGT",
   BsrFI: "RCCGGY",
   BssGI: "CCANNNNNNTGG",
   BssHII: "GCGCGC",
   Bst1107I: "GTATAC",
   BstBI: "TTCGAA",
   BstEII: "GGTNACC",
   BstNI: "CCWGG",
   BstUI: "CGCG",
   BstXI: "CCANNNNNNTGG",
   BstYI: "RGATCY",
   Bsu36I: "CCTNAGG",
   CelII: "GCTNAGC",
   CfoI: "GCGC",
   CfrI: "YGGCCR",
   Cfr10I: "RCCGGY",
   ClaI: "ATCGAT",
   DdeI: "CTNAG",
   DpnII: "GATC",
   DraI: "TTTAAA",
   DraII: "RGGNCCY",
   DraIII: "CACNNNGTG",
   DrdI: "GACNNNNNNGTC",
   DsaI: "CCRYGG",
   EaeI: "YGGCCR",
   EagI: "CGGCCG",
   Eam1105I: "GACNNNNNGTC",
   Ecl136II: "GAGCTC",
   EclXI: "CGGCCG",
   Eco47III: "AGCGCT",
   EcoNI: "CCTNNNNNAGG",
   EcoO109I: "RGGNCCY",
   EcoRI: "GAATTC",
   EcoRII: "CCWGG",
   EcoRV: "GATATC",
   EspI: "GCTNAGC",
   FnuDII: "CGCG",
   Fnu4HI: "GCNGC",
   FseI: "GGCCGGCC",
   FspI: "TGCGCA",
   HaeII: "RGCGCY",
   HaeIII: "GGCC",
   HgiAI: "GWGCWC",
   HhaI: "GCGC",
   HincII: "GTYRAC",
   HindII: "GTYRAC",
   HindIII: "AAGCTT",
   HinfI: "GANTC",
   HinPI: "GCGC",
   HpaI: "GTTAAC",
   HpaII: "CCGG",
   ItaI: "GCNGC",
   KasI: "GGCGCC",
   KpnI: "GGTACC",
   KspI: "CCGCGG",
   MaeI: "CTAG",
   MaeII: "ACGT",
   MaeIII: "GTNAC",
   MamI: "GATNNNNATC",
   MboI: "GATC",
   MfeI: "CAATTG",
   MluI: "ACGCGT",
   MluNI: "TGGCCA",
   MroI: "TCCGGA",
   MscI: "TGGCCA",
   MseI: "TTAA",
   MspI: "CCGG",
   MstI: "TGCGCA",
   MstII: "CCTNAGG",
   MunI: "CAATTG",
   MvaI: "CCWGG",
   MvnI: "CGCG",
   NaeI: "GCCGGC",
   NarI: "GGCGCC",
   NciI: "CCSGG",
   NcoI: "CCATGG",
   NdeI: "CATATG",
   NdeII: "GATC",
   NgoMI: "GCCGGC",
   NheI: "GCTAGC",
   NlaIII: "CATG",
   NlaIV: "GGNNCC",
   NotI: "GCGGCCGC",
   NruI: "TCGCGA",
   NsiI: "ATGCAT",
   NspBII: "CMGCKG",
   NspI: "RCATGY",
   NspII: "GDGCHC",
   NspV: "TTCGAA",
   PacI: "TTAATTAA",
   PaeR7I: "CTCGAG",
   PflMI: "CCANNNNNTGG",
   PinAI: "ACCGGT",
   PsiI: "TTATAA",
   PmaCI: "CACGTG",
   PmeI: "GTTTAAAC",
   PmlI: "CACGTG",
   PpuMI: "RGGWCCY",
   Psp1406I: "AACGTT",
   PstI: "CTGCAG",
   PvuI: "CGATCG",
   PvuII: "CAGCTG",
   RcaI: "TCATGA",
   RmaI: "CTAG",
   RsaI: "GTAC",
   RsrII: "CGGWCCG",
   SacI: "GAGCTC",
   SacII: "CCGCGG",
   SalI: "GTCGAC",
   SauI: "CCTNAGG",
   Sau3AI: "GATC",
   Sau96I: "GGNCC",
   ScaI: "AGTACT",
   ScrFI: "CCNGG",
   SexAI: "ACCWGGT",
   SfcI: "CTRYAG",
   SfiI: "GGCCNNNNNGGCC",
   SfuI: "TTCGAA",
   SgrAI: "CRCCGGYG",
   SmaI: "CCCGGG",
   SnaBI: "TACGTA",
   SnoI: "GTGCAC",
   SpeI: "ACTAGT",
   SphI: "GCATGC",
   SrfI: "GCCCGGGC",
   Sse8387I: "CCTGCAGG",
   SspI: "AATATT",
   SspBI: "TGTACA",
   SstI: "GAGCTC",
   SstII: "CCGCGG",
   StuI: "AGGCCT",
   StyI: "CCWWGG",
   SwaI: "ATTTAAAT",
   TaqI: "TCGA",
   TfiI: "GAWTC",
   ThaI: "CGCG",
   Tru9I: "TTAA",
   Tth111I: "GACNNNGTC",
   Van91I: "CCANNNNNTGG",
   XbaI: "TCTAGA",
   XcmI: "CCANNNNNNNNNTGG",
   XhoI: "CTCGAG",
   XhoII: "RGATCY",
   XmaI: "CCCGGG",
   XmaIII: "CGGCCG",
   XmaCI: "CCCGGG",
   XmnI: "GAANNNNTTC"
 };
 Sequence.RSFlipped = void 0;
 Sequence.getRSFlipped = function() {
   var name, seq, _ref;
   if (this.RSFlipped === void 0) {
     this.RSFlipped = {};
     _ref = this.RS;
     for (name in _ref) {
       seq = _ref[name];
       if (this.RSFlipped[seq]) {
         this.RSFlipped[seq] += " / " + name;
       } else {
         this.RSFlipped[seq] = name;
       }
     }
   }
   return this.RSFlipped;
 };
 Sequence.nucleotideMatches = {
   A: ["A", "R", "M", "W", "D", "H", "V", "N"],
   C: ["C", "Y", "M", "S", "B", "H", "V", "N"],
   G: ["G", "R", "K", "S", "B", "D", "V", "N"],
   T: ["T", "Y", "K", "W", "B", "D", "H", "N"],
   R: ["A", "G"],
   Y: ["C", "T"],
   K: ["G", "T"],
   M: ["A", "C"],
   S: ["C", "G"],
   W: ["A", "T"],
   B: ["C", "G", "T"],
   D: ["A", "G", "T"],
   H: ["A", "C", "T"],
   V: ["A", "C", "G"],
   N: ["A", "C", "G", "T"]
 };
 Sequence.fromString = function(data) {
   var found, name, seq, sequence, _ref;
   found = false;
   _ref = this.RS;
   for (name in _ref) {
     sequence = _ref[name];
     if (name.toLowerCase() === data.toLowerCase()) {
       seq = new Sequence(sequence.split());
       found = true;
     }
   }
   if (!found) {
     seq = new Sequence(data.toUpperCase().replace(/\s/ig, "").split());
   }
   seq.validateData();
   return seq;
 };
 Sequence.fromRandom = function(length) {
   var data, i;
   if (length === 0 || length === "0" || !length || length === void 0) {
     return new Sequence([]);
   } else {
     data = (function() {
       var _i, _results;
       _results = [];
       for (i = _i = 1; 1 <= length ? _i <= length : _i >= length; i = 1 <= length ? ++_i : --_i) {
         _results.push(Sequence.allowed[Math.floor(Math.random() * Sequence.allowed.length)]);
       }
       return _results;
     })();
     return new Sequence(data);
   }
 };
 /*
   The constructor assumes the data has been validated (for performance reasons -> premature and non-consequent
   optimization ^^). Anyway, the Sequence.fromString function is probably what you were looking for!
 */


 function Sequence(data) {
   this.data = data;
 }
 Sequence.prototype.validateData = function(strict) {
   if (strict) {
     if (this.data.some(function(x) {
       return __indexOf.call(Sequence.allowed, x) < 0;
     })) {
       throw new Error("Invalid character encountered");
     }
   } else {
     if (this.data.some(function(x) {
       return __indexOf.call(Sequence.extendedAllowed, x) < 0;
     })) {
       throw new Error("Invalid character encountered");
     }
   }
 };
 Sequence.prototype.matches = function(seq2) {
   var combined;
   if (this.length() !== seq2.length()) {
     return false;
   }
   combined = _.zip(this.data, seq2.data);
   return _.all(combined, function(_arg) {
     var i, j, n1, n1Matches, n2, n2Matches, _i, _j, _ref, _ref1;
     n1 = _arg[0], n2 = _arg[1];
     if (n1 === n2) {
       return true;
     } else {
       if (__indexOf.call(Sequence.allowed, n1) >= 0) {
         return __indexOf.call(Sequence.nucleotideMatches[n1], n2) >= 0;
       }
       if (__indexOf.call(Sequence.allowed, n2) >= 0) {
         return __indexOf.call(Sequence.nucleotideMatches[n2], n1) >= 0;
       }
       n1Matches = Sequence.nucleotideMatches[n1];
       n2Matches = Sequence.nucleotideMatches[n2];
       for (i = _i = 0, _ref = n1Matches.length - 1; 0 <= _ref ? _i <= _ref : _i >= _ref; i = 0 <= _ref ? ++_i : --_i) {
         for (j = _j = 0, _ref1 = n2Matches.length - 1; 0 <= _ref1 ? _j <= _ref1 : _j >= _ref1; j = 0 <= _ref1 ? ++_j : --_j) {
           if (n1Matches[i] === n2Matches[j]) {
             return true;
           }
         }
       }
       return false;
     }
   });
 };
 Sequence.prototype.findRS = function() {
   var key, name, rs, seq, _ref;
   key = this.data.join("");
   rs = Sequence.getRSFlipped();
   if (rs[key]) {
     return rs[key] + " (" + key + ")";
   } else {
     _ref = Sequence.RS;
     for (seq in _ref) {
       name = _ref[seq];
       if (this.matches(Sequence.fromString(seq))) {
         return name;
       }
     }
     return "";
   }
 };
 Sequence.prototype.length = function() {
   return this.data.length;
 };
 Sequence.prototype.snip = function(length) {
   if (length >= 0 && length <= this.data.length) {
     return new Sequence(this.data.slice(0, length));
   } else {
     throw new Error("Invalid snip length (tried to snip a piece of length " + length + " in a sequence of " + this.length() + "bp)");
   }
 };
 Sequence.prototype.reverseComplement = function() {
   var complement, k, l, revCompl, v, _i, _len, _ref;
   revCompl = [];
   complement = function(x) {
     switch (x) {
       case "A":
         return "T";
       case "T":
         return "A";
       case "C":
         return "G";
       case "G":
         return "C";
       default:
         throw new Error("Invalid character encountered (" + x + ")");
     }
   };
   l = this.data.length - 1;
   _ref = this.data;
   for (k = _i = 0, _len = _ref.length; _i < _len; k = ++_i) {
     v = _ref[k];
     revCompl[l - k] = complement(v);
   }
   return new Sequence(revCompl);
 };
 Sequence.prototype.append = function(seqToAdd) {
   return new Sequence(this.data.concat(seqToAdd.data));
 };
 Sequence.prototype.tm = function() {
   return TmCalc.calc(this.data);
 };
 Sequence.prototype.toString = function() {
   return this.data.join("");
 };
 return Sequence;

})(); /* Container class that helps with Tm calculations. It has been seperated to enable easy improvements to the algorithm.

  • /

var TmCalc;

TmCalc = (function() {

 function TmCalc() {}
 TmCalc.maxCacheSize = 500;
 TmCalc.cache = {};
 TmCalc.calc = function(data) {
   var key, keys, result;
   if (!this.cache[data]) {
     result = Math.round(this.calcFull(data) * 10) / 10;
     if (this.cache.length >= this.maxCacheSize) {
       keys = Object.keys(TmCalc.cache);
       key = keys[Math.floor(Math.random() * keys.length)];
       delete TmCalc.cache[key];
     }
     this.cache[data] = result;
   }
   return this.cache[data];
 };
 TmCalc.complement = function(n) {
   switch (n) {
     case "A":
       return "T";
     case "T":
       return "A";
     case "C":
       return "G";
     case "G":
       return "C";
     default:
       throw new Error("Invalid character encountered (" + n + ")");
   }
 };
 TmCalc.neighborH = {
   AA: 9.1,
   AT: 8.6,
   TA: 6.0,
   CA: 5.8,
   GT: 6.5,
   CT: 7.8,
   GA: 5.6,
   CG: 11.9,
   GC: 11.1,
   GG: 11.0
 };
 TmCalc.getH = function(n1, n2) {
   return this.get(this.neighborH, n1, n2);
 };
 TmCalc.neighborG = {
   AA: 1.9,
   AT: 1.5,
   TA: 0.9,
   CA: 1.9,
   GT: 1.3,
   CT: 1.6,
   GA: 1.6,
   CG: 3.6,
   GC: 3.1,
   GG: 3.1
 };
 TmCalc.getG = function(n1, n2) {
   return this.get(this.neighborG, n1, n2);
 };
 TmCalc.neighborS = {
   AA: 24.0,
   AT: 23.9,
   TA: 16.9,
   CA: 12.9,
   GT: 17.3,
   CT: 20.8,
   GA: 13.5,
   CG: 27.8,
   GC: 26.7,
   GG: 26.6
 };
 TmCalc.getS = function(n1, n2) {
   return this.get(this.neighborS, n1, n2);
 };
 TmCalc.get = function(searchVar, n1, n2) {
   switch (n1 + n2) {
     case "AA":
       return searchVar["AA"];
     case "TT":
       return searchVar["AA"];
     case "AT":
       return searchVar["AT"];
     case "TA":
       return searchVar["TA"];
     case "TT":
       return searchVar["TA"];
     case "CA":
       return searchVar["CA"];
     case "TG":
       return searchVar["CA"];
     case "GT":
       return searchVar["GT"];
     case "AC":
       return searchVar["GT"];
     case "CT":
       return searchVar["CT"];
     case "AG":
       return searchVar["CT"];
     case "GA":
       return searchVar["GA"];
     case "TC":
       return searchVar["GA"];
     case "CG":
       return searchVar["CG"];
     case "TC":
       return searchVar["CG"];
     case "GC":
       return searchVar["GC"];
     case "TC":
       return searchVar["GC"];
     case "GG":
       return searchVar["GG"];
     case "CC":
       return searchVar["GG"];
     default:
       throw new Error("No idea what happened... (tried getting " + n1 + n2 + ")");
   }
 };
 TmCalc.calcFull = function(data) {
   /* See (Breslauer et al. 1986): http://www.pnas.org/content/83/11/3746.full.pdf
   */
   var R, i, n1, n2, naConc, predG, predH, predS, primerConc, _i, _ref;
   predH = 0;
   predG = -(5 + 0.4);
   predS = 0;
   primerConc = 500 * 10e-9;
   naConc = 0.03;
   R = 1.987;
   n1 = data[0];
   for (i = _i = 1, _ref = data.length - 1; 1 <= _ref ? _i <= _ref : _i >= _ref; i = 1 <= _ref ? ++_i : --_i) {
     n2 = data[i];
     predH += this.getH(n1, n2);
     predG += this.getG(n1, n2);
     predS += this.getS(n1, n2);
     n1 = n2;
   }
   return (1000 * (predH - 3.4) / (predS + R * Math.log(1 / primerConc))) - 272.9 + 7.21 * Math.log(naConc / 1000) / Math.log(10);
 };
 /* Again, old
 @calcFull: (data, options) ->
   ##
   For more details see: http://www.basic.northwestern.edu/biotools/oligocalc.html
   ##
   freq = @calcFrequencies(data)
   
   if not options
     options = {}
   
   if not options.Na
     options.Na = 0.05
   
   GC = freq["G"] + freq["C"]
   AT = freq["A"] + freq["T"]
   N = data.length
   
   if N < 14
     AT*2 + GC*4 - 16.6*Math.log(0.050)/Math.log(10) + 16.6*Math.log(options.Na)/Math.log(10)
   else if N < 50 #18
     100.5 + (41 * GC/N) - (820/N) + 16.6*Math.log(options.Na)/Math.log(10)
   #else if N < 50
   #  81.5 + (41 * GC/N) - (500/N) + 16.6*Math.log(options.Na) - 0.62*options.F
   else
     79.8 + 18.5*Math.log(options.Na)/Math.log(10) + (58.4 * GC/N) + (11.8 * (GC/N) * (GC/N)) - (820/N) 
   
   
 
 @calcFrequencies: (data) ->
   freq = {A: 0, C: 0, G: 0, T: 0}
   data.forEach (x) ->
     freq[x]++
   freq
 */


 return TmCalc;

})();

/* Old version (not very accurate):

 @calc: (data) ->
   Math.round(@calcFull(data)*100)/100
 
 @calcFull: (data) ->
   freq = @calcFrequencies(data)
   if data.length < 14
     4 * (freq["G"] + freq["C"]) + 2 * (freq["A"] + freq["T"])
   else
     64.9 + 41 * (freq["G"] + freq["C"] - 16.4)/data.length
 
 @calcFrequencies: (data) ->
   freq = {A: 0, C: 0, G: 0, T: 0}
   data.forEach (x) ->
     freq[x]++
   freq
  • /

// Underscore.js 1.3.3 // (c) 2009-2012 Jeremy Ashkenas, DocumentCloud Inc. // Underscore is freely distributable under the MIT license. // Portions of Underscore are inspired or borrowed from Prototype, // Oliver Steele's Functional, and John Resig's Micro-Templating. // For all details and documentation: // http://documentcloud.github.com/underscore (function(){function r(a,c,d){if(a===c)return 0!==a||1/a==1/c;if(null==a||null==c)return a===c;a._chain&&(a=a._wrapped);c._chain&&(c=c._wrapped);if(a.isEqual&&b.isFunction(a.isEqual))return a.isEqual(c);if(c.isEqual&&b.isFunction(c.isEqual))return c.isEqual(a);var e=l.call(a);if(e!=l.call(c))return!1;switch(e){case "[object String]":return a==""+c;case "[object Number]":return a!=+a?c!=+c:0==a?1/a==1/c:a==+c;case "[object Date]":case "[object Boolean]":return+a==+c;case "[object RegExp]":return a.source== c.source&&a.global==c.global&&a.multiline==c.multiline&&a.ignoreCase==c.ignoreCase}if("object"!=typeof a||"object"!=typeof c)return!1;for(var f=d.length;f--;)if(d[f]==a)return!0;d.push(a);var f=0,g=!0;if("[object Array]"==e){if(f=a.length,g=f==c.length)for(;f--&&(g=f in a==f in c&&r(a[f],c[f],d)););}else{if("constructor"in a!="constructor"in c||a.constructor!=c.constructor)return!1;for(var h in a)if(b.has(a,h)&&(f++,!(g=b.has(c,h)&&r(a[h],c[h],d))))break;if(g){for(h in c)if(b.has(c,h)&&!f--)break; g=!f}}d.pop();return g}var s=this,I=s._,o={},k=Array.prototype,p=Object.prototype,i=k.slice,J=k.unshift,l=p.toString,K=p.hasOwnProperty,y=k.forEach,z=k.map,A=k.reduce,B=k.reduceRight,C=k.filter,D=k.every,E=k.some,q=k.indexOf,F=k.lastIndexOf,p=Array.isArray,L=Object.keys,t=Function.prototype.bind,b=function(a){return new m(a)};"undefined"!==typeof exports?("undefined"!==typeof module&&module.exports&&(exports=module.exports=b),exports._=b):s._=b;b.VERSION="1.3.3";var j=b.each=b.forEach=function(a, c,d){if(a!=null)if(y&&a.forEach===y)a.forEach(c,d);else if(a.length===+a.length)for(var e=0,f=a.length;e<f;e++){if(e in a&&c.call(d,a[e],e,a)===o)break}else for(e in a)if(b.has(a,e)&&c.call(d,a[e],e,a)===o)break};b.map=b.collect=function(a,c,b){var e=[];if(a==null)return e;if(z&&a.map===z)return a.map(c,b);j(a,function(a,g,h){e[e.length]=c.call(b,a,g,h)});if(a.length===+a.length)e.length=a.length;return e};b.reduce=b.foldl=b.inject=function(a,c,d,e){var f=arguments.length>2;a==null&&(a=[]);if(A&& a.reduce===A){e&&(c=b.bind(c,e));return f?a.reduce(c,d):a.reduce(c)}j(a,function(a,b,i){if(f)d=c.call(e,d,a,b,i);else{d=a;f=true}});if(!f)throw new TypeError("Reduce of empty array with no initial value");return d};b.reduceRight=b.foldr=function(a,c,d,e){var f=arguments.length>2;a==null&&(a=[]);if(B&&a.reduceRight===B){e&&(c=b.bind(c,e));return f?a.reduceRight(c,d):a.reduceRight(c)}var g=b.toArray(a).reverse();e&&!f&&(c=b.bind(c,e));return f?b.reduce(g,c,d,e):b.reduce(g,c)};b.find=b.detect=function(a, c,b){var e;G(a,function(a,g,h){if(c.call(b,a,g,h)){e=a;return true}});return e};b.filter=b.select=function(a,c,b){var e=[];if(a==null)return e;if(C&&a.filter===C)return a.filter(c,b);j(a,function(a,g,h){c.call(b,a,g,h)&&(e[e.length]=a)});return e};b.reject=function(a,c,b){var e=[];if(a==null)return e;j(a,function(a,g,h){c.call(b,a,g,h)||(e[e.length]=a)});return e};b.every=b.all=function(a,c,b){var e=true;if(a==null)return e;if(D&&a.every===D)return a.every(c,b);j(a,function(a,g,h){if(!(e=e&&c.call(b, a,g,h)))return o});return!!e};var G=b.some=b.any=function(a,c,d){c||(c=b.identity);var e=false;if(a==null)return e;if(E&&a.some===E)return a.some(c,d);j(a,function(a,b,h){if(e||(e=c.call(d,a,b,h)))return o});return!!e};b.include=b.contains=function(a,c){var b=false;if(a==null)return b;if(q&&a.indexOf===q)return a.indexOf(c)!=-1;return b=G(a,function(a){return a===c})};b.invoke=function(a,c){var d=i.call(arguments,2);return b.map(a,function(a){return(b.isFunction(c)?c||a:a[c]).apply(a,d)})};b.pluck= function(a,c){return b.map(a,function(a){return a[c]})};b.max=function(a,c,d){if(!c&&b.isArray(a)&&a[0]===+a[0])return Math.max.apply(Math,a);if(!c&&b.isEmpty(a))return-Infinity;var e={computed:-Infinity};j(a,function(a,b,h){b=c?c.call(d,a,b,h):a;b>=e.computed&&(e={value:a,computed:b})});return e.value};b.min=function(a,c,d){if(!c&&b.isArray(a)&&a[0]===+a[0])return Math.min.apply(Math,a);if(!c&&b.isEmpty(a))return Infinity;var e={computed:Infinity};j(a,function(a,b,h){b=c?c.call(d,a,b,h):a;b<e.computed&& (e={value:a,computed:b})});return e.value};b.shuffle=function(a){var b=[],d;j(a,function(a,f){d=Math.floor(Math.random()*(f+1));b[f]=b[d];b[d]=a});return b};b.sortBy=function(a,c,d){var e=b.isFunction(c)?c:function(a){return a[c]};return b.pluck(b.map(a,function(a,b,c){return{value:a,criteria:e.call(d,a,b,c)}}).sort(function(a,b){var c=a.criteria,d=b.criteria;return c===void 0?1:d===void 0?-1:c<d?-1:c>d?1:0}),"value")};b.groupBy=function(a,c){var d={},e=b.isFunction(c)?c:function(a){return a[c]}; j(a,function(a,b){var c=e(a,b);(d[c]||(d[c]=[])).push(a)});return d};b.sortedIndex=function(a,c,d){d||(d=b.identity);for(var e=0,f=a.length;e<f;){var g=e+f>>1;d(a[g])<d(c)?e=g+1:f=g}return e};b.toArray=function(a){return!a?[]:b.isArray(a)||b.isArguments(a)?i.call(a):a.toArray&&b.isFunction(a.toArray)?a.toArray():b.values(a)};b.size=function(a){return b.isArray(a)?a.length:b.keys(a).length};b.first=b.head=b.take=function(a,b,d){return b!=null&&!d?i.call(a,0,b):a[0]};b.initial=function(a,b,d){return i.call(a, 0,a.length-(b==null||d?1:b))};b.last=function(a,b,d){return b!=null&&!d?i.call(a,Math.max(a.length-b,0)):a[a.length-1]};b.rest=b.tail=function(a,b,d){return i.call(a,b==null||d?1:b)};b.compact=function(a){return b.filter(a,function(a){return!!a})};b.flatten=function(a,c){return b.reduce(a,function(a,e){if(b.isArray(e))return a.concat(c?e:b.flatten(e));a[a.length]=e;return a},[])};b.without=function(a){return b.difference(a,i.call(arguments,1))};b.uniq=b.unique=function(a,c,d){var d=d?b.map(a,d):a, e=[];a.length<3&&(c=true);b.reduce(d,function(d,g,h){if(c?b.last(d)!==g||!d.length:!b.include(d,g)){d.push(g);e.push(a[h])}return d},[]);return e};b.union=function(){return b.uniq(b.flatten(arguments,true))};b.intersection=b.intersect=function(a){var c=i.call(arguments,1);return b.filter(b.uniq(a),function(a){return b.every(c,function(c){return b.indexOf(c,a)>=0})})};b.difference=function(a){var c=b.flatten(i.call(arguments,1),true);return b.filter(a,function(a){return!b.include(c,a)})};b.zip=function(){for(var a= i.call(arguments),c=b.max(b.pluck(a,"length")),d=Array(c),e=0;e<c;e++)d[e]=b.pluck(a,""+e);return d};b.indexOf=function(a,c,d){if(a==null)return-1;var e;if(d){d=b.sortedIndex(a,c);return a[d]===c?d:-1}if(q&&a.indexOf===q)return a.indexOf(c);d=0;for(e=a.length;d<e;d++)if(d in a&&a[d]===c)return d;return-1};b.lastIndexOf=function(a,b){if(a==null)return-1;if(F&&a.lastIndexOf===F)return a.lastIndexOf(b);for(var d=a.length;d--;)if(d in a&&a[d]===b)return d;return-1};b.range=function(a,b,d){if(arguments.length<= 1){b=a||0;a=0}for(var d=arguments[2]||1,e=Math.max(Math.ceil((b-a)/d),0),f=0,g=Array(e);f<e;){g[f++]=a;a=a+d}return g};var H=function(){};b.bind=function(a,c){var d,e;if(a.bind===t&&t)return t.apply(a,i.call(arguments,1));if(!b.isFunction(a))throw new TypeError;e=i.call(arguments,2);return d=function(){if(!(this instanceof d))return a.apply(c,e.concat(i.call(arguments)));H.prototype=a.prototype;var b=new H,g=a.apply(b,e.concat(i.call(arguments)));return Object(g)===g?g:b}};b.bindAll=function(a){var c= i.call(arguments,1);c.length==0&&(c=b.functions(a));j(c,function(c){a[c]=b.bind(a[c],a)});return a};b.memoize=function(a,c){var d={};c||(c=b.identity);return function(){var e=c.apply(this,arguments);return b.has(d,e)?d[e]:d[e]=a.apply(this,arguments)}};b.delay=function(a,b){var d=i.call(arguments,2);return setTimeout(function(){return a.apply(null,d)},b)};b.defer=function(a){return b.delay.apply(b,[a,1].concat(i.call(arguments,1)))};b.throttle=function(a,c){var d,e,f,g,h,i,j=b.debounce(function(){h= g=false},c);return function(){d=this;e=arguments;f||(f=setTimeout(function(){f=null;h&&a.apply(d,e);j()},c));g?h=true:i=a.apply(d,e);j();g=true;return i}};b.debounce=function(a,b,d){var e;return function(){var f=this,g=arguments;d&&!e&&a.apply(f,g);clearTimeout(e);e=setTimeout(function(){e=null;d||a.apply(f,g)},b)}};b.once=function(a){var b=false,d;return function(){if(b)return d;b=true;return d=a.apply(this,arguments)}};b.wrap=function(a,b){return function(){var d=[a].concat(i.call(arguments,0)); return b.apply(this,d)}};b.compose=function(){var a=arguments;return function(){for(var b=arguments,d=a.length-1;d>=0;d--)b=[a[d].apply(this,b)];return b[0]}};b.after=function(a,b){return a<=0?b():function(){if(--a<1)return b.apply(this,arguments)}};b.keys=L||function(a){if(a!==Object(a))throw new TypeError("Invalid object");var c=[],d;for(d in a)b.has(a,d)&&(c[c.length]=d);return c};b.values=function(a){return b.map(a,b.identity)};b.functions=b.methods=function(a){var c=[],d;for(d in a)b.isFunction(a[d])&& c.push(d);return c.sort()};b.extend=function(a){j(i.call(arguments,1),function(b){for(var d in b)a[d]=b[d]});return a};b.pick=function(a){var c={};j(b.flatten(i.call(arguments,1)),function(b){b in a&&(c[b]=a[b])});return c};b.defaults=function(a){j(i.call(arguments,1),function(b){for(var d in b)a[d]==null&&(a[d]=b[d])});return a};b.clone=function(a){return!b.isObject(a)?a:b.isArray(a)?a.slice():b.extend({},a)};b.tap=function(a,b){b(a);return a};b.isEqual=function(a,b){return r(a,b,[])};b.isEmpty= function(a){if(a==null)return true;if(b.isArray(a)||b.isString(a))return a.length===0;for(var c in a)if(b.has(a,c))return false;return true};b.isElement=function(a){return!!(a&&a.nodeType==1)};b.isArray=p||function(a){return l.call(a)=="[object Array]"};b.isObject=function(a){return a===Object(a)};b.isArguments=function(a){return l.call(a)=="[object Arguments]"};b.isArguments(arguments)||(b.isArguments=function(a){return!(!a||!b.has(a,"callee"))});b.isFunction=function(a){return l.call(a)=="[object Function]"}; b.isString=function(a){return l.call(a)=="[object String]"};b.isNumber=function(a){return l.call(a)=="[object Number]"};b.isFinite=function(a){return b.isNumber(a)&&isFinite(a)};b.isNaN=function(a){return a!==a};b.isBoolean=function(a){return a===true||a===false||l.call(a)=="[object Boolean]"};b.isDate=function(a){return l.call(a)=="[object Date]"};b.isRegExp=function(a){return l.call(a)=="[object RegExp]"};b.isNull=function(a){return a===null};b.isUndefined=function(a){return a===void 0};b.has=function(a, b){return K.call(a,b)};b.noConflict=function(){s._=I;return this};b.identity=function(a){return a};b.times=function(a,b,d){for(var e=0;e<a;e++)b.call(d,e)};b.escape=function(a){return(""+a).replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""").replace(/'/g,"'").replace(/\//g,"/")};b.result=function(a,c){if(a==null)return null;var d=a[c];return b.isFunction(d)?d.call(a):d};b.mixin=function(a){j(b.functions(a),function(c){M(c,b[c]=a[c])})};var N=0;b.uniqueId= function(a){var b=N++;return a?a+b:b};b.templateSettings={evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,escape:/<%-([\s\S]+?)%>/g};var u=/.^/,n={"\\":"\\","'":"'",r:"\r",n:"\n",t:"\t",u2028:"\u2028",u2029:"\u2029"},v;for(v in n)n[n[v]]=v;var O=/\\|'|\r|\n|\t|\u2028|\u2029/g,P=/\\(\\|'|r|n|t|u2028|u2029)/g,w=function(a){return a.replace(P,function(a,b){return n[b]})};b.template=function(a,c,d){d=b.defaults(d||{},b.templateSettings);a="__p+='"+a.replace(O,function(a){return"\\"+n[a]}).replace(d.escape|| u,function(a,b){return"'+\n_.escape("+w(b)+")+\n'"}).replace(d.interpolate||u,function(a,b){return"'+\n("+w(b)+")+\n'"}).replace(d.evaluate||u,function(a,b){return"';\n"+w(b)+"\n;__p+='"})+"';\n";d.variable||(a="with(obj||{}){\n"+a+"}\n");var a="var __p=;var print=function(){__p+=Array.prototype.join.call(arguments, )};\n"+a+"return __p;\n",e=new Function(d.variable||"obj","_",a);if(c)return e(c,b);c=function(a){return e.call(this,a,b)};c.source="function("+(d.variable||"obj")+"){\n"+a+"}";return c}; b.chain=function(a){return b(a).chain()};var m=function(a){this._wrapped=a};b.prototype=m.prototype;var x=function(a,c){return c?b(a).chain():a},M=function(a,c){m.prototype[a]=function(){var a=i.call(arguments);J.call(a,this._wrapped);return x(c.apply(b,a),this._chain)}};b.mixin(b);j("pop,push,reverse,shift,sort,splice,unshift".split(","),function(a){var b=k[a];m.prototype[a]=function(){var d=this._wrapped;b.apply(d,arguments);var e=d.length;(a=="shift"||a=="splice")&&e===0&&delete d[0];return x(d, this._chain)}});j(["concat","join","slice"],function(a){var b=k[a];m.prototype[a]=function(){return x(b.apply(this._wrapped,arguments),this._chain)}});m.prototype.chain=function(){this._chain=true;return this};m.prototype.value=function(){return this._wrapped}}).call(this);