From b53336c2d065b2e139446a30a99f0b836bc32a18 Mon Sep 17 00:00:00 2001 From: Eric Banks Date: Mon, 6 May 2013 14:57:01 -0400 Subject: [PATCH 01/10] Added hidden mode for BQSR to force all read groups to be the same one. * Very useful for debugging sample-specific issues * This argument got lost in the transition from BQSR v1 to v2 * Added unit test to cover this case --- .../bqsr/RecalibrationArgumentCollection.java | 4 ++++ .../covariates/ReadGroupCovariate.java | 8 ++++++- .../ReadGroupCovariateUnitTest.java | 21 ++++++++++++++----- 3 files changed, 27 insertions(+), 6 deletions(-) diff --git a/protected/java/src/org/broadinstitute/sting/gatk/walkers/bqsr/RecalibrationArgumentCollection.java b/protected/java/src/org/broadinstitute/sting/gatk/walkers/bqsr/RecalibrationArgumentCollection.java index 0a4899f1c..5a2cdc7a6 100644 --- a/protected/java/src/org/broadinstitute/sting/gatk/walkers/bqsr/RecalibrationArgumentCollection.java +++ b/protected/java/src/org/broadinstitute/sting/gatk/walkers/bqsr/RecalibrationArgumentCollection.java @@ -219,6 +219,10 @@ public class RecalibrationArgumentCollection { @Argument(fullName = "force_platform", shortName = "fP", required = false, doc = "If provided, the platform of EVERY read will be forced to be the provided String. Valid options are illumina, 454, and solid.") public String FORCE_PLATFORM = null; + @Hidden + @Argument(fullName = "force_readgroup", shortName = "fRG", required = false, doc = "If provided, the read group of EVERY read will be forced to be the provided String.") + public String FORCE_READGROUP = null; + @Hidden @Output(fullName = "recal_table_update_log", shortName = "recal_table_update_log", required = false, doc = "If provided, log all updates to the recalibration tables to the given file. For debugging/testing purposes only", defaultToStdout = false) public PrintStream RECAL_TABLE_UPDATE_LOG = null; diff --git a/protected/java/src/org/broadinstitute/sting/utils/recalibration/covariates/ReadGroupCovariate.java b/protected/java/src/org/broadinstitute/sting/utils/recalibration/covariates/ReadGroupCovariate.java index 350cf5d33..664c1786e 100644 --- a/protected/java/src/org/broadinstitute/sting/utils/recalibration/covariates/ReadGroupCovariate.java +++ b/protected/java/src/org/broadinstitute/sting/utils/recalibration/covariates/ReadGroupCovariate.java @@ -93,10 +93,13 @@ public class ReadGroupCovariate implements RequiredCovariate { private final HashMap readGroupLookupTable = new HashMap(); private final HashMap readGroupReverseLookupTable = new HashMap(); private int nextId = 0; + private String forceReadGroup; // Initialize any member variables using the command-line arguments passed to the walkers @Override - public void initialize(final RecalibrationArgumentCollection RAC) {} + public void initialize(final RecalibrationArgumentCollection RAC) { + forceReadGroup = RAC.FORCE_READGROUP; + } @Override public void recordValues(final GATKSAMRecord read, final ReadCovariates values) { @@ -170,6 +173,9 @@ public class ReadGroupCovariate implements RequiredCovariate { * @return platform unit or readgroup id */ private String readGroupValueFromRG(final GATKSAMReadGroupRecord rg) { + if ( forceReadGroup != null ) + return forceReadGroup; + final String platformUnit = rg.getPlatformUnit(); return platformUnit == null ? rg.getId() : platformUnit; } diff --git a/protected/java/test/org/broadinstitute/sting/utils/recalibration/ReadGroupCovariateUnitTest.java b/protected/java/test/org/broadinstitute/sting/utils/recalibration/ReadGroupCovariateUnitTest.java index 0878fba82..0b2df6369 100644 --- a/protected/java/test/org/broadinstitute/sting/utils/recalibration/ReadGroupCovariateUnitTest.java +++ b/protected/java/test/org/broadinstitute/sting/utils/recalibration/ReadGroupCovariateUnitTest.java @@ -75,26 +75,37 @@ public class ReadGroupCovariateUnitTest { final String expected = "SAMPLE.1"; GATKSAMReadGroupRecord rg = new GATKSAMReadGroupRecord("MY.ID"); rg.setPlatformUnit(expected); - runTest(rg, expected); + runTest(rg, expected, covariate); } @Test(enabled = true) public void testMissingPlatformUnit() { final String expected = "MY.7"; GATKSAMReadGroupRecord rg = new GATKSAMReadGroupRecord(expected); - runTest(rg, expected); + runTest(rg, expected, covariate); } - private void runTest(GATKSAMReadGroupRecord rg, String expected) { + @Test(enabled = true) + public void testForceReadgroup() { + final RecalibrationArgumentCollection forcedRAC = new RecalibrationArgumentCollection(); + forcedRAC.FORCE_READGROUP = "FOO"; + final ReadGroupCovariate forcedCovariate = new ReadGroupCovariate(); + forcedCovariate.initialize(forcedRAC); + + final GATKSAMReadGroupRecord rg = new GATKSAMReadGroupRecord("NOT_FOO"); + runTest(rg, "FOO", forcedCovariate); + } + + private static void runTest(final GATKSAMReadGroupRecord rg, final String expected, final ReadGroupCovariate covariate) { GATKSAMRecord read = ReadUtils.createRandomRead(10); read.setReadGroup(rg); ReadCovariates readCovariates = new ReadCovariates(read.getReadLength(), 1); covariate.recordValues(read, readCovariates); - verifyCovariateArray(readCovariates.getMismatchesKeySet(), expected); + verifyCovariateArray(readCovariates.getMismatchesKeySet(), expected, covariate); } - private void verifyCovariateArray(int[][] values, String expected) { + private static void verifyCovariateArray(final int[][] values, final String expected, final ReadGroupCovariate covariate) { for (int[] value : values) { String actual = covariate.formatKey(value[0]); Assert.assertEquals(actual, expected); From d242f1bba3fa0578ec9cfa7aa35a5bce3e99616e Mon Sep 17 00:00:00 2001 From: Eric Banks Date: Mon, 6 May 2013 19:00:58 -0400 Subject: [PATCH 02/10] Secondary alignments were not handled correctly in IndelRealigner * This is emerging now because BWA-MEM produces lots of reads that are not primary alignments * The ConstrainedMateFixingManager class used by IndelRealigner was mis-adjusting SAM flags because it was getting confused by these secondary alignments * Added unit test to cover this case --- .../indels/ConstrainedMateFixingManager.java | 13 ++- .../ConstrainedMateFixingManagerUnitTest.java | 108 ++++++++++++++++++ 2 files changed, 119 insertions(+), 2 deletions(-) create mode 100644 protected/java/test/org/broadinstitute/sting/gatk/walkers/indels/ConstrainedMateFixingManagerUnitTest.java diff --git a/protected/java/src/org/broadinstitute/sting/gatk/walkers/indels/ConstrainedMateFixingManager.java b/protected/java/src/org/broadinstitute/sting/gatk/walkers/indels/ConstrainedMateFixingManager.java index 5411c5d98..c98fe4d3c 100644 --- a/protected/java/src/org/broadinstitute/sting/gatk/walkers/indels/ConstrainedMateFixingManager.java +++ b/protected/java/src/org/broadinstitute/sting/gatk/walkers/indels/ConstrainedMateFixingManager.java @@ -212,6 +212,15 @@ public class ConstrainedMateFixingManager { public int getNReadsInQueue() { return waitingReads.size(); } + /** + * For testing purposes only + * + * @return the list of reads currently in the queue + */ + protected List getReadsInQueueForTesting() { + return new ArrayList(waitingReads); + } + public boolean canMoveReads(GenomeLoc earliestPosition) { if ( DEBUG ) logger.info("Refusing to realign? " + earliestPosition + " vs. " + lastLocFlushed); @@ -233,7 +242,7 @@ public class ConstrainedMateFixingManager { addRead(newRead, modifiedReads.contains(newRead), false); } - private void addRead(SAMRecord newRead, boolean readWasModified, boolean canFlush) { + protected void addRead(SAMRecord newRead, boolean readWasModified, boolean canFlush) { if ( DEBUG ) logger.info("New read pos " + newRead.getAlignmentStart() + " OP = " + newRead.getAttribute("OP") + " " + readWasModified); //final long curTime = timer.currentTime(); @@ -265,7 +274,7 @@ public class ConstrainedMateFixingManager { // fix mates, as needed // Since setMateInfo can move reads, we potentially need to remove the mate, and requeue // it to ensure proper sorting - if ( newRead.getReadPairedFlag() ) { + if ( newRead.getReadPairedFlag() && !newRead.getNotPrimaryAlignmentFlag() ) { SAMRecordHashObject mate = forMateMatching.get(newRead.getReadName()); if ( mate != null ) { // 1. Frustratingly, Picard's setMateInfo() method unaligns (by setting the reference contig diff --git a/protected/java/test/org/broadinstitute/sting/gatk/walkers/indels/ConstrainedMateFixingManagerUnitTest.java b/protected/java/test/org/broadinstitute/sting/gatk/walkers/indels/ConstrainedMateFixingManagerUnitTest.java new file mode 100644 index 000000000..9bcd7a3a3 --- /dev/null +++ b/protected/java/test/org/broadinstitute/sting/gatk/walkers/indels/ConstrainedMateFixingManagerUnitTest.java @@ -0,0 +1,108 @@ +/* +* By downloading the PROGRAM you agree to the following terms of use: +* +* BROAD INSTITUTE - SOFTWARE LICENSE AGREEMENT - FOR ACADEMIC NON-COMMERCIAL RESEARCH PURPOSES ONLY +* +* This Agreement is made between the Broad Institute, Inc. with a principal address at 7 Cambridge Center, Cambridge, MA 02142 (BROAD) and the LICENSEE and is effective at the date the downloading is completed (EFFECTIVE DATE). +* +* WHEREAS, LICENSEE desires to license the PROGRAM, as defined hereinafter, and BROAD wishes to have this PROGRAM utilized in the public interest, subject only to the royalty-free, nonexclusive, nontransferable license rights of the United States Government pursuant to 48 CFR 52.227-14; and +* WHEREAS, LICENSEE desires to license the PROGRAM and BROAD desires to grant a license on the following terms and conditions. +* NOW, THEREFORE, in consideration of the promises and covenants made herein, the parties hereto agree as follows: +* +* 1. DEFINITIONS +* 1.1 PROGRAM shall mean copyright in the object code and source code known as GATK2 and related documentation, if any, as they exist on the EFFECTIVE DATE and can be downloaded from http://www.broadinstitute/GATK on the EFFECTIVE DATE. +* +* 2. LICENSE +* 2.1 Grant. Subject to the terms of this Agreement, BROAD hereby grants to LICENSEE, solely for academic non-commercial research purposes, a non-exclusive, non-transferable license to: (a) download, execute and display the PROGRAM and (b) create bug fixes and modify the PROGRAM. +* The LICENSEE may apply the PROGRAM in a pipeline to data owned by users other than the LICENSEE and provide these users the results of the PROGRAM provided LICENSEE does so for academic non-commercial purposes only. For clarification purposes, academic sponsored research is not a commercial use under the terms of this Agreement. +* 2.2 No Sublicensing or Additional Rights. LICENSEE shall not sublicense or distribute the PROGRAM, in whole or in part, without prior written permission from BROAD. LICENSEE shall ensure that all of its users agree to the terms of this Agreement. LICENSEE further agrees that it shall not put the PROGRAM on a network, server, or other similar technology that may be accessed by anyone other than the LICENSEE and its employees and users who have agreed to the terms of this agreement. +* 2.3 License Limitations. Nothing in this Agreement shall be construed to confer any rights upon LICENSEE by implication, estoppel, or otherwise to any computer software, trademark, intellectual property, or patent rights of BROAD, or of any other entity, except as expressly granted herein. LICENSEE agrees that the PROGRAM, in whole or part, shall not be used for any commercial purpose, including without limitation, as the basis of a commercial software or hardware product or to provide services. LICENSEE further agrees that the PROGRAM shall not be copied or otherwise adapted in order to circumvent the need for obtaining a license for use of the PROGRAM. +* +* 3. OWNERSHIP OF INTELLECTUAL PROPERTY +* LICENSEE acknowledges that title to the PROGRAM shall remain with BROAD. The PROGRAM is marked with the following BROAD copyright notice and notice of attribution to contributors. LICENSEE shall retain such notice on all copies. LICENSEE agrees to include appropriate attribution if any results obtained from use of the PROGRAM are included in any publication. +* Copyright 2012 Broad Institute, Inc. +* Notice of attribution: The GATK2 program was made available through the generosity of Medical and Population Genetics program at the Broad Institute, Inc. +* LICENSEE shall not use any trademark or trade name of BROAD, or any variation, adaptation, or abbreviation, of such marks or trade names, or any names of officers, faculty, students, employees, or agents of BROAD except as states above for attribution purposes. +* +* 4. INDEMNIFICATION +* LICENSEE shall indemnify, defend, and hold harmless BROAD, and their respective officers, faculty, students, employees, associated investigators and agents, and their respective successors, heirs and assigns, (Indemnitees), against any liability, damage, loss, or expense (including reasonable attorneys fees and expenses) incurred by or imposed upon any of the Indemnitees in connection with any claims, suits, actions, demands or judgments arising out of any theory of liability (including, without limitation, actions in the form of tort, warranty, or strict liability and regardless of whether such action has any factual basis) pursuant to any right or license granted under this Agreement. +* +* 5. NO REPRESENTATIONS OR WARRANTIES +* THE PROGRAM IS DELIVERED AS IS. BROAD MAKES NO REPRESENTATIONS OR WARRANTIES OF ANY KIND CONCERNING THE PROGRAM OR THE COPYRIGHT, EXPRESS OR IMPLIED, INCLUDING, WITHOUT LIMITATION, WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NONINFRINGEMENT, OR THE ABSENCE OF LATENT OR OTHER DEFECTS, WHETHER OR NOT DISCOVERABLE. BROAD EXTENDS NO WARRANTIES OF ANY KIND AS TO PROGRAM CONFORMITY WITH WHATEVER USER MANUALS OR OTHER LITERATURE MAY BE ISSUED FROM TIME TO TIME. +* IN NO EVENT SHALL BROAD OR ITS RESPECTIVE DIRECTORS, OFFICERS, EMPLOYEES, AFFILIATED INVESTIGATORS AND AFFILIATES BE LIABLE FOR INCIDENTAL OR CONSEQUENTIAL DAMAGES OF ANY KIND, INCLUDING, WITHOUT LIMITATION, ECONOMIC DAMAGES OR INJURY TO PROPERTY AND LOST PROFITS, REGARDLESS OF WHETHER BROAD SHALL BE ADVISED, SHALL HAVE OTHER REASON TO KNOW, OR IN FACT SHALL KNOW OF THE POSSIBILITY OF THE FOREGOING. +* +* 6. ASSIGNMENT +* This Agreement is personal to LICENSEE and any rights or obligations assigned by LICENSEE without the prior written consent of BROAD shall be null and void. +* +* 7. MISCELLANEOUS +* 7.1 Export Control. LICENSEE gives assurance that it will comply with all United States export control laws and regulations controlling the export of the PROGRAM, including, without limitation, all Export Administration Regulations of the United States Department of Commerce. Among other things, these laws and regulations prohibit, or require a license for, the export of certain types of software to specified countries. +* 7.2 Termination. LICENSEE shall have the right to terminate this Agreement for any reason upon prior written notice to BROAD. If LICENSEE breaches any provision hereunder, and fails to cure such breach within thirty (30) days, BROAD may terminate this Agreement immediately. Upon termination, LICENSEE shall provide BROAD with written assurance that the original and all copies of the PROGRAM have been destroyed, except that, upon prior written authorization from BROAD, LICENSEE may retain a copy for archive purposes. +* 7.3 Survival. The following provisions shall survive the expiration or termination of this Agreement: Articles 1, 3, 4, 5 and Sections 2.2, 2.3, 7.3, and 7.4. +* 7.4 Notice. Any notices under this Agreement shall be in writing, shall specifically refer to this Agreement, and shall be sent by hand, recognized national overnight courier, confirmed facsimile transmission, confirmed electronic mail, or registered or certified mail, postage prepaid, return receipt requested. All notices under this Agreement shall be deemed effective upon receipt. +* 7.5 Amendment and Waiver; Entire Agreement. This Agreement may be amended, supplemented, or otherwise modified only by means of a written instrument signed by all parties. Any waiver of any rights or failure to act in a specific instance shall relate only to such instance and shall not be construed as an agreement to waive any rights or fail to act in any other instance, whether or not similar. This Agreement constitutes the entire agreement among the parties with respect to its subject matter and supersedes prior agreements or understandings between the parties relating to its subject matter. +* 7.6 Binding Effect; Headings. This Agreement shall be binding upon and inure to the benefit of the parties and their respective permitted successors and assigns. All headings are for convenience only and shall not affect the meaning of any provision of this Agreement. +* 7.7 Governing Law. This Agreement shall be construed, governed, interpreted and applied in accordance with the internal laws of the Commonwealth of Massachusetts, U.S.A., without regard to conflict of laws principles. +*/ + +package org.broadinstitute.sting.gatk.walkers.indels; + +import net.sf.samtools.SAMFileHeader; +import net.sf.samtools.SAMRecord; +import org.broadinstitute.sting.BaseTest; +import org.broadinstitute.sting.utils.GenomeLocParser; +import org.broadinstitute.sting.utils.sam.ArtificialSAMUtils; +import org.broadinstitute.sting.utils.sam.GATKSAMRecord; +import org.testng.Assert; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.Test; + +import java.util.List; + + +public class ConstrainedMateFixingManagerUnitTest extends BaseTest { + + private static SAMFileHeader header; + private static GenomeLocParser genomeLocParser; + + @BeforeClass + public void beforeClass() { + header = ArtificialSAMUtils.createArtificialSamHeader(3, 1, 100); + genomeLocParser = new GenomeLocParser(header.getSequenceDictionary()); + } + @Test + public void testSecondaryAlignmentsDoNotInterfere() { + final List properReads = ArtificialSAMUtils.createPair(header, "foo", 1, 10, 30, true, false); + final GATKSAMRecord read1 = properReads.get(0); + read1.setAlignmentStart(8); // move the read + read1.setFlags(99); // first in proper pair, mate negative strand + + final GATKSAMRecord read2Primary = properReads.get(1); + read2Primary.setFlags(147); // second in pair, mate unmapped, not primary alignment + + Assert.assertEquals(read1.getInferredInsertSize(), 21); + + final GATKSAMRecord read2NonPrimary = new GATKSAMRecord(read2Primary); + read2NonPrimary.setFlags(393); // second in proper pair, on reverse strand + + final ConstrainedMateFixingManager manager = new ConstrainedMateFixingManager(null, genomeLocParser, 1000, 1000, 1000); + manager.addRead(read1, true, false); + manager.addRead(read2NonPrimary, false, false); + manager.addRead(read2Primary, false, false); + + Assert.assertEquals(manager.getNReadsInQueue(), 3); + + for ( final SAMRecord read : manager.getReadsInQueueForTesting() ) { + if ( read.getFirstOfPairFlag() ) { + Assert.assertEquals(read.getFlags(), 99); + Assert.assertEquals(read.getInferredInsertSize(), 23); + } else if ( read.getNotPrimaryAlignmentFlag() ) { + Assert.assertEquals(read.getFlags(), 393); + Assert.assertEquals(read.getInferredInsertSize(), -21); + } else { + Assert.assertEquals(read.getFlags(), 147); + Assert.assertEquals(read.getInferredInsertSize(), -23); + } + } + } + +} \ No newline at end of file From 2b86ab02bee8d5fff0175f4faebdb4083e229266 Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Tue, 7 May 2013 12:11:46 -0400 Subject: [PATCH 03/10] Improve queue script jobreport visualization script -- the Queue jobreport PDF script now provides a high-level summary of the de-scattered runtimes of each analysis, so that its easy to see where your script is spending its time across scatters. --- .../sting/queue/util/queueJobReport.R | 53 +++++++++++++++---- 1 file changed, 42 insertions(+), 11 deletions(-) diff --git a/public/R/scripts/org/broadinstitute/sting/queue/util/queueJobReport.R b/public/R/scripts/org/broadinstitute/sting/queue/util/queueJobReport.R index 36e6343cb..2bc0a2fa5 100644 --- a/public/R/scripts/org/broadinstitute/sting/queue/util/queueJobReport.R +++ b/public/R/scripts/org/broadinstitute/sting/queue/util/queueJobReport.R @@ -3,6 +3,7 @@ library(ggplot2) library(gplots) library(tools) library(reshape) +library(plyr) # # Standard command line switch. Can we loaded interactively for development @@ -14,7 +15,7 @@ if ( onCMDLine ) { inputFileName = args[1] outputPDF = args[2] } else { - inputFileName = "Q-26618@gsa4.jobreport.txt" + inputFileName = "~/Desktop/broadLocal/projects/pipelinePerformance/FullProcessingPipeline.jobreport.txt" #inputFileName = "/humgen/gsa-hpprojects/dev/depristo/oneOffProjects/Q-25718@node1149.jobreport.txt" #inputFileName = "/humgen/gsa-hpprojects/dev/depristo/oneOffProjects/rodPerformanceGoals/history/report.082711.txt" outputPDF = NA @@ -35,13 +36,11 @@ allJobsFromReport <- function(report) { # # Creates segmentation plots of time (x) vs. job (y) with segments for the duration of the job # -plotJobsGantt <- function(gatkReport, sortOverall, includeText) { +plotJobsGantt <- function(gatkReport, sortOverall, title, includeText) { allJobs = allJobsFromReport(gatkReport) if ( sortOverall ) { - title = "All jobs, by analysis, by start time" allJobs = allJobs[order(allJobs$analysisName, allJobs$startTime, decreasing=T), ] } else { - title = "All jobs, sorted by start time" allJobs = allJobs[order(allJobs$startTime, decreasing=T), ] } allJobs$index = 1:nrow(allJobs) @@ -54,11 +53,11 @@ plotJobsGantt <- function(gatkReport, sortOverall, includeText) { p <- p + theme_bw() p <- p + geom_segment(aes(xend=relDoneTime, yend=index), size=1, arrow=arrow(length = unit(0.1, "cm"))) if ( includeText ) - p <- p + geom_text(aes(x=relDoneTime, label=ganttName, hjust=-0.2), size=2) - p <- p + xlim(0, maxRelTime * 1.1) + p <- p + geom_text(aes(x=relStartTime, label=ganttName, hjust=0, vjust=-1), size=2) + p <- p + xlim(0, maxRelTime * 1.3) p <- p + xlab(paste("Start time, relative to first job", RUNTIME_UNITS)) p <- p + ylab("Job number") - p <- p + opts(title=title) + p <- p + ggtitle(title) print(p) } @@ -182,6 +181,27 @@ plotTimeByHost <- function(gatkReportData) { plotMe("Jittered points", geom_jitter) } +mergeScattersForAnalysis <- function(table) { + #allJobs$ganttName = paste(allJobs$jobName, "@", allJobs$exechosts) + + ddply(table, .(analysisName, iteration), summarize, + jobName = analysisName[1], + exechosts = paste(length(exechosts), "hosts"), + formattedStartTime = "NA", + formattedDoneTime = "NA", + intermediate = intermediate[1], + startTime = min(startTime), + doneTime = min(startTime) + sum(runtime), + runtime = sum(runtime)) +} + +mergeScatters <- function(report) { + newReport = list() + for ( name in names(gatkReportData) ) { + newReport[[name]] = mergeScattersForAnalysis(gatkReportData[[name]]) + } + newReport +} # read the table gatkReportData <- gsa.read.gatkreport(inputFileName) @@ -192,13 +212,24 @@ if ( ! is.na(outputPDF) ) { pdf(outputPDF, height=8.5, width=11) } -plotJobsGantt(gatkReportData, T, F) -plotJobsGantt(gatkReportData, F, F) +plotJobsGantt(gatkReportData, T, "All jobs, by analysis, by start time", F) +plotJobsGantt(gatkReportData, F, "All jobs, sorted by start time", F) plotProgressByTime(gatkReportData) + +# plots summarizing overall costs, merging scattered counts +merged.by.scatter = mergeScatters(gatkReportData) +plotJobsGantt(merged.by.scatter, F, "Jobs merged by scatter by start time", T) + +merged.as.df = do.call(rbind.data.frame, merged.by.scatter)[,c("analysisName", "runtime")] +merged.as.df$percent = merged.as.df$runtime / sum(merged.as.df$runtime) * 100 +merged.as.df.formatted = data.frame(analysisName=merged.as.df$analysisName,runtime=prettyNum(merged.as.df$runtime), percent=prettyNum(merged.as.df$percent,digits=2)) +textplot(merged.as.df.formatted[order(merged.as.df$runtime),], show.rownames=F) +title("Total runtime for each analysis") + plotTimeByHost(gatkReportData) for ( group in gatkReportData ) { - print(group) - plotGroup(group) + #print(group) + plotGroup(group) } if ( ! is.na(outputPDF) ) { From 52c20a8416e577f89b1143d38705641930b94dc1 Mon Sep 17 00:00:00 2001 From: David Roazen Date: Tue, 7 May 2013 14:21:15 -0400 Subject: [PATCH 04/10] Rev picard, sam-jdk, tribble, and variant to version 1.91.1453 -mainly for the seek() performance optimization --- .../repository/net.sf/picard-1.90.1442.xml | 3 --- ...ard-1.90.1442.jar => picard-1.91.1453.jar} | Bin 1644252 -> 1645927 bytes .../repository/net.sf/picard-1.91.1453.xml | 3 +++ settings/repository/net.sf/sam-1.90.1442.xml | 3 --- .../{sam-1.90.1442.jar => sam-1.91.1453.jar} | Bin 617595 -> 620475 bytes settings/repository/net.sf/sam-1.91.1453.xml | 3 +++ ...le-1.90.1442.jar => tribble-1.91.1453.jar} | Bin 265519 -> 265537 bytes ...le-1.90.1442.xml => tribble-1.91.1453.xml} | 2 +- ...nt-1.90.1446.jar => variant-1.91.1453.jar} | Bin 556173 -> 556361 bytes ...nt-1.90.1446.xml => variant-1.91.1453.xml} | 2 +- 10 files changed, 8 insertions(+), 8 deletions(-) delete mode 100644 settings/repository/net.sf/picard-1.90.1442.xml rename settings/repository/net.sf/{picard-1.90.1442.jar => picard-1.91.1453.jar} (85%) create mode 100644 settings/repository/net.sf/picard-1.91.1453.xml delete mode 100644 settings/repository/net.sf/sam-1.90.1442.xml rename settings/repository/net.sf/{sam-1.90.1442.jar => sam-1.91.1453.jar} (85%) create mode 100644 settings/repository/net.sf/sam-1.91.1453.xml rename settings/repository/org.broad/{tribble-1.90.1442.jar => tribble-1.91.1453.jar} (82%) rename settings/repository/org.broad/{tribble-1.90.1442.xml => tribble-1.91.1453.xml} (76%) rename settings/repository/org.broadinstitute/{variant-1.90.1446.jar => variant-1.91.1453.jar} (80%) rename settings/repository/org.broadinstitute/{variant-1.90.1446.xml => variant-1.91.1453.xml} (71%) diff --git a/settings/repository/net.sf/picard-1.90.1442.xml b/settings/repository/net.sf/picard-1.90.1442.xml deleted file mode 100644 index 4ec267817..000000000 --- a/settings/repository/net.sf/picard-1.90.1442.xml +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/settings/repository/net.sf/picard-1.90.1442.jar b/settings/repository/net.sf/picard-1.91.1453.jar similarity index 85% rename from settings/repository/net.sf/picard-1.90.1442.jar rename to settings/repository/net.sf/picard-1.91.1453.jar index caf2bc09d6fb177f5165882867bf8a16fe6cb5ac..f196ee5a43bcee0abd96f130acb28135c89792a1 100644 GIT binary patch delta 99563 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 z z5Ou(tNgnM5zGNpIY$O}yV69nQ(ka@(K>|FmK+Iq~@LPI7OhFv`3>!~sTWBZiNqR?n zQ%GM1GgxP6Se|uYod=(hMA=8O??Fi@Y#ci$^~PCnX8eP=_sUfes!xtPKo;7HGp{0| z{J*BTukeLk3Rm=pPjpY?xc;|jlDn%4i%8A=c^Zm&1ZGMk@cxwIGg5}sk7vqD6AH~n!!P`iiE9D z4?L^FBL%svBFOD4gp>z9V$1u?U zV7j|ME{}-RS=j({QQmpjaAyEuzJJ#&_i+srTGOEwm|RXt0@Lab=n<$Vg6!~BVlWGoMvPC5rPE^~OpUY2^akQRm24kQqmSui z8E^)Df)84m_@J|i1v;Bppc=72E3piUIiDW5WG>bER+1Wh?U2q{giIE*_cYSxE7(#S zb{6=+&=d@J%~#ULD*9MW7ZUKXmOj?mFk|531NzutgQOuoFf0YbHFF4~XlyHeY|~g5 z`o-X%O@n_n+i9mMTw{BzG}05W#9{mFg}~<;Z-+$Sej6%qfQk*iRAqLE9v{);u*N>d zKo)1VsX6`ct#=nu?Njlsv7-nOUvG5Zj2!z1SX(`@N4!Bjm}dJ9Kn90u`><`XjFILFbVUn(iZ%xa}FAE z8ni6oyBdA`2qU`m0kHEhw$3inM-BSG%zBAFQs@IS?Ujo3g>s!|S2gx4eSkEaXTNE< zXo!!d^zl1Au2Y%*a6kY8RRTe}$^I0>ZLSz`cC*`88tOLRFc4k?^6JnD=zyrjZ3m9L z!02dC)aEX|-J{2SFnrIHvE&n-WM?kcy#-*k(+ven-A8JgKbH5W_vxSNDP_r70!{O2R!#6xez+>~99wXsl+4}~>5H?D5*z2w-)1X9$!(;oK z9wXrK@=XtrOVMy&b`&d}1Bey--0|923}A(g6Q}mMqvWJD@0`+I+~4OegW*@e?|yER zzByby#*4zo+zsJ7@|b(RCI__ovT2=4*iu%)i3=Ttl>EX?*I^ob;oc7WY~saPcefZr z9;6`}qY;>CO|bcH$_j#>RfGPu#;k>Z?Dy{83O15|oOf5w-bhAcBN>B@WFc%MOaI_* zZ0IrB#{W+F8d{mY78w_0`98bg{xAps(?9wTcPT|(!3v5UH{I=3H4=m!*BbllmXV_o+MX#bHos+UupeziC&mKUQG_Ai**az-M?yu!?p~Z%%I^yG3ZaPVN{>p7NG{k>j92VswYID~C9BUeQ<>Erw8mJnIde zIj!b*tthH?@l=QJq%NKlxn(UjWX^%bmx&%QR{b+3dhQuYt$in;P}`8#d{VJ$ zy2k@e@gU%N7#7lvEJV$u6r}daJS=aY~D+AvA!_)i%qpW!Nbjz zcRSW>B?dJJE41(rOAQhGM|ijhU+f7M;j=wf|JfOyKqP#wMihP?D?k=f zVf2~}GPZ%QW_wCP!PM%%RJXt89MA6HLRhkBI8(=IL`_<|(9%X>wF9~8?hjip7BTNh zG)aBW1C`LU2EOt0$Zn#~`yNtLzV&@i17l@8e`%c~XV9Z(L{g^LZ5;W=F7v=FuRm#- zrYb6OA@X z{A<6_v(|u&h<~{ao15R3iCrsx-t8eHn1%Lu zqKpx-Z{pp&{fgj}yofx?%}OU!eDO>gE}7={oXrbmT5W&!e4;`wLFBpUDW-0RxwZxu zJrg+};y?Y1rzz(v{rN6?zBR_4b3X4Vp8YCEZPIU^LQdIcEi*F9W0TXu^Ck#80^g@a=!vCN_rxz-tdX%ibE3m7KI5o++r~qcJVPI2$0^p>u0}cyD;)!!ch{S>#KmckMP9MjB zB7pMp9c*A4COAl}@Kx9ajmPNp@i3=Te4%n+?E zkAz{z841<{f1IuhfoBHt2&Nh2Hb>#Kj;;&YKqw5kYa?CSY#8azGt-Tjh@A&r3#a7f zBe_wcU?*=PPY|6ud3VUJdzE|6rGXHZ80a$i(&!G0=+nb%6G^EO#~P-32fJk7#1$)b zXfLMwz2ci;UOKdO4ENp&!7101bng@uQea}jL~kVaYcXQcMDIo@JxreD-54zefpFAl z!TF09Cz3H(MG0;}E@n-JcFmM#oMLfyq4BJ&?t?qDNxyWw0WGrg^7^ z$P)jO9<;PQX3q918B#DoGnRVYZ{za!3%nhT!FL4m+%0iFM^xpbXT@N{zo~gcf zLf2vNd=>H4rVr7SABt_MzOpgP;f1g4KWviBzi0Y8Sc(X1Q2!U}-(lzxv`=Vi*t7m6lt~g3$Lio+<1u%BV<;_lnv32Q_k%%}` z%bH6}9qrTQ2=4Mj;b!7eXP-y8r7jGyM2YY&K8K2AL`)Z7v@$<*;jYl=!ml%=40^3C zk*ie@EHJ@U4#?nyn9#*nKbC-YSAO|UH&m2dnE;?sPLbZlS90OyF2SOGcb`LopN(r< z>-~H%uLQR=0KBBn0H5INCr%IYIb^Z>U+Vatc-0lf9$z^txRU5M%%{uLj(>5~TNF4* zF0h2QMp@uC5&p3fER&RR{;F4?RCS!EkgFI_f{ZR`gvfEnx8cx85| zh(UM65N`Lsci}Pi#I!GdEAd65aUWljY`OQX>w^md`N`qnKl^W8`XLXc2{E$c{Ea@& z{doQR&jCd)u*Aiq4fxZgpm(c6U$chpk5&$=fTaPsHwnkO^pQEU;b9;=H`m3ctVr|9 zuj3-S&FsH*=@EsqBk3ZzpNX1}raXH`#P$Pn?5-%o7p{EQD|#YW*71vLTXtT? z7$X;mm!BmW-~-P1#8+A1ha!h9x&%T?EcYp(jof2fU{jh+6sB)p^lMG*Cdmah zNv<|V@=0I0#EGY7x4e-ei_QKZ>bXns0lvIN-lUw=kKyaD$34d!LGGRG2vnZC6rq+J z9~Z_HpDP*ep!F((ti_Y2KXAvhFa;X z+o}xOoE+&?$@*KD{?iJXXy!`peNBrR^hxRy1qL*^z;5b-s-O%2w~B8E`kWFUr&Mir z^nJ7`xxiufW#%B09uJMm>^w9VW8^w#Wsl!==?4!{k}&am73U|7hxx2xCq8AdCw@C| zxFN&ESW-Zw?LWQ|DUgun{WYjt7?7S^m4Hb8KV{)X#$VhgnqM-JX#BbF%HBq!kZThF zABS$B1_wOM-VO1U$V0uR{V=S{5fB)14Flk(6;u{B@%hBjs!FT~3soY-#vwjkMtZz? zY~E;8om{1WNV|&}ksANaV_r`QGE&)Ff9IWmT*w8IoPza?NLMlxk0j&<{QO-_z+5E; zE2|lh$&<#}@P)+np=O~f_b-!N7zvYW9RT00RW^LA*s(-`P%7p&naSu6U$%QtgfVh~ zH3^!X9qEmlNF-*{KiMko5A%7{Ci}C2!oNH+L*Unc7!HTu zj?{lCe`5lb3x;FEebMT}lZpucLWvSjhx;6|ybF#WZ&VLU5ptzoCkn(X@`Z#gEoP7Q zIposi_Vgp~Yz7>;zy?3@6&B*jUn_;gjuAeutW(n&G3Uj%BYYmqv2U{i!$+EpGD<(pO4mcj|24(l7(g$OUHi-AyCKTmlEI;bMf}r^^_=FG`)=2fQX% zG$7e#w-ICYIi-N;bQ{Zxu}946-)MPx(P0c@a)Favvj;{~Wj;jr4tRh_vqqVb)TSu~ z55z%;2re+2-cPe5g^J6ge7bD?guU`|`_iTVn5*z9wixa)EStbxtv8oG&u7 z1avXxuBO^vuLLJuJaa{?8Vx#E6T(t*?+Lz;7=KRHMAdO7e5cz{CmN#Z$<+=h@}QpxzJyqUnH&yHFD#o~e-%9#DPF8#q(5iuGhZpT&CDaMR9E4`y{ zsTeVDJd!F0l^zrxI|#O1vRC;n2rKPWs>gs1uP$+o!p?!boE(4FK0QDx^% zBdLBj)tJ{=-B@0|p;(!Qr2e=g!xtuP4HDm{nV5Jp4ZDWi_r?qlM8II<_4@s>^SKO6FVoFh5UVQie(ovB^TIY zg*wW_Jz`ErOQ`sKvblbAb?mZE#7;)FW4f3CSGro9;%;X&&Nq`ya`%`;=c|q+$px;C zu6H+MRqbKPDUNhEW6AN;EaKh+ah1{@-`j)=pW@3aYZiUDQ0cEgWXJ^$puHna*xplo zu@W{o@8tfttxPwT-`c5uKGB5DHOUewewt#oLXQ_s+y9M($pt1}Z)#R?X9Cfv6=V)~ z{%MGt;dJrx?RK7$VG)DoTfAbzWJH)T)r`>B_3OUU7?9)wJNV1_GD5M}jpv#nk}jbG z$>l=^S%2%&Yh{|HDItKs*?n@a(deS0(KNJMk7Xvn_-VdE*_(c~<0@PsQZ%2qJk9JN z@5JgW?;&w=fdufqEZDToFmaGL-K>Cj)u+Ec@M4hy2Nh@8f9ulEeP$*;`!h?72n&le z_Wx@Zber22?Mbd$HaL-i=u6`CbYEjh2X?L>-**MNiCo}<>Aa{q!`D+%w88^tkJ^WV zlB*w()U8{%n&GPr3(6*)&A6U)CcZj41+Gj0Xh?)ep6RP56C9Oe(1Lj?T z?Es*=;>>JcOF5>VwG+og$vMbr{Qx7U38_IAF=39`U}KuAr*EJc$OY!~{;+KLsLZ)$ z!MDEiT(Z@@3LMjHF&`p!Y)J4avx1{a9s>+cxw6V?XVMVIWV!J^u5GiN1Bls%fQoG<_k>fqH6hFn^I`aS&h+N=mWpNLJ zPY`?Wn4mLqGKg;eFP_<@r36}3blQW-{d*)WQNMcqFV=`V?*aASS#E}}47P}J@B6&6 zYNf4AX=y99m}m5jUg{ z)~bUx`(j07;R}(l8{DbiT>}WYKz_ppz+x^aDYgo8CDV$Zx+aba6!C&Z99`rqCI(n= zl{0dYIVM7Cp369g@ktJ)%>G-K{SBDi!NC?fY(0Nz zuG2`1T+!?_X@2yK7y;(+&_@ za)FH+Sy95qh`EWHP58H%oY-!AcTN_kmY~RktG)>kzSNvmzk892fL06YXhz8Buay;JuuznPVA(L7=;T<2BCP548X!t^T4ol( znbG{%Bh0?!0*lajycr>Exz8pJE%Ui#E9~E&F|cYuV*6T3dqVnxypEmRcUhW@X+!bJYxPk&obbXbA*`Dd32m9bNE${n(7z;^pj z^eqyAuD$;5bRUTsyR{GLRs!Fc{ zm+xXCA{WRT-S#p&k|wUKHXFybWdF0)ps(ZtJLPFPh!n5#6%nUhnnhGwV@4RaI$mwTh|sd3jF2E=zp%Q*ldK45uH`#%#tGR7 zxEcWwerqlx)D#2PnFw3k(fsRA3o=G7a02%9mayf;l{jm-=)KM?&4XLzJBw(X_`%f) zNW8`Zv-q8sp)~jQU|hUhYi2W`$(l`vK)T5VW>amu8SC_8%`5)-$3&;+oj%$p?yd*M z_tAq5KTTnSRsgnqdN+U*uO9oSAF0sAe;EWnuSA(co-uW$Q`@% z+lnhm}$G-@5e9VdBho%xqUq0iy?A04|Q(V9qnU z0pBUNHB|J>g3tSDmEcF1o5%&iFDh@qPxuH#ji{0ApxbA?qs7%tDDOqA;;7{^HbYu} zLLOAA%4V|=H=5>*&yN)`xxh_|6N@@CQbC!XZ}Y_l;>c#CH?Ol1ly(T6@NZU9#TsuD zr!ct51(t7R7X$uuS6irPx5dobdAMZCSP(vPfz!?5UfJ-lvX%vZvaO|eCA2NMK=`SH zv*GQ+z13%2o=@%Z{g~-6 zU3dAI*!+-W4Hsezb_>7}AESn|+$+FS;m)gFj`_RGU0%>>5-t0&aWIBPEv$mNn zW4X|#M-y}ixxkX;J)RxO^*WP~1zpenfbx(FY?)7gFyObJw}prcE6w!3o$vl861_w& zkjZi50>Q_LW!rsjiKOprygSp2l{UG+K~nn?ff~E$xhFG6}FqZFiUj;4g2!e-+(GF0g{@uV=$s#K2-fHWB-~Em&-RVUmYW4^FGv3pATt>42Qh z-!fuOC>s@0gBo{a_WMFGdC6n+;5%w@KYo}Sf(}fa0I~fLk9bDi% zzW7mgq(~9_5GDI}r&+QG>oRhl!(yCVAo?E;63KoHzoTcsK6{KzKijjP$k>hizbN}gc;zzo5OMINS*e>zpVasU6BxO`=Dt$l%}9I9LcdW?d57Ql zrj5zV1_WQ|KgR|mQal|ef&!3h`$qJ2>D3y_7-C0#dsz|tp;@B+A!*$|$6zAYFaW-C zO9MWwzb#H~vt=`nA6sLL*!dxVhPR2+S@55BY}w%uV95oh?-^^rU-{aui%R><^k;s1TipVb zBNsS<6q{kd&z*@LCk}$FTF&9C!^Dw&s9M5eBmC7qU!1I3#k=GGg$O>~(1G~W>%VpB z(?1~4+A@KAgHCo3^ZiI5evgqr_z(8n;;!FkoF9DT*nhQpZp1ILSC*_Ii2c z#=03GF63GTl%&s>Z$!Fx6zxgFSXQsR>v?gJa1h1meZ~MfIs}PAa57kA95hoNI=|X^ z4_27u0w=%`zZ;QW-mr(hX4?csUH*9licYRsKuNCMHQ-NIvKM+Stm0c5KCVs(R%via z0r1~EGTH= zrAiR9&=ReprF_rY?>^_ve(!gFzxB`A&suw4dwBPL_V?X_8`0?=!H$;3&xY4m5BT#( zsIw$_&G681L+ZrP->@ZOEo*Xe{)Z@d7IoYR>OmQ0@^sGH;bV>_V_aiNP)uIwL$k}A zZeeR%<+DCZ(VSS+-rR2@ohoy7)4p2wxsuopV-1Tsv&?=SS*n;697nbp!68)kDlXXb zmtmoHX=vWENzFj>14&krP&HLki={o^H~7NaWX6(U+OWgv056$(B5zW_uUB@!1dE!R zEmDPS-!#E-yK=cUD9j&O`59`MB}Iw5tXPYKN_YAM=?ks!wukr5>(rW-->Bp ze5xrd2u54-tB?`AIAQCFxoFNT-Mnb7vKi-P1pO+U?ZqNsbBA+>E@OLuMfGW~UYFSV zNF3~~^%zetnD*})R0&I0#rSvkJ;r@aVU)E^I;qq<4-PyFFpH{i+mA+U$Fedj&J;@J z+hiMx{-RN@Bfzq#GlGs54PR(7g;2(JiH~|QA~V;CwFM+YNznLk#&;5}>5HB-H*Lop ziAC*(VJ#UIP1UZ(CMroYX$K>PgKGO+i`&$=5)LZC%^)Y0UJnMz8jgdA@KGI3zDXlE zyr8ufUSEkB{HjVBer?jk$yCna^=-Z39YlJ1Ex%B+46`d1wcQ@-=!FJSeI|-#>JFLJ zaW%eM#-eMnq$*k6m8~f??`$&Bf=x31ne#JEbae;fpVCtckJ;&r6(5_jcj8;|^uAQF z%NhA!WB%<=Tjr1jt5*ni54^t>)3#_Dl>fNNMYDIwcyi7?Q$+PU5zoq@!XjCRnr!sU z7@6*p&g=G$x8qncM6Dvzi;RDO-rMDj5akm4Khw7z#bky>osaJ+WN4N^#q>YDC=(MB z7S%Ugo69JCV|&BYl=|(K38D>Z3cl-c5jol+79yn~=hwXe&iV$^v_F% zm=fsfB^k-vYu-qB6&{F19ak%B8l-IaGdRo8bRRq+A9s%2hi}>QE}H1{CUYn~x(_>Z zd|M*#!i__ezYkjtm7Xy7IXDg8){1AqsaLudHqF5ec8FSPPyQl}LBT<{F2IGcLRQhg z^51|1U{T%D%7@Ha?EPAs6DgyW8TZ8Z%V;)Q&CT|pgR}JUwpw<4MIEKq`u)!CA_mh- z$E>^19$D1Z^o4r&(sO%l?}0{`y)t(aCfR%#U$n3L)2Q!*XvQtl4F2nWET zW^z=4u8>4oFPkk3#vrQF5bU~hq4o%wY|ce>v#5po^#olrk+R1_YwBTHxuZuH1=6qs zyvHDzAl>=Z&&SJ||sh98z@iBX(8t8H7Bx$6?ji3Jld?{CGl zl3uK)RqBa~%~4c-2p;f<9R4>6*H!T`jtxxgyJ_AM^h*|XIx^@fh9=UMv$9Sr7FTW+ zO~a@C5Dp5b{CvE2Qj%-&qc4AiCt`d1FD}W|E}j}sXk04LlRZn*U^Ox4WsvqEUm~=VbdD0Q0i8BYRwvqWGr12rSx|-rJ}bi8T6~C!5=)Z+f9v+ zAnR=wY2l@dEG_By53=0L=5IHsb;V8D4EX}L|ly+2hUw7z1(v#rMZF%@53mN`VO?(DE(*bnK1rD!rTFm zB6%!n3cj*$1N;u0-4lMIVCCeIRPr-YnLC-oai8{IW()oNGj^NUUa$1Ux$&qr7IoG& zb&7_MJLYUHpi>;}Rz<lEVcWS|Jm|9a4c#q;IXq-$DI$0q{NPTx$Q!K++Bk_q*zT~%BVz|y5DLR7Y#%y zbT_YBPX~{q6v9_(;RPRq(3i&nVXtIL^ea~|e_>I_f!&{J__$B4k(B?t%+<-1!S6gZ z6w7IdTDPY4j8CBBHPZ9NyO#~}tYEt;NnK&KQU4n5p(fjd`Vb==Y|$ZSNfpYAMRiHJ zPF>4J%TLH?0)hr#ei|1GS)k+nt(Z18ODZhQwwdS;D2O6?&$kT@*`LG!;x|YFZ22}9 zMd3@h06ekvljrFr20S**1IV-W7QJ4i@o?@(Gv`O6gF#epn>|u0ln$^tsKhgcX`FYd zWoHaAEIqv`WM7pyLn^r6w>77KpTd4PH_lcqCib9%r;vo5Z|MLxwXAifX=%**(?P>) zITN#VKyT_BDvY<;(slnRayIGwow5Cz;&2i~9S>{4AIo*N zV6`oQhMbfG?m^$#aesy2wkt#(r>1Z4;v>m)My6_Wt4B6oK>@I+!{-l`pyuP5WYGwF zydhOfQs-XwG>8<9tqYx_cj~8{+^W{YFN~`t91GdO6Qj<0SKqs9vEk=dodV9 zc~G;@&pJUFXMtLGSV!S3D26Oi8h!m8?b1+XEQw0BZ9Xd%)}M7o3G62sH@B~Xi)K-s zZrpi^?R45^A?rEWllHtA*f`z>{{vC`yB~;5&%ysi`5|M)nhgNJ5O{bfNU z<2#7{Y|8Y{+=RIai<;!{7|rUsNV`9^0I>nt&P&q^92xuO#b8qb60hVlD87Nxp!3pI z#il3q_!?P9O}Byp@*;EO&8J+TX$ zzAWjA{ZS+75ki}HT5{O;@2@-jNs=)e#f@w>e%E&WCIsg(9rQ8zKKV}gK?&#= z5E4w8p5zTn%fIB=P*t}(wq`X{So{l4GjLmYAUDun3ID*NuC+IHl?t=6LLA~UolqE9 z_sgA~Pd94BqW1gh?m{7vhWsiEOMgV!*t~0&3M@`ZECSm(AKw*Qc>3GPMt_|4xlo88 z@scvnl7xc(c(T!7^jI$y@RqWL#$J^kUwuPaPd^bplh;#_;rRc=!Gn*Cps4*6-uu;lZEjDJl9Yk zfp7Q_b*-|{A+dAph`~}Xv#P)3VBD#wYAF=lG!|IVu=qU(Dy&Dts%Rs^uwrU~*A5sU zVpm>qP|9Lrrcj8a0GxIZTPxAmv9+?HXDEG`9U76YZZYdmHyCq#MpMAj@H%qL-y8Vt zhR5J@SRPd3EFLI~1KSVJNx<<6y#A_#akY_=C-I(#9{gy^c$qW(l?P*kZZk}3iH9E| z7s@rX{@80g;ZfoJPeTi2aMg>qle+5!XL65D{llSHNEMeR5-Q7 z7DyMNAS{Zv0vg|h#kEHz;95KzW+h!fh_9y0~&uC z0TzEMzihG7>I%%~r0c8s`gM!9-#vR%5nWdFVkP)Bw|1<~( zN$Ue*BBQgi8pp@(yES~=wlFI_iW4cKfCRH@{$1Eh|5Xcj*M~L5MtE2-4f4hLz1*$naq7%y!E3CWM4#ZisfdPYHb-F#ghefq>iJwG(epoYMTmi!sW<1Z zcSZQTzLW#ti57-8)jmgYoI25_G0wbYUc6P`@MFr&r!g&ONl;2H5pVB!?Z4UBrG3NF z$vBM({dT2>2R&zuXch_|r(*h@&opR)k1JecN-z(0_Ccue2@jl!0sT;sEb25R)sH@I>WZN92w&ildXYOp8Q9ksxtnC>@ML<= z*QIUhDZGJ78NP4RpdJQ6tss~*a012+mTn6E`7jM%mB6f9ezM@60+rx4jB+gM=eYJx z#`h7$%hZBtK(MF_*a6X+Rkxq3wSY3;pc9{+SkyLuA)cX0voDicy824U)D?ub&VKh_{-Y-v8GLwIx zv@F;S;{#!N;|mQyg~M^%HwX6$gk>f>ti*l>i@Ha*X0%tyL3?0S;F$*TDjYIPl9G~d z-_h{4AX(ZvH=@x8T$!aoL6m5vFZ|>lE3@>0Ba8{64POi2TCCx-=7CDCD#)evUcujY zItcmu-z8eOpxTQIc4<4|UM9a_<8*7(at#k&Kc~22A+o0P_8DvYjdcy|d#L>U^r@B3 z<7Zq>PFSyLmTm7UXZ2Hf-F z<0oVp6Ts{ux)4H^Uut{Z<5qCuRW* zw7RrexR?D8!;LzgKO5uVEwtB%NXt$%> zzV9xLy_Drh5#7Rj;J_#SF`8p{#ff(9r4+<`AzrDYA&*hD-IXCwIuD)^_3lWv73L+y z^P^E?r1$VL;vuK1_h|o5O$oeo7McSdI}wS_a%b$~k9?rvL3x)d+6S}Tku4Ox+qB4Q zDx8u$U(cA*ZYg>;i@KQFRP4ov(r2MAx6k6$R1@lY$ftZYg@n1<`P8kZN5fp5e4T3? zjc{1h;h;~#TxmX!ub~5Bu3X~n796l*`%Ws0a5??XjdY^;{}0Id0;K=| delta 98557 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�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 diff --git a/settings/repository/net.sf/picard-1.91.1453.xml b/settings/repository/net.sf/picard-1.91.1453.xml new file mode 100644 index 000000000..5d1bf41e8 --- /dev/null +++ b/settings/repository/net.sf/picard-1.91.1453.xml @@ -0,0 +1,3 @@ + + + diff --git a/settings/repository/net.sf/sam-1.90.1442.xml b/settings/repository/net.sf/sam-1.90.1442.xml deleted file mode 100644 index 918ea6ff0..000000000 --- a/settings/repository/net.sf/sam-1.90.1442.xml +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/settings/repository/net.sf/sam-1.90.1442.jar b/settings/repository/net.sf/sam-1.91.1453.jar similarity index 85% rename from settings/repository/net.sf/sam-1.90.1442.jar rename to settings/repository/net.sf/sam-1.91.1453.jar index f6e003657d94954a8a3443cf744e175f3db08351..d25dc0f66ea469b443a1a4519d028c1af8765551 100644 GIT binary patch delta 36363 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 Bb + + diff --git a/settings/repository/org.broad/tribble-1.90.1442.jar b/settings/repository/org.broad/tribble-1.91.1453.jar similarity index 82% rename from settings/repository/org.broad/tribble-1.90.1442.jar rename to settings/repository/org.broad/tribble-1.91.1453.jar index 75b4c2fc5b4e143c6eaf8ae07f8a9373ab16db93..aad68d8ddf69bdf20fe565eea5189dbfe78d9869 100644 GIT binary patch delta 17807 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 UqnYp$shAK)Sn#_Su`ucX0J|aEAOHXW delta 17865 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 diff --git a/settings/repository/org.broad/tribble-1.90.1442.xml b/settings/repository/org.broad/tribble-1.91.1453.xml similarity index 76% rename from settings/repository/org.broad/tribble-1.90.1442.xml rename to settings/repository/org.broad/tribble-1.91.1453.xml index 01b944fe4..93c75edab 100644 --- a/settings/repository/org.broad/tribble-1.90.1442.xml +++ b/settings/repository/org.broad/tribble-1.91.1453.xml @@ -1,3 +1,3 @@ - + diff --git a/settings/repository/org.broadinstitute/variant-1.90.1446.jar b/settings/repository/org.broadinstitute/variant-1.91.1453.jar similarity index 80% rename from settings/repository/org.broadinstitute/variant-1.90.1446.jar rename to settings/repository/org.broadinstitute/variant-1.91.1453.jar index 9ca3c4f38aa4910227f0f704183965f3be8f1431..d339781e93ad1d8ced991b7219c3cff8009b1828 100644 GIT binary patch delta 43000 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 delta 42717 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 z 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 - + From fa8a47ceef0840fa23966f573c1adf63fdb33e0d Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Thu, 18 Apr 2013 08:17:15 -0400 Subject: [PATCH 05/10] Replace DeBruijnAssembler with ReadThreadingAssembler Problem ------- The DeBruijn assembler was too slow. The cause of the slowness was the need to construct many kmer graphs (from max read length in the interval to 11 kmer, in increments of 6 bp). This need to build many kmer graphs was because the assembler (1) needed long kmers to assemble through regions where a shorter kmer was non-unique in the reference, as we couldn't split cycles in the reference (2) shorter kmers were needed to be sensitive to differences from the reference near the edge of reads, which would be lost often when there was chain of kmers of longer length that started before and after the variant. Solution -------- The read threading assembler uses a fixed kmer, in this implementation by default two graphs with 10 and 25 kmers. The algorithm operates as follows: identify all non-unique kmers of size K among all reads and the reference for each sequence (ref and read): find a unique starting position of the sequence in the graph by matching to a unique kmer, or starting a new source node if non exist for each base in the sequence from the starting vertex kmer: look at the existing outgoing nodes of current vertex V. If the base in sequence matches the suffix of outgoing vertex N, read the sequence to N, and continue If no matching next vertex exists, find a unique vertex with kmer K. If one exists, merge the sequence into this vertex, and continue If a merge vertex cannot be found, create a new vertex (note this vertex may have a kmer identical to another in the graph, if it is not unique) and thread the sequence to this vertex, and continue This algorithm has a key property: it can robustly use a very short kmer without introducing cycles, as we will create paths through the graph through regions that aren't unique w.r.t. the sequence at the given kmer size. This allows us to assemble well with even very short kmers. This commit includes many critical changes to the haplotype caller to make it fast, sensitive, and accurate on deep and shallow WGS and exomes, the key changes are highlighted below: -- The ReadThreading assembler keeps track of the maximum edge multiplicity per sample in the graph, so that we prune per sample, not across all samples. This change is essential to operate effectively when there are many deep samples (i.e., 100 exomes) -- A new pruning algorithm that will only prune linear paths where the maximum edge weight among all edges in the path have < pruningFactor. This makes pruning more robust when you have a long chain of bases that have high multiplicity at the start but only barely make it back into the main path in the graph. -- We now do a global SmithWaterman to compute the cigar of a Path, instead of the previous bubble-based SmithWaterman optimization. This change is essential for us to get good variants from our paths when the kmer size is small. It also ensures that we produce a cigar from a path that only depends only the sequence of bases in the path, unlike the previous approach which would depend on both the bases and the way the path was decomposed into vertices, which depended on the kmer size we used. -- Removed MergeHeadlessIncomingSources, which was introducing problems in the graphs in some cases, and just isn't the safest operation. Since we build a kmer graph of size 10, this operation is no longer necessary as it required a perfect match of 10 bp to merge anyway. -- The old DebruijnAssembler is still available with a command line option -- The number of paths we take forward from the each assembly graph is now capped at a factor per sample, so that we allow 128 paths for a single sample up to 10 x nSamples as necessary. This is an essential change to make the system work well for large numbers of samples. -- Add a global mismapping parameter to the HC likelihood calculation: The phredScaledGlobalReadMismappingRate reflects the average global mismapping rate of all reads, regardless of their mapping quality. This term effects the probability that a read originated from the reference haploytype, regardless of its edit distance from the reference, in that the read could have originated from the reference haplotype but from another location in the genome. Suppose a read has many mismatches from the reference, say like 5, but has a very high mapping quality of 60. Without this parameter, the read would contribute 5 * Q30 evidence in favor of its 5 mismatch haplotype compared to reference, potentially enough to make a call off that single read for all of these events. With this parameter set to Q30, though, the maximum evidence against the reference that this (and any) read could contribute against reference is Q30. -- Controllable via a command line argument, defaulting to Q60 rate. Results from 20:10-11 mb for branch are consistent with the previous behavior, but this does help in cases where you have rare very divergent haplotypes -- Reduced ActiveRegionExtension from 200 bp to 100 bp, which is a performance win and the large extension is largely unnecessary with the short kmers used with the read threading assembler Infrastructure changes / improvements ------------------------------------- -- Refactored BaseGraph to take a subclass of BaseEdge, so that we can use a MultiSampleEdge in the ReadThreadingAssembler -- Refactored DeBruijnAssembler, moving common functionality into LocalAssemblyEngine, which now more directly manages the subclasses, requiring them to only implement a assemble() method that takes ref and reads and provides a List, which the LocalAssemblyEngine takes forward to compute haplotypes and other downstream operations. This allows us to have only a limited amount of code that differentiates the Debruijn and ReadThreading assemblers -- Refactored active region trimming code into ActiveRegionTrimmer class -- Cleaned up the arguments in HaplotypeCaller, reorganizing them and making arguments @Hidden and @Advanced as appropriate. Renamed several arguments now that the read threading assembler is the default -- LocalAssemblyEngineUnitTest reads in the reference sequence from b37, and assembles with synthetic reads intervals from 10-11 mbs with only the reference sequence as well as artificial snps, deletions, and insertions. -- Misc. updates to Smith Waterman code. Added generic interface to called not surpisingly SmithWaterman, making it easier to have alternative implementations. -- Many many more unit tests throughout the entire assembler, and in random utilities --- .../haplotypecaller/ActiveRegionTrimmer.java | 142 ++++ .../haplotypecaller/DeBruijnAssembler.java | 436 +----------- .../haplotypecaller/HaplotypeCaller.java | 357 ++++++---- .../walkers/haplotypecaller/KMerCounter.java | 18 +- .../gatk/walkers/haplotypecaller/Kmer.java | 8 + .../LikelihoodCalculationEngine.java | 59 +- .../haplotypecaller/LocalAssemblyEngine.java | 406 ++++++++++- .../haplotypecaller/graphs/BaseEdge.java | 61 +- .../haplotypecaller/graphs/BaseGraph.java | 233 ++++--- .../graphs/BaseGraphIterator.java | 6 +- .../haplotypecaller/graphs/BaseVertex.java | 16 + .../graphs/CommonSuffixSplitter.java | 4 +- .../haplotypecaller/graphs/DeBruijnGraph.java | 17 +- .../graphs/DeBruijnVertex.java | 2 +- .../haplotypecaller/graphs/GraphUtils.java | 48 +- .../haplotypecaller/graphs/KBestPaths.java | 28 +- .../graphs/LowWeightChainPruner.java | 170 +++++ .../graphs/MultiSampleEdge.java | 123 ++++ .../walkers/haplotypecaller/graphs/Path.java | 263 +++---- .../haplotypecaller/graphs/SeqGraph.java | 58 +- .../graphs/SharedSequenceMerger.java | 4 +- .../graphs/SharedVertexSequenceSplitter.java | 4 +- .../readthreading/MultiDeBruijnVertex.java | 118 ++++ .../readthreading/ReadThreadingAssembler.java | 162 +++++ .../readthreading/ReadThreadingGraph.java | 640 ++++++++++++++++++ .../readthreading/SequenceForKmers.java | 93 +++ .../DeBruijnAssemblerUnitTest.java | 53 -- ...lexAndSymbolicVariantsIntegrationTest.java | 6 +- .../HaplotypeCallerIntegrationTest.java | 18 +- .../KMerCounterCaseFixUnitTest.java | 103 +-- .../LocalAssemblyEngineUnitTest.java | 280 ++++++++ .../graphs/BaseEdgeUnitTest.java | 5 +- .../graphs/BaseGraphUnitTest.java | 2 +- .../graphs/CommonSuffixMergerUnitTest.java | 8 +- .../graphs/CommonSuffixSplitterUnitTest.java | 6 +- .../graphs/GraphUtilsUnitTest.java | 120 ++++ .../graphs/KBestPathsUnitTest.java | 147 ++-- .../graphs/LowWeightChainPrunerUnitTest.java | 163 +++++ .../graphs/MultiSampleEdgeUnitTest.java | 103 +++ .../haplotypecaller/graphs/PathUnitTest.java | 80 +++ .../graphs/SeqGraphUnitTest.java | 13 +- .../SharedVertexSequenceSplitterUnitTest.java | 8 +- .../ReadThreadingAssemblerUnitTest.java | 213 ++++++ .../ReadThreadingGraphUnitTest.java | 191 ++++++ .../SequenceForKmersUnitTest.java | 80 +++ .../traversals/TraverseActiveRegions.java | 2 +- .../org/broadinstitute/sting/utils/Utils.java | 41 ++ .../smithwaterman/SWPairwiseAlignment.java | 27 +- .../utils/smithwaterman/SmithWaterman.java | 56 ++ .../sting/utils/UtilsUnitTest.java | 27 + .../utils/clipping/ReadClipperUnitTest.java | 26 + 51 files changed, 4146 insertions(+), 1108 deletions(-) create mode 100644 protected/java/src/org/broadinstitute/sting/gatk/walkers/haplotypecaller/ActiveRegionTrimmer.java create mode 100644 protected/java/src/org/broadinstitute/sting/gatk/walkers/haplotypecaller/graphs/LowWeightChainPruner.java create mode 100644 protected/java/src/org/broadinstitute/sting/gatk/walkers/haplotypecaller/graphs/MultiSampleEdge.java create mode 100644 protected/java/src/org/broadinstitute/sting/gatk/walkers/haplotypecaller/readthreading/MultiDeBruijnVertex.java create mode 100644 protected/java/src/org/broadinstitute/sting/gatk/walkers/haplotypecaller/readthreading/ReadThreadingAssembler.java create mode 100644 protected/java/src/org/broadinstitute/sting/gatk/walkers/haplotypecaller/readthreading/ReadThreadingGraph.java create mode 100644 protected/java/src/org/broadinstitute/sting/gatk/walkers/haplotypecaller/readthreading/SequenceForKmers.java create mode 100644 protected/java/test/org/broadinstitute/sting/gatk/walkers/haplotypecaller/LocalAssemblyEngineUnitTest.java create mode 100644 protected/java/test/org/broadinstitute/sting/gatk/walkers/haplotypecaller/graphs/GraphUtilsUnitTest.java create mode 100644 protected/java/test/org/broadinstitute/sting/gatk/walkers/haplotypecaller/graphs/LowWeightChainPrunerUnitTest.java create mode 100644 protected/java/test/org/broadinstitute/sting/gatk/walkers/haplotypecaller/graphs/MultiSampleEdgeUnitTest.java create mode 100644 protected/java/test/org/broadinstitute/sting/gatk/walkers/haplotypecaller/graphs/PathUnitTest.java create mode 100644 protected/java/test/org/broadinstitute/sting/gatk/walkers/haplotypecaller/readthreading/ReadThreadingAssemblerUnitTest.java create mode 100644 protected/java/test/org/broadinstitute/sting/gatk/walkers/haplotypecaller/readthreading/ReadThreadingGraphUnitTest.java create mode 100644 protected/java/test/org/broadinstitute/sting/gatk/walkers/haplotypecaller/readthreading/SequenceForKmersUnitTest.java create mode 100644 public/java/src/org/broadinstitute/sting/utils/smithwaterman/SmithWaterman.java diff --git a/protected/java/src/org/broadinstitute/sting/gatk/walkers/haplotypecaller/ActiveRegionTrimmer.java b/protected/java/src/org/broadinstitute/sting/gatk/walkers/haplotypecaller/ActiveRegionTrimmer.java new file mode 100644 index 000000000..063e3b218 --- /dev/null +++ b/protected/java/src/org/broadinstitute/sting/gatk/walkers/haplotypecaller/ActiveRegionTrimmer.java @@ -0,0 +1,142 @@ +/* +* By downloading the PROGRAM you agree to the following terms of use: +* +* BROAD INSTITUTE - SOFTWARE LICENSE AGREEMENT - FOR ACADEMIC NON-COMMERCIAL RESEARCH PURPOSES ONLY +* +* This Agreement is made between the Broad Institute, Inc. with a principal address at 7 Cambridge Center, Cambridge, MA 02142 (BROAD) and the LICENSEE and is effective at the date the downloading is completed (EFFECTIVE DATE). +* +* WHEREAS, LICENSEE desires to license the PROGRAM, as defined hereinafter, and BROAD wishes to have this PROGRAM utilized in the public interest, subject only to the royalty-free, nonexclusive, nontransferable license rights of the United States Government pursuant to 48 CFR 52.227-14; and +* WHEREAS, LICENSEE desires to license the PROGRAM and BROAD desires to grant a license on the following terms and conditions. +* NOW, THEREFORE, in consideration of the promises and covenants made herein, the parties hereto agree as follows: +* +* 1. DEFINITIONS +* 1.1 PROGRAM shall mean copyright in the object code and source code known as GATK2 and related documentation, if any, as they exist on the EFFECTIVE DATE and can be downloaded from http://www.broadinstitute/GATK on the EFFECTIVE DATE. +* +* 2. LICENSE +* 2.1 Grant. Subject to the terms of this Agreement, BROAD hereby grants to LICENSEE, solely for academic non-commercial research purposes, a non-exclusive, non-transferable license to: (a) download, execute and display the PROGRAM and (b) create bug fixes and modify the PROGRAM. +* The LICENSEE may apply the PROGRAM in a pipeline to data owned by users other than the LICENSEE and provide these users the results of the PROGRAM provided LICENSEE does so for academic non-commercial purposes only. For clarification purposes, academic sponsored research is not a commercial use under the terms of this Agreement. +* 2.2 No Sublicensing or Additional Rights. LICENSEE shall not sublicense or distribute the PROGRAM, in whole or in part, without prior written permission from BROAD. LICENSEE shall ensure that all of its users agree to the terms of this Agreement. LICENSEE further agrees that it shall not put the PROGRAM on a network, server, or other similar technology that may be accessed by anyone other than the LICENSEE and its employees and users who have agreed to the terms of this agreement. +* 2.3 License Limitations. Nothing in this Agreement shall be construed to confer any rights upon LICENSEE by implication, estoppel, or otherwise to any computer software, trademark, intellectual property, or patent rights of BROAD, or of any other entity, except as expressly granted herein. LICENSEE agrees that the PROGRAM, in whole or part, shall not be used for any commercial purpose, including without limitation, as the basis of a commercial software or hardware product or to provide services. LICENSEE further agrees that the PROGRAM shall not be copied or otherwise adapted in order to circumvent the need for obtaining a license for use of the PROGRAM. +* +* 3. OWNERSHIP OF INTELLECTUAL PROPERTY +* LICENSEE acknowledges that title to the PROGRAM shall remain with BROAD. The PROGRAM is marked with the following BROAD copyright notice and notice of attribution to contributors. LICENSEE shall retain such notice on all copies. LICENSEE agrees to include appropriate attribution if any results obtained from use of the PROGRAM are included in any publication. +* Copyright 2012 Broad Institute, Inc. +* Notice of attribution: The GATK2 program was made available through the generosity of Medical and Population Genetics program at the Broad Institute, Inc. +* LICENSEE shall not use any trademark or trade name of BROAD, or any variation, adaptation, or abbreviation, of such marks or trade names, or any names of officers, faculty, students, employees, or agents of BROAD except as states above for attribution purposes. +* +* 4. INDEMNIFICATION +* LICENSEE shall indemnify, defend, and hold harmless BROAD, and their respective officers, faculty, students, employees, associated investigators and agents, and their respective successors, heirs and assigns, (Indemnitees), against any liability, damage, loss, or expense (including reasonable attorneys fees and expenses) incurred by or imposed upon any of the Indemnitees in connection with any claims, suits, actions, demands or judgments arising out of any theory of liability (including, without limitation, actions in the form of tort, warranty, or strict liability and regardless of whether such action has any factual basis) pursuant to any right or license granted under this Agreement. +* +* 5. NO REPRESENTATIONS OR WARRANTIES +* THE PROGRAM IS DELIVERED AS IS. BROAD MAKES NO REPRESENTATIONS OR WARRANTIES OF ANY KIND CONCERNING THE PROGRAM OR THE COPYRIGHT, EXPRESS OR IMPLIED, INCLUDING, WITHOUT LIMITATION, WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NONINFRINGEMENT, OR THE ABSENCE OF LATENT OR OTHER DEFECTS, WHETHER OR NOT DISCOVERABLE. BROAD EXTENDS NO WARRANTIES OF ANY KIND AS TO PROGRAM CONFORMITY WITH WHATEVER USER MANUALS OR OTHER LITERATURE MAY BE ISSUED FROM TIME TO TIME. +* IN NO EVENT SHALL BROAD OR ITS RESPECTIVE DIRECTORS, OFFICERS, EMPLOYEES, AFFILIATED INVESTIGATORS AND AFFILIATES BE LIABLE FOR INCIDENTAL OR CONSEQUENTIAL DAMAGES OF ANY KIND, INCLUDING, WITHOUT LIMITATION, ECONOMIC DAMAGES OR INJURY TO PROPERTY AND LOST PROFITS, REGARDLESS OF WHETHER BROAD SHALL BE ADVISED, SHALL HAVE OTHER REASON TO KNOW, OR IN FACT SHALL KNOW OF THE POSSIBILITY OF THE FOREGOING. +* +* 6. ASSIGNMENT +* This Agreement is personal to LICENSEE and any rights or obligations assigned by LICENSEE without the prior written consent of BROAD shall be null and void. +* +* 7. MISCELLANEOUS +* 7.1 Export Control. LICENSEE gives assurance that it will comply with all United States export control laws and regulations controlling the export of the PROGRAM, including, without limitation, all Export Administration Regulations of the United States Department of Commerce. Among other things, these laws and regulations prohibit, or require a license for, the export of certain types of software to specified countries. +* 7.2 Termination. LICENSEE shall have the right to terminate this Agreement for any reason upon prior written notice to BROAD. If LICENSEE breaches any provision hereunder, and fails to cure such breach within thirty (30) days, BROAD may terminate this Agreement immediately. Upon termination, LICENSEE shall provide BROAD with written assurance that the original and all copies of the PROGRAM have been destroyed, except that, upon prior written authorization from BROAD, LICENSEE may retain a copy for archive purposes. +* 7.3 Survival. The following provisions shall survive the expiration or termination of this Agreement: Articles 1, 3, 4, 5 and Sections 2.2, 2.3, 7.3, and 7.4. +* 7.4 Notice. Any notices under this Agreement shall be in writing, shall specifically refer to this Agreement, and shall be sent by hand, recognized national overnight courier, confirmed facsimile transmission, confirmed electronic mail, or registered or certified mail, postage prepaid, return receipt requested. All notices under this Agreement shall be deemed effective upon receipt. +* 7.5 Amendment and Waiver; Entire Agreement. This Agreement may be amended, supplemented, or otherwise modified only by means of a written instrument signed by all parties. Any waiver of any rights or failure to act in a specific instance shall relate only to such instance and shall not be construed as an agreement to waive any rights or fail to act in any other instance, whether or not similar. This Agreement constitutes the entire agreement among the parties with respect to its subject matter and supersedes prior agreements or understandings between the parties relating to its subject matter. +* 7.6 Binding Effect; Headings. This Agreement shall be binding upon and inure to the benefit of the parties and their respective permitted successors and assigns. All headings are for convenience only and shall not affect the meaning of any provision of this Agreement. +* 7.7 Governing Law. This Agreement shall be construed, governed, interpreted and applied in accordance with the internal laws of the Commonwealth of Massachusetts, U.S.A., without regard to conflict of laws principles. +*/ + +package org.broadinstitute.sting.gatk.walkers.haplotypecaller; + +import org.apache.log4j.Logger; +import org.broadinstitute.sting.utils.GenomeLoc; +import org.broadinstitute.sting.utils.GenomeLocParser; +import org.broadinstitute.sting.utils.activeregion.ActiveRegion; +import org.broadinstitute.variant.variantcontext.VariantContext; + +import java.util.LinkedList; +import java.util.List; +import java.util.TreeSet; + +/** + * Trim down an active region based on a set of variants found across the haplotypes within the region + * + * User: depristo + * Date: 4/27/13 + * Time: 2:10 PM + */ +class ActiveRegionTrimmer { + private final static Logger logger = Logger.getLogger(ActiveRegionTrimmer.class); + private final boolean logTrimming; + private final int snpPadding, nonSnpPadding, maxDistanceInExtensionForGenotyping; + private final GenomeLocParser parser; + + /** + * Create a new ActiveRegionTrimmer + * + * @param logTrimming should we log our trimming events? + * @param snpPadding how much bp context should we ensure around snps? + * @param nonSnpPadding how much bp context should we ensure around anything not a snp? + * @param maxDistanceInExtensionForGenotyping the max extent we are will to go into the extended region of the + * origin active region in order to properly genotype events in the + * non-extended active region? + * @param parser a genome loc parser so we can create genome locs + */ + ActiveRegionTrimmer(boolean logTrimming, int snpPadding, int nonSnpPadding, int maxDistanceInExtensionForGenotyping, GenomeLocParser parser) { + if ( snpPadding < 0 ) throw new IllegalArgumentException("snpPadding must be >= 0 but got " + snpPadding); + if ( nonSnpPadding < 0 ) throw new IllegalArgumentException("nonSnpPadding must be >= 0 but got " + nonSnpPadding); + if ( maxDistanceInExtensionForGenotyping < 0 ) throw new IllegalArgumentException("maxDistanceInExtensionForGenotyping must be >= 0 but got " + maxDistanceInExtensionForGenotyping); + if ( parser == null ) throw new IllegalArgumentException("parser cannot be null"); + + this.logTrimming = logTrimming; + this.snpPadding = snpPadding; + this.nonSnpPadding = nonSnpPadding; + this.maxDistanceInExtensionForGenotyping = maxDistanceInExtensionForGenotyping; + this.parser = parser; + } + + /** + * Trim down the active region to a region large enough to properly genotype the events found within the active + * region span, excluding all variants that only occur within its extended span. + * + * This function merely creates the region, but it doesn't populate the reads back into the region. + * + * @param region our full active region + * @param allVariantsWithinExtendedRegion all of the variants found in the entire region, sorted by their start position + * @return a new ActiveRegion trimmed down to just what's needed for genotyping, or null if we couldn't do this successfully + */ + public ActiveRegion trimRegion(final ActiveRegion region, final TreeSet allVariantsWithinExtendedRegion) { + if ( allVariantsWithinExtendedRegion.isEmpty() ) // no variants, so just return the current region + return null; + + final List withinActiveRegion = new LinkedList(); + int pad = snpPadding; + GenomeLoc trimLoc = null; + for ( final VariantContext vc : allVariantsWithinExtendedRegion ) { + final GenomeLoc vcLoc = parser.createGenomeLoc(vc); + if ( region.getLocation().overlapsP(vcLoc) ) { + if ( ! vc.isSNP() ) // if anything isn't a SNP use the bigger padding + pad = nonSnpPadding; + trimLoc = trimLoc == null ? vcLoc : trimLoc.endpointSpan(vcLoc); + withinActiveRegion.add(vc); + } + } + + // we don't actually have anything in the region after removing variants that don't overlap the region's full location + if ( trimLoc == null ) return null; + + final GenomeLoc maxSpan = parser.createPaddedGenomeLoc(region.getLocation(), maxDistanceInExtensionForGenotyping); + final GenomeLoc idealSpan = parser.createPaddedGenomeLoc(trimLoc, pad); + final GenomeLoc finalSpan = maxSpan.intersect(idealSpan); + + final ActiveRegion trimmedRegion = region.trim(finalSpan); + if ( logTrimming ) { + logger.info("events : " + withinActiveRegion); + logger.info("trimLoc : " + trimLoc); + logger.info("pad : " + pad); + logger.info("idealSpan : " + idealSpan); + logger.info("maxSpan : " + maxSpan); + logger.info("finalSpan : " + finalSpan); + logger.info("regionSpan : " + trimmedRegion.getExtendedLoc() + " size is " + trimmedRegion.getExtendedLoc().size()); + } + return trimmedRegion; + } +} diff --git a/protected/java/src/org/broadinstitute/sting/gatk/walkers/haplotypecaller/DeBruijnAssembler.java b/protected/java/src/org/broadinstitute/sting/gatk/walkers/haplotypecaller/DeBruijnAssembler.java index 5a5946183..48972dfd5 100644 --- a/protected/java/src/org/broadinstitute/sting/gatk/walkers/haplotypecaller/DeBruijnAssembler.java +++ b/protected/java/src/org/broadinstitute/sting/gatk/walkers/haplotypecaller/DeBruijnAssembler.java @@ -46,101 +46,53 @@ package org.broadinstitute.sting.gatk.walkers.haplotypecaller; -import com.google.java.contract.Ensures; import com.google.java.contract.Requires; -import net.sf.samtools.Cigar; -import net.sf.samtools.CigarElement; -import net.sf.samtools.CigarOperator; -import org.apache.commons.lang.ArrayUtils; import org.apache.log4j.Logger; -import org.broadinstitute.sting.gatk.walkers.haplotypecaller.graphs.*; -import org.broadinstitute.sting.utils.GenomeLoc; +import org.broadinstitute.sting.gatk.walkers.haplotypecaller.graphs.DeBruijnGraph; +import org.broadinstitute.sting.gatk.walkers.haplotypecaller.graphs.SeqGraph; import org.broadinstitute.sting.utils.MathUtils; -import org.broadinstitute.sting.utils.smithwaterman.SWPairwiseAlignment; -import org.broadinstitute.sting.utils.activeregion.ActiveRegion; import org.broadinstitute.sting.utils.exceptions.UserException; import org.broadinstitute.sting.utils.haplotype.Haplotype; -import org.broadinstitute.sting.utils.sam.AlignmentUtils; import org.broadinstitute.sting.utils.sam.GATKSAMRecord; import org.broadinstitute.sting.utils.sam.ReadUtils; -import org.broadinstitute.sting.utils.smithwaterman.SWParameterSet; -import org.broadinstitute.variant.variantcontext.Allele; -import org.broadinstitute.variant.variantcontext.VariantContext; import java.io.File; -import java.util.*; +import java.util.Arrays; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; /** - * Created by IntelliJ IDEA. + * DeBruijn assembler for the HaplotypeCaller + * * User: ebanks, rpoplin * Date: Mar 14, 2011 */ - public class DeBruijnAssembler extends LocalAssemblyEngine { private final static Logger logger = Logger.getLogger(DeBruijnAssembler.class); - private static final int KMER_OVERLAP = 5; // the additional size of a valid chunk of sequence, used to string together k-mers - // TODO -- this number is very low, and limits our ability to explore low-frequency variants. It should // TODO -- be increased to a large number of eliminated altogether when moving to the bubble caller where // TODO -- we are no longer considering a combinatorial number of haplotypes as the number of bubbles increases - private static final int NUM_BEST_PATHS_PER_KMER_GRAPH = 25; + private final static int NUM_PATHS_PER_GRAPH = 25; + private static final int KMER_OVERLAP = 5; // the additional size of a valid chunk of sequence, used to string together k-mers private static final int GRAPH_KMER_STEP = 6; - private final boolean debug; - private final boolean debugGraphTransformations; private final int minKmer; - private final boolean allowCyclesInKmerGraphToGeneratePaths; - private final int onlyBuildKmersOfThisSizeWhenDebuggingGraphAlgorithms; - protected DeBruijnAssembler() { - this(false, -1, 11, false); + this(25, -1); } - public DeBruijnAssembler(final boolean debug, - final int debugGraphTransformations, - final int minKmer, - final boolean allowCyclesInKmerGraphToGeneratePaths) { - super(); - this.debug = debug; - this.debugGraphTransformations = debugGraphTransformations > 0; - this.onlyBuildKmersOfThisSizeWhenDebuggingGraphAlgorithms = debugGraphTransformations; + public DeBruijnAssembler(final int minKmer, final int onlyBuildKmersOfThisSizeWhenDebuggingGraphAlgorithms) { + super(NUM_PATHS_PER_GRAPH); this.minKmer = minKmer; - this.allowCyclesInKmerGraphToGeneratePaths = allowCyclesInKmerGraphToGeneratePaths; + this.onlyBuildKmersOfThisSizeWhenDebuggingGraphAlgorithms = onlyBuildKmersOfThisSizeWhenDebuggingGraphAlgorithms; } - /** - * Main entry point into the assembly engine. Build a set of deBruijn graphs out of the provided reference sequence and list of reads - * @param activeRegion ActiveRegion object holding the reads which are to be used during assembly - * @param refHaplotype reference haplotype object - * @param fullReferenceWithPadding byte array holding the reference sequence with padding - * @param refLoc GenomeLoc object corresponding to the reference sequence with padding - * @param activeAllelesToGenotype the alleles to inject into the haplotypes during GGA mode - * @return a non-empty list of all the haplotypes that are produced during assembly - */ - @Ensures({"result.contains(refHaplotype)"}) - public List runLocalAssembly( final ActiveRegion activeRegion, final Haplotype refHaplotype, final byte[] fullReferenceWithPadding, final GenomeLoc refLoc, final List activeAllelesToGenotype ) { - if( activeRegion == null ) { throw new IllegalArgumentException("Assembly engine cannot be used with a null ActiveRegion."); } - if( refHaplotype == null ) { throw new IllegalArgumentException("Reference haplotype cannot be null."); } - if( fullReferenceWithPadding.length != refLoc.size() ) { throw new IllegalArgumentException("Reference bases and reference loc must be the same size."); } - if( pruneFactor < 0 ) { throw new IllegalArgumentException("Pruning factor cannot be negative"); } - - // create the graphs - final List graphs = createDeBruijnGraphs( activeRegion.getReads(), refHaplotype ); - - // print the graphs if the appropriate debug option has been turned on - if( graphWriter != null ) { - printGraphs(graphs); - } - - // find the best paths in the graphs and return them as haplotypes - return findBestPaths( graphs, refHaplotype, fullReferenceWithPadding, refLoc, activeAllelesToGenotype, activeRegion.getExtendedLoc() ); - } - - @Requires({"reads != null", "refHaplotype != null"}) - protected List createDeBruijnGraphs( final List reads, final Haplotype refHaplotype ) { + @Override + protected List assemble(final List reads, final Haplotype refHaplotype) { final List graphs = new LinkedList(); final int maxKmer = ReadUtils.getMaxReadLength(reads) - KMER_OVERLAP - 1; @@ -165,10 +117,9 @@ public class DeBruijnAssembler extends LocalAssemblyEngine { " future subsystem will actually go and error correct the reads"); } - final SeqGraph seqGraph = toSeqGraph(graph); + final SeqGraph seqGraph = cleanupSeqGraph(graph.convertToSequenceGraph()); if ( seqGraph != null ) { // if the graph contains interesting variation from the reference - sanityCheckReferenceGraph(seqGraph, refHaplotype); graphs.add(seqGraph); if ( debugGraphTransformations ) // we only want to use one graph size @@ -181,69 +132,6 @@ public class DeBruijnAssembler extends LocalAssemblyEngine { return graphs; } - private SeqGraph toSeqGraph(final DeBruijnGraph deBruijnGraph) { - final SeqGraph seqGraph = deBruijnGraph.convertToSequenceGraph(); - if ( debugGraphTransformations ) seqGraph.printGraph(new File("sequenceGraph.1.dot"), pruneFactor); - - // TODO -- we need to come up with a consistent pruning algorithm. The current pruning algorithm - // TODO -- works well but it doesn't differentiate between an isolated chain that doesn't connect - // TODO -- to anything from one that's actually has good support along the chain but just happens - // TODO -- to have a connection in the middle that has weight of < pruneFactor. Ultimately - // TODO -- the pruning algorithm really should be an error correction algorithm that knows more - // TODO -- about the structure of the data and can differentiate between an infrequent path but - // TODO -- without evidence against it (such as occurs when a region is hard to get any reads through) - // TODO -- from a error with lots of weight going along another similar path - // the very first thing we need to do is zip up the graph, or pruneGraph will be too aggressive - seqGraph.zipLinearChains(); - if ( debugGraphTransformations ) seqGraph.printGraph(new File("sequenceGraph.2.zipped.dot"), pruneFactor); - - // now go through and prune the graph, removing vertices no longer connected to the reference chain - // IMPORTANT: pruning must occur before we call simplifyGraph, as simplifyGraph adds 0 weight - // edges to maintain graph connectivity. - seqGraph.pruneGraph(pruneFactor); - seqGraph.removeVerticesNotConnectedToRefRegardlessOfEdgeDirection(); - - if ( debugGraphTransformations ) seqGraph.printGraph(new File("sequenceGraph.3.pruned.dot"), pruneFactor); - seqGraph.simplifyGraph(); - if ( debugGraphTransformations ) seqGraph.printGraph(new File("sequenceGraph.4.merged.dot"), pruneFactor); - - // The graph has degenerated in some way, so the reference source and/or sink cannot be id'd. Can - // happen in cases where for example the reference somehow manages to acquire a cycle, or - // where the entire assembly collapses back into the reference sequence. - if ( seqGraph.getReferenceSourceVertex() == null || seqGraph.getReferenceSinkVertex() == null ) - return null; - - seqGraph.removePathsNotConnectedToRef(); - seqGraph.simplifyGraph(); - if ( seqGraph.vertexSet().size() == 1 ) { - // we've perfectly assembled into a single reference haplotype, add a empty seq vertex to stop - // the code from blowing up. - // TODO -- ref properties should really be on the vertices, not the graph itself - final SeqVertex complete = seqGraph.vertexSet().iterator().next(); - final SeqVertex dummy = new SeqVertex(""); - seqGraph.addVertex(dummy); - seqGraph.addEdge(complete, dummy, new BaseEdge(true, 0)); - } - if ( debugGraphTransformations ) seqGraph.printGraph(new File("sequenceGraph.5.final.dot"), pruneFactor); - - return seqGraph; - } - - protected void sanityCheckReferenceGraph(final BaseGraph graph, final Haplotype refHaplotype) { - if( graph.getReferenceSourceVertex() == null ) { - throw new IllegalStateException("All reference graphs must have a reference source vertex."); - } - if( graph.getReferenceSinkVertex() == null ) { - throw new IllegalStateException("All reference graphs must have a reference sink vertex."); - } - if( !Arrays.equals(graph.getReferenceBytes(graph.getReferenceSourceVertex(), graph.getReferenceSinkVertex(), true, true), refHaplotype.getBases()) ) { - throw new IllegalStateException("Mismatch between the reference haplotype and the reference assembly graph path." + - " graph = " + new String(graph.getReferenceBytes(graph.getReferenceSourceVertex(), graph.getReferenceSinkVertex(), true, true)) + - " haplotype = " + new String(refHaplotype.getBases()) - ); - } - } - @Requires({"reads != null", "kmerLength > 0", "refHaplotype != null"}) protected DeBruijnGraph createGraphFromSequences( final List reads, final int kmerLength, final Haplotype refHaplotype ) { final DeBruijnGraph graph = new DeBruijnGraph(kmerLength); @@ -344,290 +232,10 @@ public class DeBruijnAssembler extends LocalAssemblyEngine { return true; } - protected void printGraphs(final List graphs) { - final int writeFirstGraphWithSizeSmallerThan = 50; - - graphWriter.println("digraph assemblyGraphs {"); - for( final SeqGraph graph : graphs ) { - if ( debugGraphTransformations && graph.getKmerSize() >= writeFirstGraphWithSizeSmallerThan ) { - logger.info("Skipping writing of graph with kmersize " + graph.getKmerSize()); - continue; - } - - graph.printGraph(graphWriter, false, pruneFactor); - - if ( debugGraphTransformations ) - break; - } - - graphWriter.println("}"); - } - - @Requires({"refWithPadding.length > refHaplotype.getBases().length", "refLoc.containsP(activeRegionWindow)"}) - @Ensures({"result.contains(refHaplotype)"}) - private List findBestPaths( final List graphs, final Haplotype refHaplotype, final byte[] refWithPadding, final GenomeLoc refLoc, final List activeAllelesToGenotype, final GenomeLoc activeRegionWindow ) { - - // add the reference haplotype separately from all the others to ensure that it is present in the list of haplotypes - // TODO -- this use of an array with contains lower may be a performance problem returning in an O(N^2) algorithm - final List returnHaplotypes = new ArrayList(); - refHaplotype.setAlignmentStartHapwrtRef(activeRegionWindow.getStart() - refLoc.getStart()); - final Cigar c = new Cigar(); - c.add(new CigarElement(refHaplotype.getBases().length, CigarOperator.M)); - refHaplotype.setCigar(c); - returnHaplotypes.add( refHaplotype ); - - final int activeRegionStart = refHaplotype.getAlignmentStartHapwrtRef(); - final int activeRegionStop = refHaplotype.getAlignmentStartHapwrtRef() + refHaplotype.getCigar().getReferenceLength(); - - // for GGA mode, add the desired allele into the haplotype - for( final VariantContext compVC : activeAllelesToGenotype ) { - for( final Allele compAltAllele : compVC.getAlternateAlleles() ) { - final Haplotype insertedRefHaplotype = refHaplotype.insertAllele(compVC.getReference(), compAltAllele, activeRegionStart + compVC.getStart() - activeRegionWindow.getStart(), compVC.getStart()); - addHaplotypeForGGA( insertedRefHaplotype, refWithPadding, returnHaplotypes, activeRegionStart, activeRegionStop, true ); - } - } - - for( final SeqGraph graph : graphs ) { - final SeqVertex source = graph.getReferenceSourceVertex(); - final SeqVertex sink = graph.getReferenceSinkVertex(); - if ( source == null || sink == null ) throw new IllegalArgumentException("Both source and sink cannot be null but got " + source + " and sink " + sink + " for graph "+ graph); - - final KBestPaths pathFinder = new KBestPaths(allowCyclesInKmerGraphToGeneratePaths); - for ( final Path path : pathFinder.getKBestPaths(graph, NUM_BEST_PATHS_PER_KMER_GRAPH, source, sink) ) { -// logger.info("Found path " + path); - Haplotype h = new Haplotype( path.getBases() ); - if( !returnHaplotypes.contains(h) ) { - final Cigar cigar = path.calculateCigar(); - if( cigar.isEmpty() ) { - throw new IllegalStateException("Smith-Waterman alignment failure. Cigar = " + cigar + " with reference length " + cigar.getReferenceLength() + " but expecting reference length of " + refHaplotype.getCigar().getReferenceLength()); - } else if ( pathIsTooDivergentFromReference(cigar) || cigar.getReferenceLength() < 60 ) { // N cigar elements means that a bubble was too divergent from the reference so skip over this path - continue; - } else if( cigar.getReferenceLength() != refHaplotype.getCigar().getReferenceLength() ) { // SW failure - throw new IllegalStateException("Smith-Waterman alignment failure. Cigar = " + cigar + " with reference length " + cigar.getReferenceLength() + " but expecting reference length of " + refHaplotype.getCigar().getReferenceLength()); - } - h.setCigar(cigar); - - // extend partial haplotypes which are anchored in the reference to include the full active region - h = extendPartialHaplotype(h, activeRegionStart, refWithPadding); - final Cigar leftAlignedCigar = leftAlignCigarSequentially(AlignmentUtils.consolidateCigar(h.getCigar()), refWithPadding, h.getBases(), activeRegionStart, 0); - if( !returnHaplotypes.contains(h) ) { - h.setAlignmentStartHapwrtRef(activeRegionStart); - h.setCigar(leftAlignedCigar); - h.setScore(path.getScore()); - returnHaplotypes.add(h); - - if ( debug ) - logger.info("Adding haplotype " + h.getCigar() + " from debruijn graph with kmer " + graph.getKmerSize()); - - // for GGA mode, add the desired allele into the haplotype if it isn't already present - if( !activeAllelesToGenotype.isEmpty() ) { - final Map eventMap = GenotypingEngine.generateVCsFromAlignment( h, refWithPadding, refLoc, "HCassembly" ); // BUGBUG: need to put this function in a shared place - for( final VariantContext compVC : activeAllelesToGenotype ) { // for GGA mode, add the desired allele into the haplotype if it isn't already present - final VariantContext vcOnHaplotype = eventMap.get(compVC.getStart()); - - // This if statement used to additionally have: - // "|| !vcOnHaplotype.hasSameAllelesAs(compVC)" - // but that can lead to problems downstream when e.g. you are injecting a 1bp deletion onto - // a haplotype that already contains a 1bp insertion (so practically it is reference but - // falls into the bin for the 1bp deletion because we keep track of the artificial alleles). - if( vcOnHaplotype == null ) { - for( final Allele compAltAllele : compVC.getAlternateAlleles() ) { - addHaplotypeForGGA( h.insertAllele(compVC.getReference(), compAltAllele, activeRegionStart + compVC.getStart() - activeRegionWindow.getStart(), compVC.getStart()), refWithPadding, returnHaplotypes, activeRegionStart, activeRegionStop, false ); - } - } - } - } - } - } - } - } - - // add genome locs to the haplotypes - for ( final Haplotype h : returnHaplotypes ) h.setGenomeLocation(activeRegionWindow); - - if ( returnHaplotypes.size() < returnHaplotypes.size() ) - logger.info("Found " + returnHaplotypes.size() + " candidate haplotypes of " + returnHaplotypes.size() + " possible combinations to evaluate every read against at " + refLoc); - - if( debug ) { - if( returnHaplotypes.size() > 1 ) { - logger.info("Found " + returnHaplotypes.size() + " candidate haplotypes of " + returnHaplotypes.size() + " possible combinations to evaluate every read against."); - } else { - logger.info("Found only the reference haplotype in the assembly graph."); - } - for( final Haplotype h : returnHaplotypes ) { - logger.info( h.toString() ); - logger.info( "> Cigar = " + h.getCigar() + " : " + h.getCigar().getReferenceLength() + " score " + h.getScore() ); - } - } - - return returnHaplotypes; - } - - /** - * Extend partial haplotypes which are anchored in the reference to include the full active region - * @param haplotype the haplotype to extend - * @param activeRegionStart the place where the active region starts in the ref byte array - * @param refWithPadding the full reference byte array with padding which encompasses the active region - * @return a haplotype fully extended to encompass the active region - */ - @Requires({"haplotype != null", "activeRegionStart >= 0", "refWithPadding != null", "refWithPadding.length > 0"}) - @Ensures({"result != null", "result.getCigar() != null"}) - private Haplotype extendPartialHaplotype( final Haplotype haplotype, final int activeRegionStart, final byte[] refWithPadding ) { - final Cigar cigar = haplotype.getCigar(); - final Cigar newCigar = new Cigar(); - byte[] newHaplotypeBases = haplotype.getBases(); - int refPos = activeRegionStart; - int hapPos = 0; - for( int iii = 0; iii < cigar.getCigarElements().size(); iii++ ) { - final CigarElement ce = cigar.getCigarElement(iii); - switch (ce.getOperator()) { - case M: - refPos += ce.getLength(); - hapPos += ce.getLength(); - newCigar.add(ce); - break; - case I: - hapPos += ce.getLength(); - newCigar.add(ce); - break; - case D: - if( iii == 0 || iii == cigar.getCigarElements().size() - 1 ) { - newHaplotypeBases = ArrayUtils.addAll( Arrays.copyOfRange(newHaplotypeBases, 0, hapPos), - ArrayUtils.addAll(Arrays.copyOfRange(refWithPadding, refPos, refPos + ce.getLength()), - Arrays.copyOfRange(newHaplotypeBases, hapPos, newHaplotypeBases.length))); - hapPos += ce.getLength(); - refPos += ce.getLength(); - newCigar.add(new CigarElement(ce.getLength(), CigarOperator.M)); - } else { - refPos += ce.getLength(); - newCigar.add(ce); - } - break; - default: - throw new IllegalStateException("Unsupported cigar operator detected: " + ce.getOperator()); - } - } - final Haplotype returnHaplotype = new Haplotype(newHaplotypeBases, haplotype.isReference()); - returnHaplotype.setCigar( newCigar ); - return returnHaplotype; - } - - /** - * We use CigarOperator.N as the signal that an incomplete or too divergent bubble was found during bubble traversal - * @param c the cigar to test - * @return true if we should skip over this path - */ - @Requires("c != null") - private boolean pathIsTooDivergentFromReference( final Cigar c ) { - for( final CigarElement ce : c.getCigarElements() ) { - if( ce.getOperator().equals(CigarOperator.N) ) { - return true; - } - } - return false; - } - - /** - * Left align the given cigar sequentially. This is needed because AlignmentUtils doesn't accept cigars with more than one indel in them. - * This is a target of future work to incorporate and generalize into AlignmentUtils for use by others. - * @param cigar the cigar to left align - * @param refSeq the reference byte array - * @param readSeq the read byte array - * @param refIndex 0-based alignment start position on ref - * @param readIndex 0-based alignment start position on read - * @return the left-aligned cigar - */ - @Ensures({"cigar != null", "refSeq != null", "readSeq != null", "refIndex >= 0", "readIndex >= 0"}) - protected Cigar leftAlignCigarSequentially(final Cigar cigar, final byte[] refSeq, final byte[] readSeq, int refIndex, int readIndex) { - final Cigar cigarToReturn = new Cigar(); - Cigar cigarToAlign = new Cigar(); - for (int i = 0; i < cigar.numCigarElements(); i++) { - final CigarElement ce = cigar.getCigarElement(i); - if (ce.getOperator() == CigarOperator.D || ce.getOperator() == CigarOperator.I) { - cigarToAlign.add(ce); - final Cigar leftAligned = AlignmentUtils.leftAlignSingleIndel(cigarToAlign, refSeq, readSeq, refIndex, readIndex, false); - for ( final CigarElement toAdd : leftAligned.getCigarElements() ) { cigarToReturn.add(toAdd); } - refIndex += cigarToAlign.getReferenceLength(); - readIndex += cigarToAlign.getReadLength(); - cigarToAlign = new Cigar(); - } else { - cigarToAlign.add(ce); - } - } - if( !cigarToAlign.isEmpty() ) { - for( final CigarElement toAdd : cigarToAlign.getCigarElements() ) { - cigarToReturn.add(toAdd); - } - } - - final Cigar result = AlignmentUtils.consolidateCigar(cigarToReturn); - if( result.getReferenceLength() != cigar.getReferenceLength() ) - throw new IllegalStateException("leftAlignCigarSequentially failed to produce a valid CIGAR. Reference lengths differ. Initial cigar " + cigar + " left aligned into " + result); - return result; - } - - /** - * Take a haplotype which was generated by injecting an allele into a string of bases and run SW against the reference to determine the variants on the haplotype. - * Unfortunately since this haplotype didn't come from the assembly graph you can't straightforwardly use the bubble traversal algorithm to get this information. - * This is a target for future work as we rewrite the HaplotypeCaller to be more bubble-caller based. - * @param haplotype the candidate haplotype - * @param ref the reference bases to align against - * @param haplotypeList the current list of haplotypes - * @param activeRegionStart the start of the active region in the reference byte array - * @param activeRegionStop the stop of the active region in the reference byte array - * @param FORCE_INCLUSION_FOR_GGA_MODE if true will include in the list even if it already exists - * @return true if the candidate haplotype was successfully incorporated into the haplotype list - */ - @Requires({"ref != null", "ref.length >= activeRegionStop - activeRegionStart"}) - private boolean addHaplotypeForGGA( final Haplotype haplotype, final byte[] ref, final List haplotypeList, final int activeRegionStart, final int activeRegionStop, final boolean FORCE_INCLUSION_FOR_GGA_MODE ) { - if( haplotype == null ) { return false; } - - final SWPairwiseAlignment swConsensus = new SWPairwiseAlignment( ref, haplotype.getBases(), SWParameterSet.STANDARD_NGS ); - haplotype.setAlignmentStartHapwrtRef( swConsensus.getAlignmentStart2wrt1() ); - - if( swConsensus.getCigar().toString().contains("S") || swConsensus.getCigar().getReferenceLength() < 60 || swConsensus.getAlignmentStart2wrt1() < 0 ) { // protect against unhelpful haplotype alignments - return false; - } - - haplotype.setCigar( AlignmentUtils.leftAlignIndel(swConsensus.getCigar(), ref, haplotype.getBases(), swConsensus.getAlignmentStart2wrt1(), 0, true) ); - - final int hapStart = ReadUtils.getReadCoordinateForReferenceCoordinate(haplotype.getAlignmentStartHapwrtRef(), haplotype.getCigar(), activeRegionStart, ReadUtils.ClippingTail.LEFT_TAIL, true); - int hapStop = ReadUtils.getReadCoordinateForReferenceCoordinate( haplotype.getAlignmentStartHapwrtRef(), haplotype.getCigar(), activeRegionStop, ReadUtils.ClippingTail.RIGHT_TAIL, true ); - if( hapStop == ReadUtils.CLIPPING_GOAL_NOT_REACHED && activeRegionStop == haplotype.getAlignmentStartHapwrtRef() + haplotype.getCigar().getReferenceLength() ) { - hapStop = activeRegionStop; // contract for getReadCoordinateForReferenceCoordinate function says that if read ends at boundary then it is outside of the clipping goal - } - byte[] newHaplotypeBases; - // extend partial haplotypes to contain the full active region sequence - if( hapStart == ReadUtils.CLIPPING_GOAL_NOT_REACHED && hapStop == ReadUtils.CLIPPING_GOAL_NOT_REACHED ) { - newHaplotypeBases = ArrayUtils.addAll( ArrayUtils.addAll( ArrayUtils.subarray(ref, activeRegionStart, swConsensus.getAlignmentStart2wrt1()), - haplotype.getBases()), - ArrayUtils.subarray(ref, swConsensus.getAlignmentStart2wrt1() + swConsensus.getCigar().getReferenceLength(), activeRegionStop) ); - } else if( hapStart == ReadUtils.CLIPPING_GOAL_NOT_REACHED ) { - newHaplotypeBases = ArrayUtils.addAll( ArrayUtils.subarray(ref, activeRegionStart, swConsensus.getAlignmentStart2wrt1()), ArrayUtils.subarray(haplotype.getBases(), 0, hapStop) ); - } else if( hapStop == ReadUtils.CLIPPING_GOAL_NOT_REACHED ) { - newHaplotypeBases = ArrayUtils.addAll( ArrayUtils.subarray(haplotype.getBases(), hapStart, haplotype.getBases().length), ArrayUtils.subarray(ref, swConsensus.getAlignmentStart2wrt1() + swConsensus.getCigar().getReferenceLength(), activeRegionStop) ); - } else { - newHaplotypeBases = ArrayUtils.subarray(haplotype.getBases(), hapStart, hapStop); - } - - final Haplotype h = new Haplotype( newHaplotypeBases ); - final SWPairwiseAlignment swConsensus2 = new SWPairwiseAlignment( ref, h.getBases(), SWParameterSet.STANDARD_NGS ); - - h.setAlignmentStartHapwrtRef( swConsensus2.getAlignmentStart2wrt1() ); - if ( haplotype.isArtificialHaplotype() ) { - h.setArtificialEvent(haplotype.getArtificialEvent()); - } - if( swConsensus2.getCigar().toString().contains("S") || swConsensus2.getCigar().getReferenceLength() != activeRegionStop - activeRegionStart || swConsensus2.getAlignmentStart2wrt1() < 0 ) { // protect against unhelpful haplotype alignments - return false; - } - - h.setCigar( AlignmentUtils.leftAlignIndel(swConsensus2.getCigar(), ref, h.getBases(), swConsensus2.getAlignmentStart2wrt1(), 0, true) ); - - if( FORCE_INCLUSION_FOR_GGA_MODE || !haplotypeList.contains(h) ) { - haplotypeList.add(h); - return true; - } else { - return false; - } + @Override + public String toString() { + return "DeBruijnAssembler{" + + "minKmer=" + minKmer + + '}'; } } \ No newline at end of file diff --git a/protected/java/src/org/broadinstitute/sting/gatk/walkers/haplotypecaller/HaplotypeCaller.java b/protected/java/src/org/broadinstitute/sting/gatk/walkers/haplotypecaller/HaplotypeCaller.java index 6ea543f25..33d1104bc 100644 --- a/protected/java/src/org/broadinstitute/sting/gatk/walkers/haplotypecaller/HaplotypeCaller.java +++ b/protected/java/src/org/broadinstitute/sting/gatk/walkers/haplotypecaller/HaplotypeCaller.java @@ -68,6 +68,7 @@ import org.broadinstitute.sting.gatk.walkers.genotyper.UnifiedArgumentCollection import org.broadinstitute.sting.gatk.walkers.genotyper.UnifiedGenotyperEngine; import org.broadinstitute.sting.gatk.walkers.genotyper.VariantCallContext; import org.broadinstitute.sting.gatk.walkers.genotyper.afcalc.AFCalcFactory; +import org.broadinstitute.sting.gatk.walkers.haplotypecaller.readthreading.ReadThreadingAssembler; import org.broadinstitute.sting.utils.*; import org.broadinstitute.sting.utils.activeregion.ActiveRegion; import org.broadinstitute.sting.utils.activeregion.ActiveRegionReadState; @@ -135,10 +136,14 @@ import java.util.*; @DocumentedGATKFeature( groupName = HelpConstants.DOCS_CAT_VARDISC, extraDocs = {CommandLineGATK.class} ) @PartitionBy(PartitionType.LOCUS) @BAQMode(ApplicationTime = ReadTransformer.ApplicationTime.FORBIDDEN) -@ActiveRegionTraversalParameters(extension=200, maxRegion=300) +@ActiveRegionTraversalParameters(extension=100, maxRegion=300) @ReadFilters({HCMappingQualityFilter.class}) @Downsample(by= DownsampleType.BY_SAMPLE, toCoverage=250) public class HaplotypeCaller extends ActiveRegionWalker implements AnnotatorCompatible { + // ----------------------------------------------------------------------------------------------- + // general haplotype caller arguments + // ----------------------------------------------------------------------------------------------- + /** * A raw, unfiltered, highly sensitive callset in VCF format. */ @@ -185,64 +190,6 @@ public class HaplotypeCaller extends ActiveRegionWalker implem @Argument(fullName="bamWriterType", shortName="bamWriterType", doc="How should haplotypes be written to the BAM?", required = false) public HaplotypeBAMWriter.Type bamWriterType = HaplotypeBAMWriter.Type.CALLED_HAPLOTYPES; - /** - * The PairHMM implementation to use for genotype likelihood calculations. The various implementations balance a tradeoff of accuracy and runtime. - */ - @Advanced - @Argument(fullName = "pair_hmm_implementation", shortName = "pairHMM", doc = "The PairHMM implementation to use for genotype likelihood calculations", required = false) - public PairHMM.HMM_IMPLEMENTATION pairHMM = PairHMM.HMM_IMPLEMENTATION.LOGLESS_CACHING; - - @Hidden - @Argument(fullName="keepRG", shortName="keepRG", doc="Only use read from this read group when making calls (but use all reads to build the assembly)", required = false) - protected String keepRG = null; - - @Advanced - @Argument(fullName="minPruning", shortName="minPruning", doc = "The minimum allowed pruning factor in assembly graph. Paths with <= X supporting kmers are pruned from the graph", required = false) - protected int MIN_PRUNE_FACTOR = 0; - - @Advanced - @Argument(fullName="gcpHMM", shortName="gcpHMM", doc="Flat gap continuation penalty for use in the Pair HMM", required = false) - protected int gcpHMM = 10; - - @Advanced - @Argument(fullName="maxNumHaplotypesInPopulation", shortName="maxNumHaplotypesInPopulation", doc="Maximum number of haplotypes to consider for your population. This number will probably need to be increased when calling organisms with high heterozygosity.", required = false) - protected int maxNumHaplotypesInPopulation = 25; - - @Advanced - @Argument(fullName="minKmer", shortName="minKmer", doc="Minimum kmer length to use in the assembly graph", required = false) - protected int minKmer = 11; - - /** - * If this flag is provided, the haplotype caller will include unmapped reads in the assembly and calling - * when these reads occur in the region being analyzed. Typically, for paired end analyses, one pair of the - * read can map, but if its pair is too divergent then it may be unmapped and placed next to its mate, taking - * the mates contig and alignment start. If this flag is provided the haplotype caller will see such reads, - * and may make use of them in assembly and calling, where possible. - */ - @Hidden - @Argument(fullName="includeUmappedReads", shortName="unmapped", doc="If provided, unmapped reads with chromosomal coordinates (i.e., those placed to their maps) will be included in the assembly and calling", required = false) - protected boolean includeUnmappedReads = false; - - @Advanced - @Argument(fullName="useAllelesTrigger", shortName="allelesTrigger", doc = "If specified, use additional trigger on variants found in an external alleles file", required=false) - protected boolean USE_ALLELES_TRIGGER = false; - - @Advanced - @Argument(fullName="useFilteredReadsForAnnotations", shortName="useFilteredReadsForAnnotations", doc = "If specified, use the contamination-filtered read maps for the purposes of annotating variants", required=false) - protected boolean USE_FILTERED_READ_MAP_FOR_ANNOTATIONS = false; - - @Hidden - @Argument(fullName="justDetermineActiveRegions", shortName="justDetermineActiveRegions", doc = "If specified, the HC won't actually do any assembly or calling, it'll just run the upfront active region determination code. Useful for benchmarking and scalability testing", required=false) - protected boolean justDetermineActiveRegions = false; - - @Hidden - @Argument(fullName="dontGenotype", shortName="dontGenotype", doc = "If specified, the HC will do any assembly but won't do calling. Useful for benchmarking and scalability testing", required=false) - protected boolean dontGenotype = false; - - @Hidden - @Argument(fullName="errorCorrectKmers", shortName="errorCorrectKmers", doc = "Use an exploratory algorithm to error correct the kmers used during assembly. May cause fundamental problems with the assembly graph itself", required=false) - protected boolean errorCorrectKmers = false; - /** * rsIDs from this file are used to populate the ID column of the output. Also, the DB INFO flag will be set when appropriate. * dbSNP is not used in any way for the calculations themselves. @@ -282,10 +229,6 @@ public class HaplotypeCaller extends ActiveRegionWalker implem @Argument(fullName="excludeAnnotation", shortName="XA", doc="One or more specific annotations to exclude", required=false) protected List annotationsToExclude = new ArrayList(Arrays.asList(new String[]{"SpanningDeletions", "TandemRepeatAnnotator"})); - @Advanced - @Argument(fullName="mergeVariantsViaLD", shortName="mergeVariantsViaLD", doc="If specified, we will merge variants together into block substitutions that are in strong local LD", required = false) - protected boolean mergeVariantsViaLD = false; - /** * Which groups of annotations to add to the output VCF file. See the VariantAnnotator -list argument to view available groups. */ @@ -295,13 +238,139 @@ public class HaplotypeCaller extends ActiveRegionWalker implem @ArgumentCollection private StandardCallerArgumentCollection SCAC = new StandardCallerArgumentCollection(); + // ----------------------------------------------------------------------------------------------- + // arguments to control internal behavior of the debruijn assembler + // ----------------------------------------------------------------------------------------------- + + @Advanced + @Argument(fullName="useDebruijnAssembler", shortName="useDebruijnAssembler", doc="If specified, we will use the old DeBruijn assembler. Depreciated as of 2.6", required = false) + protected boolean useDebruijnAssembler = false; + + @Advanced + @Argument(fullName="minKmerForDebruijnAssembler", shortName="minKmerForDebruijnAssembler", doc="Minimum kmer length to use in the debruijn assembly graph", required = false) + protected int minKmerForDebruijnAssembler = 11; + + @Advanced + @Argument(fullName="onlyUseKmerSizeForDebruijnAssembler", shortName="onlyUseKmerSizeForDebruijnAssembler", doc="If specified, we will only build kmer graphs with this kmer size in the debruijn", required = false) + protected int onlyUseKmerSizeForDebruijnAssembler = -1; + + // ----------------------------------------------------------------------------------------------- + // arguments to control internal behavior of the read threading assembler + // ----------------------------------------------------------------------------------------------- + + @Advanced + @Argument(fullName="kmerSize", shortName="kmerSize", doc="Kmer size to use in the read threading assembler", required = false) + protected List kmerSizes = Arrays.asList(10, 25); + + /** + * Assembly graph can be quite complex, and could imply a very large number of possible haplotypes. Each haplotype + * considered requires N PairHMM evaluations if there are N reads across all samples. In order to control the + * run of the haplotype caller we only take maxPathsPerSample * nSample paths from the graph, in order of their + * weights, no matter how many paths are possible to generate from the graph. Putting this number too low + * will result in dropping true variation because paths that include the real variant are not even considered. + */ + @Advanced + @Argument(fullName="maxPathsPerSample", shortName="maxPathsPerSample", doc="Max number of paths to consider for the read threading assembler per sample.", required = false) + protected int maxPathsPerSample = 10; + + /** + * The minimum number of paths to advance forward for genotyping, regardless of the + * number of samples + */ + private final static int MIN_PATHS_PER_GRAPH = 128; + + @Hidden + @Argument(fullName="dontRecoverDanglingTails", shortName="dontRecoverDanglingTails", doc="Should we disable dangling tail recovery in the read threading assembler?", required = false) + protected boolean dontRecoverDanglingTails = false; + + // ----------------------------------------------------------------------------------------------- + // general advanced arguments to control haplotype caller behavior + // ----------------------------------------------------------------------------------------------- + + @Advanced + @Argument(fullName="minPruning", shortName="minPruning", doc = "The minimum allowed pruning factor in assembly graph. Paths with <= X supporting kmers are pruned from the graph", required = false) + protected int MIN_PRUNE_FACTOR = 2; + + @Advanced + @Argument(fullName="gcpHMM", shortName="gcpHMM", doc="Flat gap continuation penalty for use in the Pair HMM", required = false) + protected int gcpHMM = 10; + + /** + * If this flag is provided, the haplotype caller will include unmapped reads in the assembly and calling + * when these reads occur in the region being analyzed. Typically, for paired end analyses, one pair of the + * read can map, but if its pair is too divergent then it may be unmapped and placed next to its mate, taking + * the mates contig and alignment start. If this flag is provided the haplotype caller will see such reads, + * and may make use of them in assembly and calling, where possible. + */ + @Hidden + @Argument(fullName="includeUmappedReads", shortName="unmapped", doc="If provided, unmapped reads with chromosomal coordinates (i.e., those placed to their maps) will be included in the assembly and calling", required = false) + protected boolean includeUnmappedReads = false; + + @Advanced + @Argument(fullName="useAllelesTrigger", shortName="allelesTrigger", doc = "If specified, use additional trigger on variants found in an external alleles file", required=false) + protected boolean USE_ALLELES_TRIGGER = false; + + @Advanced + @Argument(fullName="useFilteredReadsForAnnotations", shortName="useFilteredReadsForAnnotations", doc = "If specified, use the contamination-filtered read maps for the purposes of annotating variants", required=false) + protected boolean USE_FILTERED_READ_MAP_FOR_ANNOTATIONS = false; + + /** + * The phredScaledGlobalReadMismappingRate reflects the average global mismapping rate of all reads, regardless of their + * mapping quality. This term effects the probability that a read originated from the reference haplotype, regardless of + * its edit distance from the reference, in that the read could have originated from the reference haplotype but + * from another location in the genome. Suppose a read has many mismatches from the reference, say like 5, but + * has a very high mapping quality of 60. Without this parameter, the read would contribute 5 * Q30 evidence + * in favor of its 5 mismatch haplotype compared to reference, potentially enough to make a call off that single + * read for all of these events. With this parameter set to Q30, though, the maximum evidence against the reference + * that this (and any) read could contribute against reference is Q30. + * + * Set this term to any negative number to turn off the global mapping rate + */ + @Advanced + @Argument(fullName="phredScaledGlobalReadMismappingRate", shortName="globalMAPQ", doc="The global assumed mismapping rate for reads", required = false) + protected int phredScaledGlobalReadMismappingRate = 60; + + @Advanced + @Argument(fullName="maxNumHaplotypesInPopulation", shortName="maxNumHaplotypesInPopulation", doc="Maximum number of haplotypes to consider for your population. This number will probably need to be increased when calling organisms with high heterozygosity.", required = false) + protected int maxNumHaplotypesInPopulation = 25; + + @Advanced + @Argument(fullName="mergeVariantsViaLD", shortName="mergeVariantsViaLD", doc="If specified, we will merge variants together into block substitutions that are in strong local LD", required = false) + protected boolean mergeVariantsViaLD = false; + + // ----------------------------------------------------------------------------------------------- + // arguments for debugging / developing the haplotype caller + // ----------------------------------------------------------------------------------------------- + /** + * The PairHMM implementation to use for genotype likelihood calculations. The various implementations balance a tradeoff of accuracy and runtime. + */ + @Hidden + @Argument(fullName = "pair_hmm_implementation", shortName = "pairHMM", doc = "The PairHMM implementation to use for genotype likelihood calculations", required = false) + public PairHMM.HMM_IMPLEMENTATION pairHMM = PairHMM.HMM_IMPLEMENTATION.LOGLESS_CACHING; + + @Hidden + @Argument(fullName="keepRG", shortName="keepRG", doc="Only use read from this read group when making calls (but use all reads to build the assembly)", required = false) + protected String keepRG = null; + + @Hidden + @Argument(fullName="justDetermineActiveRegions", shortName="justDetermineActiveRegions", doc = "If specified, the HC won't actually do any assembly or calling, it'll just run the upfront active region determination code. Useful for benchmarking and scalability testing", required=false) + protected boolean justDetermineActiveRegions = false; + + @Hidden + @Argument(fullName="dontGenotype", shortName="dontGenotype", doc = "If specified, the HC will do any assembly but won't do calling. Useful for benchmarking and scalability testing", required=false) + protected boolean dontGenotype = false; + + @Hidden + @Argument(fullName="errorCorrectKmers", shortName="errorCorrectKmers", doc = "Use an exploratory algorithm to error correct the kmers used during assembly. May cause fundamental problems with the assembly graph itself", required=false) + protected boolean errorCorrectKmers = false; + @Advanced @Argument(fullName="debug", shortName="debug", doc="If specified, print out very verbose debug information about each triggering active region", required = false) protected boolean DEBUG; - @Advanced + @Hidden @Argument(fullName="debugGraphTransformations", shortName="debugGraphTransformations", doc="If specified, we will write DOT formatted graph files out of the assembler for only this graph size", required = false) - protected int debugGraphTransformations = -1; + protected boolean debugGraphTransformations = false; @Hidden // TODO -- not currently useful @Argument(fullName="useLowQualityBasesForAssembly", shortName="useLowQualityBasesForAssembly", doc="If specified, we will include low quality bases when doing the assembly", required = false) @@ -311,10 +380,17 @@ public class HaplotypeCaller extends ActiveRegionWalker implem @Argument(fullName="dontTrimActiveRegions", shortName="dontTrimActiveRegions", doc="If specified, we will not trim down the active region from the full region (active + extension) to just the active interval for genotyping", required = false) protected boolean dontTrimActiveRegions = false; + @Hidden + @Argument(fullName="dontUseSoftClippedBases", shortName="dontUseSoftClippedBases", doc="If specified, we will not analyze soft clipped bases in the reads", required = false) + protected boolean dontUseSoftClippedBases = false; + @Hidden @Argument(fullName="allowCyclesInKmerGraphToGeneratePaths", shortName="allowCyclesInKmerGraphToGeneratePaths", doc="If specified, we will allow cycles in the kmer graphs to generate paths with multiple copies of the path sequenece rather than just the shortest paths", required = false) protected boolean allowCyclesInKmerGraphToGeneratePaths = false; + // ----------------------------------------------------------------------------------------------- + // done with Haplotype caller parameters + // ----------------------------------------------------------------------------------------------- // the UG engines private UnifiedGenotyperEngine UG_engine = null; @@ -344,12 +420,17 @@ public class HaplotypeCaller extends ActiveRegionWalker implem // the maximum extent into the full active region extension that we're willing to go in genotyping our events private final static int MAX_GENOTYPING_ACTIVE_REGION_EXTENSION = 25; + private ActiveRegionTrimmer trimmer = null; + private final static int maxReadsInRegionPerSample = 1000; // TODO -- should be an argument private final static int minReadsPerAlignmentStart = 5; // TODO -- should be an argument // bases with quality less than or equal to this value are trimmed off the tails of the reads private static final byte MIN_TAIL_QUALITY = 20; + // the minimum length of a read we'd consider using for genotyping + private final static int MIN_READ_LENGTH = 10; + private List samplesList = new ArrayList(); private final static double LOG_ONE_HALF = -Math.log10(2.0); private final static double LOG_ONE_THIRD = -Math.log10(3.0); @@ -373,6 +454,7 @@ public class HaplotypeCaller extends ActiveRegionWalker implem // get all of the unique sample names Set samples = SampleUtils.getSAMFileSamples(getToolkit().getSAMFileHeader()); samplesList.addAll( samples ); + final int nSamples = samples.size(); // initialize the UnifiedGenotyper Engine which is used to call into the exact model final UnifiedArgumentCollection UAC = new UnifiedArgumentCollection( SCAC ); // this adapter is used so that the full set of unused UG arguments aren't exposed to the HC user UG_engine = new UnifiedGenotyperEngine(getToolkit(), UAC, logger, null, null, samples, GATKVariantContextUtils.DEFAULT_PLOIDY); @@ -428,14 +510,36 @@ public class HaplotypeCaller extends ActiveRegionWalker implem throw new UserException.CouldNotReadInputFile(getToolkit().getArguments().referenceFile, e); } - // setup the assembler - assemblyEngine = new DeBruijnAssembler(DEBUG, debugGraphTransformations, minKmer, allowCyclesInKmerGraphToGeneratePaths); + // create and setup the assembler + final int maxAllowedPathsForReadThreadingAssembler = Math.max(maxPathsPerSample * nSamples, MIN_PATHS_PER_GRAPH); + assemblyEngine = useDebruijnAssembler + ? new DeBruijnAssembler(minKmerForDebruijnAssembler, onlyUseKmerSizeForDebruijnAssembler) + : new ReadThreadingAssembler(maxAllowedPathsForReadThreadingAssembler, kmerSizes); + assemblyEngine.setErrorCorrectKmers(errorCorrectKmers); assemblyEngine.setPruneFactor(MIN_PRUNE_FACTOR); + assemblyEngine.setDebug(DEBUG); + assemblyEngine.setDebugGraphTransformations(debugGraphTransformations); + assemblyEngine.setAllowCyclesInKmerGraphToGeneratePaths(allowCyclesInKmerGraphToGeneratePaths); + assemblyEngine.setRecoverDanglingTails(!dontRecoverDanglingTails); + if ( graphWriter != null ) assemblyEngine.setGraphWriter(graphWriter); if ( useLowQualityBasesForAssembly ) assemblyEngine.setMinBaseQualityToUseInAssembly((byte)1); - likelihoodCalculationEngine = new LikelihoodCalculationEngine( (byte)gcpHMM, DEBUG, pairHMM ); + // setup the likelihood calculation engine + if ( phredScaledGlobalReadMismappingRate < 0 ) phredScaledGlobalReadMismappingRate = -1; + + // configure the global mismapping rate + final double log10GlobalReadMismappingRate; + if ( phredScaledGlobalReadMismappingRate < 0 ) { + log10GlobalReadMismappingRate = - Double.MAX_VALUE; + } else { + log10GlobalReadMismappingRate = QualityUtils.qualToErrorProbLog10(phredScaledGlobalReadMismappingRate); + logger.info("Using global mismapping rate of " + phredScaledGlobalReadMismappingRate + " => " + log10GlobalReadMismappingRate + " in log10 likelihood units"); + } + + // create our likelihood calculation engine + likelihoodCalculationEngine = new LikelihoodCalculationEngine( (byte)gcpHMM, DEBUG, pairHMM, log10GlobalReadMismappingRate ); final MergeVariantsAcrossHaplotypes variantMerger = mergeVariantsViaLD ? new LDMerger(DEBUG, 10, 1) : new MergeVariantsAcrossHaplotypes(); @@ -443,6 +547,9 @@ public class HaplotypeCaller extends ActiveRegionWalker implem if ( bamWriter != null ) haplotypeBAMWriter = HaplotypeBAMWriter.create(bamWriterType, bamWriter, getToolkit().getSAMFileHeader()); + + trimmer = new ActiveRegionTrimmer(DEBUG, PADDING_AROUND_SNPS_FOR_CALLING, PADDING_AROUND_OTHERS_FOR_CALLING, + MAX_GENOTYPING_ACTIVE_REGION_EXTENSION, getToolkit().getGenomeLocParser()); } //--------------------------------------------------------------------------------------------------------------- @@ -564,7 +671,7 @@ public class HaplotypeCaller extends ActiveRegionWalker implem final AssemblyResult assemblyResult = assembleReads(originalActiveRegion, activeAllelesToGenotype); // abort early if something is out of the acceptable range - if( assemblyResult.haplotypes.size() == 1 ) { return 1; } // only the reference haplotype remains so nothing else to do! + if( ! assemblyResult.isVariationPresent() ) { return 1; } // only the reference haplotype remains so nothing else to do! if (dontGenotype) return 1; // user requested we not proceed // filter out reads from genotyping which fail mapping quality based criteria @@ -613,12 +720,18 @@ public class HaplotypeCaller extends ActiveRegionWalker implem final ActiveRegion regionForGenotyping; final byte[] fullReferenceWithPadding; final GenomeLoc paddedReferenceLoc; + final boolean variationPresent; - private AssemblyResult(List haplotypes, ActiveRegion regionForGenotyping, byte[] fullReferenceWithPadding, GenomeLoc paddedReferenceLoc) { + private AssemblyResult(List haplotypes, ActiveRegion regionForGenotyping, byte[] fullReferenceWithPadding, GenomeLoc paddedReferenceLoc, boolean variationPresent) { this.haplotypes = haplotypes; this.regionForGenotyping = regionForGenotyping; this.fullReferenceWithPadding = fullReferenceWithPadding; this.paddedReferenceLoc = paddedReferenceLoc; + this.variationPresent = variationPresent; + } + + public boolean isVariationPresent() { + return variationPresent && haplotypes.size() > 1; } } @@ -644,63 +757,11 @@ public class HaplotypeCaller extends ActiveRegionWalker implem if ( ! dontTrimActiveRegions ) { return trimActiveRegion(activeRegion, haplotypes, fullReferenceWithPadding, paddedReferenceLoc); } else { - // we don't want to or cannot create a trimmed active region, so go ahead and use the old one - return new AssemblyResult(haplotypes, activeRegion, fullReferenceWithPadding, paddedReferenceLoc); + // we don't want to trim active regions, so go ahead and use the old one + return new AssemblyResult(haplotypes, activeRegion, fullReferenceWithPadding, paddedReferenceLoc, true); } } - /** - * Trim down the active region to just enough to properly genotype the events among the haplotypes - * - * This function merely creates the region, but it doesn't populate the reads back into the region - * - * @param region our full active region - * @param haplotypes the list of haplotypes we've created from assembly - * @param ref the reference bases over the full padded location - * @param refLoc the span of the reference bases - * @return a new ActiveRegion trimmed down to just what's needed for genotyping, or null if we couldn't do this successfully - */ - private ActiveRegion createTrimmedRegion(final ActiveRegion region, final List haplotypes, final byte[] ref, final GenomeLoc refLoc) { - EventMap.buildEventMapsForHaplotypes(haplotypes, ref, refLoc, DEBUG); - final TreeSet allContexts = EventMap.getAllVariantContexts(haplotypes); - final GenomeLocParser parser = getToolkit().getGenomeLocParser(); - - if ( allContexts.isEmpty() ) // no variants, so just return the current region - return null; - - final List withinActiveRegion = new LinkedList(); - int pad = PADDING_AROUND_SNPS_FOR_CALLING; - GenomeLoc trimLoc = null; - for ( final VariantContext vc : allContexts ) { - final GenomeLoc vcLoc = parser.createGenomeLoc(vc); - if ( region.getLocation().overlapsP(vcLoc) ) { - if ( ! vc.isSNP() ) // if anything isn't a SNP use the bigger padding - pad = PADDING_AROUND_OTHERS_FOR_CALLING; - trimLoc = trimLoc == null ? vcLoc : trimLoc.endpointSpan(vcLoc); - withinActiveRegion.add(vc); - } - } - - // we don't actually have anything in the region after removing variants that don't overlap the region's full location - if ( trimLoc == null ) return null; - - final GenomeLoc maxSpan = getToolkit().getGenomeLocParser().createPaddedGenomeLoc(region.getLocation(), MAX_GENOTYPING_ACTIVE_REGION_EXTENSION); - final GenomeLoc idealSpan = getToolkit().getGenomeLocParser().createPaddedGenomeLoc(trimLoc, pad); - final GenomeLoc finalSpan = maxSpan.intersect(idealSpan); - - final ActiveRegion trimmedRegion = region.trim(finalSpan); - if ( DEBUG ) { - logger.info("events : " + withinActiveRegion); - logger.info("trimLoc : " + trimLoc); - logger.info("pad : " + pad); - logger.info("idealSpan : " + idealSpan); - logger.info("maxSpan : " + maxSpan); - logger.info("finalSpan : " + finalSpan); - logger.info("regionSpan : " + trimmedRegion.getExtendedLoc() + " size is " + trimmedRegion.getExtendedLoc().size()); - } - return trimmedRegion; - } - /** * Trim down the active region to just enough to properly genotype the events among the haplotypes * @@ -709,17 +770,24 @@ public class HaplotypeCaller extends ActiveRegionWalker implem * @param fullReferenceWithPadding the reference bases over the full padded location * @param paddedReferenceLoc the span of the reference bases * @return an AssemblyResult containing the trimmed active region with all of the reads we should use - * trimmed down as well, and a revised set of haplotypes. If trimming failed this function - * may choose to use the originalActiveRegion without modification + * trimmed down as well, and a revised set of haplotypes. If trimming down the active region results + * in only the reference haplotype over the non-extended active region, returns null. */ private AssemblyResult trimActiveRegion(final ActiveRegion originalActiveRegion, final List haplotypes, final byte[] fullReferenceWithPadding, final GenomeLoc paddedReferenceLoc) { - final ActiveRegion trimmedActiveRegion = createTrimmedRegion(originalActiveRegion, haplotypes, fullReferenceWithPadding, paddedReferenceLoc); + if ( DEBUG ) logger.info("Trimming active region " + originalActiveRegion + " with " + haplotypes.size() + " haplotypes"); - if ( trimmedActiveRegion == null ) - return new AssemblyResult(haplotypes, originalActiveRegion, fullReferenceWithPadding, paddedReferenceLoc); + EventMap.buildEventMapsForHaplotypes(haplotypes, fullReferenceWithPadding, paddedReferenceLoc, DEBUG); + final TreeSet allVariantsWithinFullActiveRegion = EventMap.getAllVariantContexts(haplotypes); + final ActiveRegion trimmedActiveRegion = trimmer.trimRegion(originalActiveRegion, allVariantsWithinFullActiveRegion); + + if ( trimmedActiveRegion == null ) { + // there were no variants found within the active region itself, so just return null + if ( DEBUG ) logger.info("No variation found within the active region, skipping the region :-)"); + return new AssemblyResult(haplotypes, originalActiveRegion, fullReferenceWithPadding, paddedReferenceLoc, false); + } // trim down the haplotypes final Set haplotypeSet = new HashSet(haplotypes.size()); @@ -738,8 +806,8 @@ public class HaplotypeCaller extends ActiveRegionWalker implem // sort haplotypes to take full advantage of haplotype start offset optimizations in PairHMM Collections.sort( trimmedHaplotypes, new HaplotypeBaseComparator() ); + if ( DEBUG ) logger.info("Trimmed region to " + trimmedActiveRegion.getLocation() + " size " + trimmedActiveRegion.getLocation().size() + " reduced number of haplotypes from " + haplotypes.size() + " to only " + trimmedHaplotypes.size()); if ( DEBUG ) { - logger.info("Trimming haplotypes reduced number of haplotypes from " + haplotypes.size() + " to only " + trimmedHaplotypes.size()); for ( final Haplotype remaining: trimmedHaplotypes ) { logger.info(" Remains: " + remaining + " cigar " + remaining.getCigar()); } @@ -757,7 +825,7 @@ public class HaplotypeCaller extends ActiveRegionWalker implem trimmedActiveRegion.clearReads(); trimmedActiveRegion.addAll(ReadUtils.sortReadsByCoordinate(trimmedReads)); - return new AssemblyResult(trimmedHaplotypes, trimmedActiveRegion, fullReferenceWithPadding, paddedReferenceLoc); + return new AssemblyResult(trimmedHaplotypes, trimmedActiveRegion, fullReferenceWithPadding, paddedReferenceLoc, true); } /** @@ -821,15 +889,17 @@ public class HaplotypeCaller extends ActiveRegionWalker implem if( postAdapterRead != null && !postAdapterRead.isEmpty() && postAdapterRead.getCigar().getReadLength() > 0 ) { GATKSAMRecord clippedRead = useLowQualityBasesForAssembly ? postAdapterRead : ReadClipper.hardClipLowQualEnds( postAdapterRead, MIN_TAIL_QUALITY ); - // revert soft clips so that we see the alignment start and end assuming the soft clips are all matches - // TODO -- WARNING -- still possibility that unclipping the soft clips will introduce bases that aren't - // TODO -- truly in the extended region, as the unclipped bases might actually include a deletion - // TODO -- w.r.t. the reference. What really needs to happen is that kmers that occur before the - // TODO -- reference haplotype start must be removed - clippedRead = ReadClipper.revertSoftClippedBases(clippedRead); - - // uncomment to remove hard clips from consideration at all - //clippedRead = ReadClipper.hardClipSoftClippedBases(clippedRead); + if ( dontUseSoftClippedBases ) { + // uncomment to remove hard clips from consideration at all + clippedRead = ReadClipper.hardClipSoftClippedBases(clippedRead); + } else { + // revert soft clips so that we see the alignment start and end assuming the soft clips are all matches + // TODO -- WARNING -- still possibility that unclipping the soft clips will introduce bases that aren't + // TODO -- truly in the extended region, as the unclipped bases might actually include a deletion + // TODO -- w.r.t. the reference. What really needs to happen is that kmers that occur before the + // TODO -- reference haplotype start must be removed + clippedRead = ReadClipper.revertSoftClippedBases(clippedRead); + } clippedRead = ReadClipper.hardClipToRegion( clippedRead, activeRegion.getExtendedLoc().getStart(), activeRegion.getExtendedLoc().getStop() ); if( activeRegion.readOverlapsRegion(clippedRead) && clippedRead.getReadLength() > 0 ) { @@ -843,13 +913,16 @@ public class HaplotypeCaller extends ActiveRegionWalker implem } private List filterNonPassingReads( final org.broadinstitute.sting.utils.activeregion.ActiveRegion activeRegion ) { - final List readsToRemove = new ArrayList(); + final List readsToRemove = new ArrayList<>(); +// logger.info("Filtering non-passing regions: n incoming " + activeRegion.getReads().size()); for( final GATKSAMRecord rec : activeRegion.getReads() ) { - if( rec.getReadLength() < 10 || rec.getMappingQuality() < 20 || BadMateFilter.hasBadMate(rec) || (keepRG != null && !rec.getReadGroup().getId().equals(keepRG)) ) { + if( rec.getReadLength() < MIN_READ_LENGTH || rec.getMappingQuality() < 20 || BadMateFilter.hasBadMate(rec) || (keepRG != null && !rec.getReadGroup().getId().equals(keepRG)) ) { readsToRemove.add(rec); +// logger.info("\tremoving read " + rec + " len " + rec.getReadLength()); } } activeRegion.removeAll( readsToRemove ); +// logger.info("Filtered non-passing regions: n remaining " + activeRegion.getReads().size()); return readsToRemove; } diff --git a/protected/java/src/org/broadinstitute/sting/gatk/walkers/haplotypecaller/KMerCounter.java b/protected/java/src/org/broadinstitute/sting/gatk/walkers/haplotypecaller/KMerCounter.java index a7194f85f..aad8407dd 100644 --- a/protected/java/src/org/broadinstitute/sting/gatk/walkers/haplotypecaller/KMerCounter.java +++ b/protected/java/src/org/broadinstitute/sting/gatk/walkers/haplotypecaller/KMerCounter.java @@ -46,9 +46,7 @@ package org.broadinstitute.sting.gatk.walkers.haplotypecaller; -import java.util.Collection; -import java.util.HashMap; -import java.util.Map; +import java.util.*; /** * generic utility class that counts kmers @@ -97,6 +95,20 @@ public class KMerCounter { return countsByKMer.values(); } + /** + * Get kmers that have minCount or greater in this counter + * @param minCount only return kmers with count >= this value + * @return a non-null collection of kmers + */ + public Collection getKmersWithCountsAtLeast(final int minCount) { + final List result = new LinkedList(); + for ( final CountedKmer countedKmer : getCountedKmers() ) { + if ( countedKmer.count >= minCount ) + result.add(countedKmer.kmer); + } + return result; + } + /** * Remove all current counts, resetting the counter to an empty state */ diff --git a/protected/java/src/org/broadinstitute/sting/gatk/walkers/haplotypecaller/Kmer.java b/protected/java/src/org/broadinstitute/sting/gatk/walkers/haplotypecaller/Kmer.java index 9b0e1ac0a..745d4de06 100644 --- a/protected/java/src/org/broadinstitute/sting/gatk/walkers/haplotypecaller/Kmer.java +++ b/protected/java/src/org/broadinstitute/sting/gatk/walkers/haplotypecaller/Kmer.java @@ -149,6 +149,14 @@ public class Kmer { return bases; } + /** + * Get a string representation of the bases of this kmer + * @return a non-null string + */ + public String baseString() { + return new String(bases()); + } + /** * The length of this kmer * @return an integer >= 0 diff --git a/protected/java/src/org/broadinstitute/sting/gatk/walkers/haplotypecaller/LikelihoodCalculationEngine.java b/protected/java/src/org/broadinstitute/sting/gatk/walkers/haplotypecaller/LikelihoodCalculationEngine.java index 8697833a6..fbd9b29d5 100644 --- a/protected/java/src/org/broadinstitute/sting/gatk/walkers/haplotypecaller/LikelihoodCalculationEngine.java +++ b/protected/java/src/org/broadinstitute/sting/gatk/walkers/haplotypecaller/LikelihoodCalculationEngine.java @@ -69,19 +69,33 @@ public class LikelihoodCalculationEngine { private static final double LOG_ONE_HALF = -Math.log10(2.0); private final byte constantGCP; + private final double log10globalReadMismappingRate; private final boolean DEBUG; private final PairHMM pairHMM; - private final int minReadLength = 20; /** * The expected rate of random sequencing errors for a read originating from its true haplotype. * * For example, if this is 0.01, then we'd expect 1 error per 100 bp. */ - private final double EXPECTED_ERROR_RATE_PER_BASE = 0.02; - - public LikelihoodCalculationEngine( final byte constantGCP, final boolean debug, final PairHMM.HMM_IMPLEMENTATION hmmType ) { + private final static double EXPECTED_ERROR_RATE_PER_BASE = 0.02; + /** + * Create a new LikelihoodCalculationEngine using provided parameters and hmm to do its calculations + * + * @param constantGCP the gap continuation penalty to use with the PairHMM + * @param debug should we emit debugging information during the calculation? + * @param hmmType the type of the HMM to use + * @param log10globalReadMismappingRate the global mismapping probability, in log10(prob) units. A value of + * -3 means that the chance that a read doesn't actually belong at this + * location in the genome is 1 in 1000. The effect of this parameter is + * to cap the maximum likelihood difference between the reference haplotype + * and the best alternative haplotype by -3 log units. So if the best + * haplotype is at -10 and this parameter has a value of -3 then even if the + * reference haplotype gets a score of -100 from the pairhmm it will be + * assigned a likelihood of -13. + */ + public LikelihoodCalculationEngine( final byte constantGCP, final boolean debug, final PairHMM.HMM_IMPLEMENTATION hmmType, final double log10globalReadMismappingRate ) { switch (hmmType) { case EXACT: pairHMM = new Log10PairHMM(true); @@ -98,6 +112,11 @@ public class LikelihoodCalculationEngine { this.constantGCP = constantGCP; DEBUG = debug; + this.log10globalReadMismappingRate = log10globalReadMismappingRate; + } + + public LikelihoodCalculationEngine() { + this((byte)10, false, PairHMM.HMM_IMPLEMENTATION.LOGLESS_CACHING, -3); } /** @@ -134,7 +153,6 @@ public class LikelihoodCalculationEngine { // Add likelihoods for each sample's reads to our stratifiedReadMap final Map stratifiedReadMap = new HashMap(); for( final Map.Entry> sampleEntry : perSampleReadList.entrySet() ) { - //if( DEBUG ) { System.out.println("Evaluating sample " + sample + " with " + perSampleReadList.get( sample ).size() + " passing reads"); } // evaluate the likelihood of the reads given those haplotypes final PerReadAlleleLikelihoodMap map = computeReadLikelihoods(haplotypes, sampleEntry.getValue()); @@ -152,17 +170,16 @@ public class LikelihoodCalculationEngine { private PerReadAlleleLikelihoodMap computeReadLikelihoods( final List haplotypes, final List reads) { // first, a little set up to get copies of the Haplotypes that are Alleles (more efficient than creating them each time) final int numHaplotypes = haplotypes.size(); - final Map alleleVersions = new HashMap(numHaplotypes); + final Map alleleVersions = new HashMap<>(numHaplotypes); + Allele refAllele = null; for ( final Haplotype haplotype : haplotypes ) { - alleleVersions.put(haplotype, Allele.create(haplotype, true)); + final Allele allele = Allele.create(haplotype, true); + alleleVersions.put(haplotype, allele); + if ( haplotype.isReference() ) refAllele = allele; } final PerReadAlleleLikelihoodMap perReadAlleleLikelihoodMap = new PerReadAlleleLikelihoodMap(); for( final GATKSAMRecord read : reads ) { - if ( read.getReadLength() < minReadLength ) - // don't consider any reads that have a read length < the minimum - continue; - final byte[] overallGCP = new byte[read.getReadLength()]; Arrays.fill( overallGCP, constantGCP ); // Is there a way to derive empirical estimates for this from the data? // NOTE -- must clone anything that gets modified here so we don't screw up future uses of the read @@ -177,14 +194,34 @@ public class LikelihoodCalculationEngine { readQuals[kkk] = ( readQuals[kkk] < (byte) 18 ? QualityUtils.MIN_USABLE_Q_SCORE : readQuals[kkk] ); } + // keep track of the reference likelihood and the best non-ref likelihood + double refLog10l = Double.NEGATIVE_INFINITY; + double bestNonReflog10L = Double.NEGATIVE_INFINITY; + + // iterate over all haplotypes, calculating the likelihood of the read for each haplotype for( int jjj = 0; jjj < numHaplotypes; jjj++ ) { final Haplotype haplotype = haplotypes.get(jjj); final boolean isFirstHaplotype = jjj == 0; final double log10l = pairHMM.computeReadLikelihoodGivenHaplotypeLog10(haplotype.getBases(), read.getReadBases(), readQuals, readInsQuals, readDelQuals, overallGCP, isFirstHaplotype); + if ( haplotype.isNonReference() ) + bestNonReflog10L = Math.max(bestNonReflog10L, log10l); + else + refLog10l = log10l; + perReadAlleleLikelihoodMap.add(read, alleleVersions.get(haplotype), log10l); } + + // ensure that the reference haplotype is no worse than the best non-ref haplotype minus the global + // mismapping rate. This protects us from the case where the assembly has produced haplotypes + // that are very divergent from reference, but are supported by only one read. In effect + // we capping how badly scoring the reference can be for any read by the chance that the read + // itself just doesn't belong here + final double worstRefLog10Allowed = bestNonReflog10L + log10globalReadMismappingRate; + if ( refLog10l < (worstRefLog10Allowed) ) { + perReadAlleleLikelihoodMap.add(read, refAllele, worstRefLog10Allowed); + } } return perReadAlleleLikelihoodMap; diff --git a/protected/java/src/org/broadinstitute/sting/gatk/walkers/haplotypecaller/LocalAssemblyEngine.java b/protected/java/src/org/broadinstitute/sting/gatk/walkers/haplotypecaller/LocalAssemblyEngine.java index 4c0483ad6..20b005b40 100644 --- a/protected/java/src/org/broadinstitute/sting/gatk/walkers/haplotypecaller/LocalAssemblyEngine.java +++ b/protected/java/src/org/broadinstitute/sting/gatk/walkers/haplotypecaller/LocalAssemblyEngine.java @@ -46,28 +46,388 @@ package org.broadinstitute.sting.gatk.walkers.haplotypecaller; +import com.google.java.contract.Ensures; +import com.google.java.contract.Requires; +import net.sf.samtools.Cigar; +import net.sf.samtools.CigarElement; +import net.sf.samtools.CigarOperator; +import org.apache.commons.lang.ArrayUtils; +import org.apache.log4j.Logger; +import org.broadinstitute.sting.gatk.walkers.haplotypecaller.graphs.*; import org.broadinstitute.sting.utils.GenomeLoc; import org.broadinstitute.sting.utils.haplotype.Haplotype; import org.broadinstitute.sting.utils.activeregion.ActiveRegion; +import org.broadinstitute.sting.utils.sam.AlignmentUtils; +import org.broadinstitute.sting.utils.sam.GATKSAMRecord; +import org.broadinstitute.sting.utils.sam.ReadUtils; +import org.broadinstitute.sting.utils.smithwaterman.SWPairwiseAlignment; +import org.broadinstitute.sting.utils.smithwaterman.SWParameterSet; +import org.broadinstitute.variant.variantcontext.Allele; import org.broadinstitute.variant.variantcontext.VariantContext; +import java.io.File; import java.io.PrintStream; -import java.util.List; +import java.util.*; /** - * Created by IntelliJ IDEA. + * Abstract base class for all HaplotypeCaller assemblers + * * User: ebanks * Date: Mar 14, 2011 */ public abstract class LocalAssemblyEngine { - public static final byte DEFAULT_MIN_BASE_QUALITY_TO_USE = (byte) 8; + private final static Logger logger = Logger.getLogger(LocalAssemblyEngine.class); + + public static final byte DEFAULT_MIN_BASE_QUALITY_TO_USE = (byte) 8; + private static final int MIN_HAPLOTYPE_REFERENCE_LENGTH = 30; + + protected final int numBestHaplotypesPerGraph; + + protected boolean debug = false; + protected boolean allowCyclesInKmerGraphToGeneratePaths = false; + protected boolean debugGraphTransformations = false; + protected boolean recoverDanglingTails = true; - protected PrintStream graphWriter = null; protected byte minBaseQualityToUseInAssembly = DEFAULT_MIN_BASE_QUALITY_TO_USE; protected int pruneFactor = 2; protected boolean errorCorrectKmers = false; - protected LocalAssemblyEngine() { } + private PrintStream graphWriter = null; + + /** + * Create a new LocalAssemblyEngine with all default parameters, ready for use + * @param numBestHaplotypesPerGraph the number of haplotypes to generate for each assembled graph + */ + protected LocalAssemblyEngine(final int numBestHaplotypesPerGraph) { + if ( numBestHaplotypesPerGraph < 1 ) throw new IllegalArgumentException("numBestHaplotypesPerGraph should be >= 1 but got " + numBestHaplotypesPerGraph); + this.numBestHaplotypesPerGraph = numBestHaplotypesPerGraph; + } + + /** + * Main subclass function: given reads and a reference haplotype give us graphs to use for constructing + * non-reference haplotypes. + * + * @param reads the reads we're going to assemble + * @param refHaplotype the reference haplotype + * @return a non-null list of reads + */ + protected abstract List assemble(List reads, Haplotype refHaplotype); + + /** + * Main entry point into the assembly engine. Build a set of deBruijn graphs out of the provided reference sequence and list of reads + * @param activeRegion ActiveRegion object holding the reads which are to be used during assembly + * @param refHaplotype reference haplotype object + * @param fullReferenceWithPadding byte array holding the reference sequence with padding + * @param refLoc GenomeLoc object corresponding to the reference sequence with padding + * @param activeAllelesToGenotype the alleles to inject into the haplotypes during GGA mode + * @return a non-empty list of all the haplotypes that are produced during assembly + */ + public List runLocalAssembly(ActiveRegion activeRegion, Haplotype refHaplotype, byte[] fullReferenceWithPadding, GenomeLoc refLoc, List activeAllelesToGenotype) { + if( activeRegion == null ) { throw new IllegalArgumentException("Assembly engine cannot be used with a null ActiveRegion."); } + if( refHaplotype == null ) { throw new IllegalArgumentException("Reference haplotype cannot be null."); } + if( fullReferenceWithPadding.length != refLoc.size() ) { throw new IllegalArgumentException("Reference bases and reference loc must be the same size."); } + if( pruneFactor < 0 ) { throw new IllegalArgumentException("Pruning factor cannot be negative"); } + + // create the graphs by calling our subclass assemble method + final List graphs = assemble(activeRegion.getReads(), refHaplotype); + + // do some QC on the graphs + for ( final SeqGraph graph : graphs ) { sanityCheckGraph(graph, refHaplotype); } + + // print the graphs if the appropriate debug option has been turned on + if ( graphWriter != null ) { printGraphs(graphs); } + + // find the best paths in the graphs and return them as haplotypes + return findBestPaths( graphs, refHaplotype, fullReferenceWithPadding, refLoc, activeAllelesToGenotype, activeRegion.getExtendedLoc() ); + } + + @Requires({"refWithPadding.length > refHaplotype.getBases().length", "refLoc.containsP(activeRegionWindow)"}) + @Ensures({"result.contains(refHaplotype)"}) + protected List findBestPaths(final List graphs, final Haplotype refHaplotype, final byte[] refWithPadding, final GenomeLoc refLoc, final List activeAllelesToGenotype, final GenomeLoc activeRegionWindow) { + // add the reference haplotype separately from all the others to ensure that it is present in the list of haplotypes + final Set returnHaplotypes = new LinkedHashSet(); + refHaplotype.setAlignmentStartHapwrtRef(activeRegionWindow.getStart() - refLoc.getStart()); + final Cigar c = new Cigar(); + c.add(new CigarElement(refHaplotype.getBases().length, CigarOperator.M)); + refHaplotype.setCigar(c); + returnHaplotypes.add( refHaplotype ); + + final int activeRegionStart = refHaplotype.getAlignmentStartHapwrtRef(); + final int activeRegionStop = refHaplotype.getAlignmentStartHapwrtRef() + refHaplotype.getCigar().getReferenceLength(); + + // for GGA mode, add the desired allele into the haplotype + for( final VariantContext compVC : activeAllelesToGenotype ) { + for( final Allele compAltAllele : compVC.getAlternateAlleles() ) { + final Haplotype insertedRefHaplotype = refHaplotype.insertAllele(compVC.getReference(), compAltAllele, activeRegionStart + compVC.getStart() - activeRegionWindow.getStart(), compVC.getStart()); + addHaplotypeForGGA( insertedRefHaplotype, refWithPadding, returnHaplotypes, activeRegionStart, activeRegionStop, true ); + } + } + + for( final SeqGraph graph : graphs ) { + final SeqVertex source = graph.getReferenceSourceVertex(); + final SeqVertex sink = graph.getReferenceSinkVertex(); + if ( source == null || sink == null ) throw new IllegalArgumentException("Both source and sink cannot be null but got " + source + " and sink " + sink + " for graph "+ graph); + + final KBestPaths pathFinder = new KBestPaths(allowCyclesInKmerGraphToGeneratePaths); + for ( final Path path : pathFinder.getKBestPaths(graph, numBestHaplotypesPerGraph, source, sink) ) { +// logger.info("Found path " + path); + Haplotype h = new Haplotype( path.getBases() ); + if( !returnHaplotypes.contains(h) ) { + final Cigar cigar = path.calculateCigar(refHaplotype.getBases()); + + if ( cigar == null ) { + // couldn't produce a meaningful alignment of haplotype to reference, fail quitely + continue; + } else if( cigar.isEmpty() ) { + throw new IllegalStateException("Smith-Waterman alignment failure. Cigar = " + cigar + " with reference length " + cigar.getReferenceLength() + + " but expecting reference length of " + refHaplotype.getCigar().getReferenceLength()); + } else if ( pathIsTooDivergentFromReference(cigar) || cigar.getReferenceLength() < MIN_HAPLOTYPE_REFERENCE_LENGTH ) { + // N cigar elements means that a bubble was too divergent from the reference so skip over this path + continue; + } else if( cigar.getReferenceLength() != refHaplotype.getCigar().getReferenceLength() ) { // SW failure + throw new IllegalStateException("Smith-Waterman alignment failure. Cigar = " + cigar + " with reference length " + + cigar.getReferenceLength() + " but expecting reference length of " + refHaplotype.getCigar().getReferenceLength() + + " ref = " + refHaplotype + " path " + new String(path.getBases())); + } + + h.setCigar(cigar); + h.setAlignmentStartHapwrtRef(activeRegionStart); + h.setScore(path.getScore()); + returnHaplotypes.add(h); + + if ( debug ) + logger.info("Adding haplotype " + h.getCigar() + " from debruijn graph with kmer " + graph.getKmerSize()); + + // for GGA mode, add the desired allele into the haplotype if it isn't already present + if( !activeAllelesToGenotype.isEmpty() ) { + final Map eventMap = GenotypingEngine.generateVCsFromAlignment( h, refWithPadding, refLoc, "HCassembly" ); // BUGBUG: need to put this function in a shared place + for( final VariantContext compVC : activeAllelesToGenotype ) { // for GGA mode, add the desired allele into the haplotype if it isn't already present + final VariantContext vcOnHaplotype = eventMap.get(compVC.getStart()); + + // This if statement used to additionally have: + // "|| !vcOnHaplotype.hasSameAllelesAs(compVC)" + // but that can lead to problems downstream when e.g. you are injecting a 1bp deletion onto + // a haplotype that already contains a 1bp insertion (so practically it is reference but + // falls into the bin for the 1bp deletion because we keep track of the artificial alleles). + if( vcOnHaplotype == null ) { + for( final Allele compAltAllele : compVC.getAlternateAlleles() ) { + addHaplotypeForGGA( h.insertAllele(compVC.getReference(), compAltAllele, activeRegionStart + compVC.getStart() - activeRegionWindow.getStart(), compVC.getStart()), refWithPadding, returnHaplotypes, activeRegionStart, activeRegionStop, false ); + } + } + } + } + } + } + } + + // add genome locs to the haplotypes + for ( final Haplotype h : returnHaplotypes ) h.setGenomeLocation(activeRegionWindow); + + if ( returnHaplotypes.size() < returnHaplotypes.size() ) + logger.info("Found " + returnHaplotypes.size() + " candidate haplotypes of " + returnHaplotypes.size() + " possible combinations to evaluate every read against at " + refLoc); + + if( debug ) { + if( returnHaplotypes.size() > 1 ) { + logger.info("Found " + returnHaplotypes.size() + " candidate haplotypes of " + returnHaplotypes.size() + " possible combinations to evaluate every read against."); + } else { + logger.info("Found only the reference haplotype in the assembly graph."); + } + for( final Haplotype h : returnHaplotypes ) { + logger.info( h.toString() ); + logger.info( "> Cigar = " + h.getCigar() + " : " + h.getCigar().getReferenceLength() + " score " + h.getScore() + " ref " + h.isReference()); + } + } + + return new ArrayList(returnHaplotypes); + } + + /** + * We use CigarOperator.N as the signal that an incomplete or too divergent bubble was found during bubble traversal + * @param c the cigar to test + * @return true if we should skip over this path + */ + @Requires("c != null") + private boolean pathIsTooDivergentFromReference( final Cigar c ) { + for( final CigarElement ce : c.getCigarElements() ) { + if( ce.getOperator().equals(CigarOperator.N) ) { + return true; + } + } + return false; + } + + /** + * Take a haplotype which was generated by injecting an allele into a string of bases and run SW against the reference to determine the variants on the haplotype. + * Unfortunately since this haplotype didn't come from the assembly graph you can't straightforwardly use the bubble traversal algorithm to get this information. + * This is a target for future work as we rewrite the HaplotypeCaller to be more bubble-caller based. + * @param haplotype the candidate haplotype + * @param ref the reference bases to align against + * @param haplotypeList the current list of haplotypes + * @param activeRegionStart the start of the active region in the reference byte array + * @param activeRegionStop the stop of the active region in the reference byte array + * @param FORCE_INCLUSION_FOR_GGA_MODE if true will include in the list even if it already exists + * @return true if the candidate haplotype was successfully incorporated into the haplotype list + */ + @Requires({"ref != null", "ref.length >= activeRegionStop - activeRegionStart"}) + private boolean addHaplotypeForGGA( final Haplotype haplotype, final byte[] ref, final Set haplotypeList, final int activeRegionStart, final int activeRegionStop, final boolean FORCE_INCLUSION_FOR_GGA_MODE ) { + if( haplotype == null ) { return false; } + + final SWPairwiseAlignment swConsensus = new SWPairwiseAlignment( ref, haplotype.getBases(), SWParameterSet.STANDARD_NGS ); + haplotype.setAlignmentStartHapwrtRef( swConsensus.getAlignmentStart2wrt1() ); + + if( swConsensus.getCigar().toString().contains("S") || swConsensus.getCigar().getReferenceLength() < 60 || swConsensus.getAlignmentStart2wrt1() < 0 ) { // protect against unhelpful haplotype alignments + return false; + } + + haplotype.setCigar( AlignmentUtils.leftAlignIndel(swConsensus.getCigar(), ref, haplotype.getBases(), swConsensus.getAlignmentStart2wrt1(), 0, true) ); + + final int hapStart = ReadUtils.getReadCoordinateForReferenceCoordinate(haplotype.getAlignmentStartHapwrtRef(), haplotype.getCigar(), activeRegionStart, ReadUtils.ClippingTail.LEFT_TAIL, true); + int hapStop = ReadUtils.getReadCoordinateForReferenceCoordinate( haplotype.getAlignmentStartHapwrtRef(), haplotype.getCigar(), activeRegionStop, ReadUtils.ClippingTail.RIGHT_TAIL, true ); + if( hapStop == ReadUtils.CLIPPING_GOAL_NOT_REACHED && activeRegionStop == haplotype.getAlignmentStartHapwrtRef() + haplotype.getCigar().getReferenceLength() ) { + hapStop = activeRegionStop; // contract for getReadCoordinateForReferenceCoordinate function says that if read ends at boundary then it is outside of the clipping goal + } + byte[] newHaplotypeBases; + // extend partial haplotypes to contain the full active region sequence + if( hapStart == ReadUtils.CLIPPING_GOAL_NOT_REACHED && hapStop == ReadUtils.CLIPPING_GOAL_NOT_REACHED ) { + newHaplotypeBases = ArrayUtils.addAll(ArrayUtils.addAll(ArrayUtils.subarray(ref, activeRegionStart, swConsensus.getAlignmentStart2wrt1()), + haplotype.getBases()), + ArrayUtils.subarray(ref, swConsensus.getAlignmentStart2wrt1() + swConsensus.getCigar().getReferenceLength(), activeRegionStop)); + } else if( hapStart == ReadUtils.CLIPPING_GOAL_NOT_REACHED ) { + newHaplotypeBases = ArrayUtils.addAll( ArrayUtils.subarray(ref, activeRegionStart, swConsensus.getAlignmentStart2wrt1()), ArrayUtils.subarray(haplotype.getBases(), 0, hapStop) ); + } else if( hapStop == ReadUtils.CLIPPING_GOAL_NOT_REACHED ) { + newHaplotypeBases = ArrayUtils.addAll( ArrayUtils.subarray(haplotype.getBases(), hapStart, haplotype.getBases().length), ArrayUtils.subarray(ref, swConsensus.getAlignmentStart2wrt1() + swConsensus.getCigar().getReferenceLength(), activeRegionStop) ); + } else { + newHaplotypeBases = ArrayUtils.subarray(haplotype.getBases(), hapStart, hapStop); + } + + final Haplotype h = new Haplotype( newHaplotypeBases ); + final SWPairwiseAlignment swConsensus2 = new SWPairwiseAlignment( ref, h.getBases(), SWParameterSet.STANDARD_NGS ); + + h.setAlignmentStartHapwrtRef( swConsensus2.getAlignmentStart2wrt1() ); + if ( haplotype.isArtificialHaplotype() ) { + h.setArtificialEvent(haplotype.getArtificialEvent()); + } + if( swConsensus2.getCigar().toString().contains("S") || swConsensus2.getCigar().getReferenceLength() != activeRegionStop - activeRegionStart || swConsensus2.getAlignmentStart2wrt1() < 0 ) { // protect against unhelpful haplotype alignments + return false; + } + + h.setCigar( AlignmentUtils.leftAlignIndel(swConsensus2.getCigar(), ref, h.getBases(), swConsensus2.getAlignmentStart2wrt1(), 0, true) ); + + if( FORCE_INCLUSION_FOR_GGA_MODE || !haplotypeList.contains(h) ) { + haplotypeList.add(h); + return true; + } else { + return false; + } + } + + protected SeqGraph cleanupSeqGraph(final SeqGraph seqGraph) { + if ( debugGraphTransformations ) seqGraph.printGraph(new File("sequenceGraph.1.dot"), pruneFactor); + + // TODO -- we need to come up with a consistent pruning algorithm. The current pruning algorithm + // TODO -- works well but it doesn't differentiate between an isolated chain that doesn't connect + // TODO -- to anything from one that's actually has good support along the chain but just happens + // TODO -- to have a connection in the middle that has weight of < pruneFactor. Ultimately + // TODO -- the pruning algorithm really should be an error correction algorithm that knows more + // TODO -- about the structure of the data and can differentiate between an infrequent path but + // TODO -- without evidence against it (such as occurs when a region is hard to get any reads through) + // TODO -- from a error with lots of weight going along another similar path + // the very first thing we need to do is zip up the graph, or pruneGraph will be too aggressive + seqGraph.zipLinearChains(); + if ( debugGraphTransformations ) seqGraph.printGraph(new File("sequenceGraph.2.zipped.dot"), pruneFactor); + + // now go through and prune the graph, removing vertices no longer connected to the reference chain + // IMPORTANT: pruning must occur before we call simplifyGraph, as simplifyGraph adds 0 weight + // edges to maintain graph connectivity. + seqGraph.pruneGraph(pruneFactor); + seqGraph.removeVerticesNotConnectedToRefRegardlessOfEdgeDirection(); + + if ( debugGraphTransformations ) seqGraph.printGraph(new File("sequenceGraph.3.pruned.dot"), pruneFactor); + seqGraph.simplifyGraph(); + if ( debugGraphTransformations ) seqGraph.printGraph(new File("sequenceGraph.4.merged.dot"), pruneFactor); + + // The graph has degenerated in some way, so the reference source and/or sink cannot be id'd. Can + // happen in cases where for example the reference somehow manages to acquire a cycle, or + // where the entire assembly collapses back into the reference sequence. + if ( seqGraph.getReferenceSourceVertex() == null || seqGraph.getReferenceSinkVertex() == null ) + return null; + + seqGraph.removePathsNotConnectedToRef(); + seqGraph.simplifyGraph(); + if ( seqGraph.vertexSet().size() == 1 ) { + // we've perfectly assembled into a single reference haplotype, add a empty seq vertex to stop + // the code from blowing up. + // TODO -- ref properties should really be on the vertices, not the graph itself + final SeqVertex complete = seqGraph.vertexSet().iterator().next(); + final SeqVertex dummy = new SeqVertex(""); + seqGraph.addVertex(dummy); + seqGraph.addEdge(complete, dummy, new BaseEdge(true, 0)); + } + if ( debugGraphTransformations ) seqGraph.printGraph(new File("sequenceGraph.5.final.dot"), pruneFactor); + + return seqGraph; + } + + /** + * Perform general QC on the graph to make sure something hasn't gone wrong during assembly + * @param graph the graph to check + * @param refHaplotype the reference haplotype + * @param + */ + private void sanityCheckGraph(final BaseGraph graph, final Haplotype refHaplotype) { + sanityCheckReferenceGraph(graph, refHaplotype); + } + + /** + * Make sure the reference sequence is properly represented in the provided graph + * + * @param graph the graph to check + * @param refHaplotype the reference haplotype + * @param + */ + private void sanityCheckReferenceGraph(final BaseGraph graph, final Haplotype refHaplotype) { + if( graph.getReferenceSourceVertex() == null ) { + throw new IllegalStateException("All reference graphs must have a reference source vertex."); + } + if( graph.getReferenceSinkVertex() == null ) { + throw new IllegalStateException("All reference graphs must have a reference sink vertex."); + } + if( !Arrays.equals(graph.getReferenceBytes(graph.getReferenceSourceVertex(), graph.getReferenceSinkVertex(), true, true), refHaplotype.getBases()) ) { + throw new IllegalStateException("Mismatch between the reference haplotype and the reference assembly graph path. for graph " + graph + + " graph = " + new String(graph.getReferenceBytes(graph.getReferenceSourceVertex(), graph.getReferenceSinkVertex(), true, true)) + + " haplotype = " + new String(refHaplotype.getBases()) + ); + } + } + + /** + * Print the generated graphs to the graphWriter + * @param graphs a non-null list of graphs to print out + */ + private void printGraphs(final List graphs) { + final int writeFirstGraphWithSizeSmallerThan = 50; + + graphWriter.println("digraph assemblyGraphs {"); + for( final SeqGraph graph : graphs ) { + if ( debugGraphTransformations && graph.getKmerSize() >= writeFirstGraphWithSizeSmallerThan ) { + logger.info("Skipping writing of graph with kmersize " + graph.getKmerSize()); + continue; + } + + graph.printGraph(graphWriter, false, pruneFactor); + + if ( debugGraphTransformations ) + break; + } + + graphWriter.println("}"); + } + + // ----------------------------------------------------------------------------------------------- + // + // getter / setter routines for generic assembler properties + // + // ----------------------------------------------------------------------------------------------- public int getPruneFactor() { return pruneFactor; @@ -85,10 +445,6 @@ public abstract class LocalAssemblyEngine { this.errorCorrectKmers = errorCorrectKmers; } - public PrintStream getGraphWriter() { - return graphWriter; - } - public void setGraphWriter(PrintStream graphWriter) { this.graphWriter = graphWriter; } @@ -101,5 +457,35 @@ public abstract class LocalAssemblyEngine { this.minBaseQualityToUseInAssembly = minBaseQualityToUseInAssembly; } - public abstract List runLocalAssembly(ActiveRegion activeRegion, Haplotype refHaplotype, byte[] fullReferenceWithPadding, GenomeLoc refLoc, List activeAllelesToGenotype); + public boolean isDebug() { + return debug; + } + + public void setDebug(boolean debug) { + this.debug = debug; + } + + public boolean isAllowCyclesInKmerGraphToGeneratePaths() { + return allowCyclesInKmerGraphToGeneratePaths; + } + + public void setAllowCyclesInKmerGraphToGeneratePaths(boolean allowCyclesInKmerGraphToGeneratePaths) { + this.allowCyclesInKmerGraphToGeneratePaths = allowCyclesInKmerGraphToGeneratePaths; + } + + public boolean isDebugGraphTransformations() { + return debugGraphTransformations; + } + + public void setDebugGraphTransformations(boolean debugGraphTransformations) { + this.debugGraphTransformations = debugGraphTransformations; + } + + public boolean isRecoverDanglingTails() { + return recoverDanglingTails; + } + + public void setRecoverDanglingTails(boolean recoverDanglingTails) { + this.recoverDanglingTails = recoverDanglingTails; + } } diff --git a/protected/java/src/org/broadinstitute/sting/gatk/walkers/haplotypecaller/graphs/BaseEdge.java b/protected/java/src/org/broadinstitute/sting/gatk/walkers/haplotypecaller/graphs/BaseEdge.java index be5a431c4..a6ef0d1c2 100644 --- a/protected/java/src/org/broadinstitute/sting/gatk/walkers/haplotypecaller/graphs/BaseEdge.java +++ b/protected/java/src/org/broadinstitute/sting/gatk/walkers/haplotypecaller/graphs/BaseEdge.java @@ -76,12 +76,10 @@ public class BaseEdge { } /** - * Copy constructor - * - * @param toCopy + * Create a new copy of this BaseEdge */ - public BaseEdge(final BaseEdge toCopy) { - this(toCopy.isRef(), toCopy.getMultiplicity()); + public BaseEdge copy() { + return new BaseEdge(isRef(), getMultiplicity()); } /** @@ -92,6 +90,34 @@ public class BaseEdge { return multiplicity; } + /** + * Get the DOT format label for this edge, to be displayed when printing this edge to a DOT file + * @return a non-null string + */ + public String getDotLabel() { + return Integer.toString(getMultiplicity()); + } + + /** + * Increase the multiplicity of this edge by incr + * @param incr the change in this multiplicity, must be >= 0 + */ + public void incMultiplicity(final int incr) { + if ( incr < 0 ) throw new IllegalArgumentException("incr must be >= 0 but got " + incr); + multiplicity += incr; + } + + /** + * A special assessor that returns the multiplicity that should be used by pruning algorithm + * + * Can be overloaded by subclasses + * + * @return the multiplicity value that should be used for pruning + */ + public int getPruningMultiplicity() { + return getMultiplicity(); + } + /** * Set the multiplicity of this edge to value * @param value an integer >= 0 @@ -117,23 +143,6 @@ public class BaseEdge { this.isRef = isRef; } - /** - * Does this and edge have the same source and target vertices in graph? - * - * @param graph the graph containing both this and edge - * @param edge our comparator edge - * @param - * @return true if we have the same source and target vertices - */ - public boolean hasSameSourceAndTarget(final BaseGraph graph, final BaseEdge edge) { - return (graph.getEdgeSource(this).equals(graph.getEdgeSource(edge))) && (graph.getEdgeTarget(this).equals(graph.getEdgeTarget(edge))); - } - - // For use when comparing edges across graphs! - public boolean seqEquals( final BaseGraph graph, final BaseEdge edge, final BaseGraph graph2 ) { - return (graph.getEdgeSource(this).seqEquals(graph2.getEdgeSource(edge))) && (graph.getEdgeTarget(this).seqEquals(graph2.getEdgeTarget(edge))); - } - /** * Sorts a collection of BaseEdges in decreasing order of weight, so that the most * heavily weighted is at the start of the list @@ -187,4 +196,12 @@ public class BaseEdge { if ( edge == null ) throw new IllegalArgumentException("edge cannot be null"); return new BaseEdge(isRef() || edge.isRef(), Math.max(getMultiplicity(), edge.getMultiplicity())); } + + @Override + public String toString() { + return "BaseEdge{" + + "multiplicity=" + multiplicity + + ", isRef=" + isRef + + '}'; + } } diff --git a/protected/java/src/org/broadinstitute/sting/gatk/walkers/haplotypecaller/graphs/BaseGraph.java b/protected/java/src/org/broadinstitute/sting/gatk/walkers/haplotypecaller/graphs/BaseGraph.java index 7ce57e2e7..8938af7c2 100644 --- a/protected/java/src/org/broadinstitute/sting/gatk/walkers/haplotypecaller/graphs/BaseGraph.java +++ b/protected/java/src/org/broadinstitute/sting/gatk/walkers/haplotypecaller/graphs/BaseGraph.java @@ -66,34 +66,16 @@ import java.util.*; * Date: 2/6/13 */ @Invariant("!this.isAllowingMultipleEdges()") -public class BaseGraph extends DefaultDirectedGraph { +public class BaseGraph extends DefaultDirectedGraph { protected final static Logger logger = Logger.getLogger(BaseGraph.class); private final int kmerSize; - /** - * Construct an empty BaseGraph - */ - public BaseGraph() { - this(11); - } - - /** - * Edge factory that creates non-reference multiplicity 1 edges - * @param the new of our vertices - */ - private static class MyEdgeFactory implements EdgeFactory { - @Override - public BaseEdge createEdge(T sourceVertex, T targetVertex) { - return new BaseEdge(false, 1); - } - } - /** * Construct a DeBruijnGraph with kmerSize * @param kmerSize */ - public BaseGraph(final int kmerSize) { - super(new MyEdgeFactory()); + public BaseGraph(final int kmerSize, final EdgeFactory edgeFactory) { + super(edgeFactory); if ( kmerSize < 1 ) throw new IllegalArgumentException("kmerSize must be >= 1 but got " + kmerSize); this.kmerSize = kmerSize; @@ -111,7 +93,7 @@ public class BaseGraph extends DefaultDirectedGraph extends DefaultDirectedGraph extends DefaultDirectedGraph extends DefaultDirectedGraph getSources() { - final Set set = new LinkedHashSet(); - for ( final T v : vertexSet() ) + public Set getSources() { + final Set set = new LinkedHashSet(); + for ( final V v : vertexSet() ) if ( isSource(v) ) set.add(v); return set; @@ -153,9 +135,9 @@ public class BaseGraph extends DefaultDirectedGraph getSinks() { - final Set set = new LinkedHashSet(); - for ( final T v : vertexSet() ) + public Set getSinks() { + final Set set = new LinkedHashSet(); + for ( final V v : vertexSet() ) if ( isSink(v) ) set.add(v); return set; @@ -167,7 +149,7 @@ public class BaseGraph extends DefaultDirectedGraph extends DefaultDirectedGraph extends DefaultDirectedGraph extends DefaultDirectedGraph extends DefaultDirectedGraph extends DefaultDirectedGraph extends DefaultDirectedGraph extends DefaultDirectedGraph extends DefaultDirectedGraph extends DefaultDirectedGraph extends DefaultDirectedGraph vertices) { - for ( final T v : vertices ) + public void addVertices(final Collection vertices) { + for ( final V v : vertices ) addVertex(v); } @@ -349,8 +341,12 @@ public class BaseGraph extends DefaultDirectedGraph extends DefaultDirectedGraph extends DefaultDirectedGraph outgoingVerticesOf(final T v) { - final Set s = new LinkedHashSet(); - for ( final BaseEdge e : outgoingEdgesOf(v) ) { + public Set outgoingVerticesOf(final V v) { + final Set s = new LinkedHashSet(); + for ( final E e : outgoingEdgesOf(v) ) { s.add(getEdgeTarget(e)); } return s; @@ -384,9 +380,9 @@ public class BaseGraph extends DefaultDirectedGraph v */ - public Set incomingVerticesOf(final T v) { - final Set s = new LinkedHashSet(); - for ( final BaseEdge e : incomingEdgesOf(v) ) { + public Set incomingVerticesOf(final V v) { + final Set s = new LinkedHashSet(); + for ( final E e : incomingEdgesOf(v) ) { s.add(getEdgeSource(e)); } return s; @@ -413,15 +409,16 @@ public class BaseGraph extends DefaultDirectedGraph " + getEdgeTarget(edge).toString() + " [" + (edge.getMultiplicity() > 0 && edge.getMultiplicity() <= pruneFactor ? "style=dotted,color=grey," : "") + "label=\"" + edge.getMultiplicity() + "\"];"); + for( final E edge : edgeSet() ) { + graphWriter.println("\t" + getEdgeSource(edge).toString() + " -> " + getEdgeTarget(edge).toString() + " [" + (edge.getMultiplicity() > 0 && edge.getMultiplicity() <= pruneFactor ? "style=dotted,color=grey," : "") + "label=\"" + edge.getDotLabel() + "\"];"); if( edge.isRef() ) { graphWriter.println("\t" + getEdgeSource(edge).toString() + " -> " + getEdgeTarget(edge).toString() + " [color=red];"); } } - for( final T v : vertexSet() ) { - graphWriter.println("\t" + v.toString() + " [label=\"" + new String(getAdditionalSequence(v)) + "\",shape=box]"); + for( final V v : vertexSet() ) { +// graphWriter.println("\t" + v.toString() + " [label=\"" + v + "\",shape=box]"); + graphWriter.println("\t" + v.toString() + " [label=\"" + new String(getAdditionalSequence(v)) + v.additionalInfo() + "\",shape=box]"); } if ( writeHeader ) @@ -439,10 +436,10 @@ public class BaseGraph extends DefaultDirectedGraph edgesToCheck = new HashSet(); + final Set edgesToCheck = new HashSet(); edgesToCheck.addAll(incomingEdgesOf(getReferenceSourceVertex())); while( !edgesToCheck.isEmpty() ) { - final BaseEdge e = edgesToCheck.iterator().next(); + final E e = edgesToCheck.iterator().next(); if( !e.isRef() ) { edgesToCheck.addAll( incomingEdgesOf(getEdgeSource(e)) ); removeEdge(e); @@ -452,7 +449,7 @@ public class BaseGraph extends DefaultDirectedGraph extends DefaultDirectedGraph edgesToRemove = new ArrayList(); - for( final BaseEdge e : edgeSet() ) { - if( e.getMultiplicity() <= pruneFactor && !e.isRef() ) { // remove non-reference edges with weight less than or equal to the pruning factor + final List edgesToRemove = new ArrayList<>(); + for( final E e : edgeSet() ) { + if( e.getPruningMultiplicity() <= pruneFactor && !e.isRef() ) { // remove non-reference edges with weight less than or equal to the pruning factor edgesToRemove.add(e); } } @@ -480,13 +477,25 @@ public class BaseGraph extends DefaultDirectedGraph pruner = new LowWeightChainPruner<>(pruneFactor); + pruner.pruneLowWeightChains(this); + } + /** * Remove all vertices in the graph that have in and out degree of 0 */ protected void removeSingletonOrphanVertices() { // Run through the graph and clean up singular orphaned nodes - final List verticesToRemove = new LinkedList(); - for( final T v : vertexSet() ) { + final List verticesToRemove = new LinkedList<>(); + for( final V v : vertexSet() ) { if( inDegreeOf(v) == 0 && outDegreeOf(v) == 0 ) { verticesToRemove.add(v); } @@ -499,11 +508,11 @@ public class BaseGraph extends DefaultDirectedGraph toRemove = new HashSet(vertexSet()); + final HashSet toRemove = new HashSet<>(vertexSet()); - final T refV = getReferenceSourceVertex(); + final V refV = getReferenceSourceVertex(); if ( refV != null ) { - for ( final T v : new BaseGraphIterator(this, refV, true, true) ) { + for ( final V v : new BaseGraphIterator<>(this, refV, true, true) ) { toRemove.remove(v); } } @@ -524,22 +533,31 @@ public class BaseGraph extends DefaultDirectedGraph onPathFromRefSource = new HashSet(vertexSet().size()); - for ( final T v : new BaseGraphIterator(this, getReferenceSourceVertex(), false, true) ) { + final Set onPathFromRefSource = new HashSet<>(vertexSet().size()); + for ( final V v : new BaseGraphIterator<>(this, getReferenceSourceVertex(), false, true) ) { onPathFromRefSource.add(v); } // get the set of vertices we can reach by going backward from the ref sink - final Set onPathFromRefSink = new HashSet(vertexSet().size()); - for ( final T v : new BaseGraphIterator(this, getReferenceSinkVertex(), true, false) ) { + final Set onPathFromRefSink = new HashSet<>(vertexSet().size()); + for ( final V v : new BaseGraphIterator<>(this, getReferenceSinkVertex(), true, false) ) { onPathFromRefSink.add(v); } // we want to remove anything that's not in both the sink and source sets - final Set verticesToRemove = new HashSet(vertexSet()); + final Set verticesToRemove = new HashSet<>(vertexSet()); onPathFromRefSource.retainAll(onPathFromRefSink); verticesToRemove.removeAll(onPathFromRefSource); removeAllVertices(verticesToRemove); + + // simple santity checks that this algorithm is working. + if ( getSinks().size() > 1 ) { + throw new IllegalStateException("Should have eliminated all but the reference sink, but found " + getSinks()); + } + + if ( getSources().size() > 1 ) { + throw new IllegalStateException("Should have eliminated all but the reference source, but found " + getSources()); + } } /** @@ -555,11 +573,11 @@ public class BaseGraph extends DefaultDirectedGraph the type of the nodes in those graphs * @return true if g1 and g2 are equals */ - public static boolean graphEquals(final BaseGraph g1, BaseGraph g2) { + public static boolean graphEquals(final BaseGraph g1, BaseGraph g2) { final Set vertices1 = g1.vertexSet(); final Set vertices2 = g2.vertexSet(); - final Set edges1 = g1.edgeSet(); - final Set edges2 = g2.edgeSet(); + final Set edges1 = g1.edgeSet(); + final Set edges2 = g2.edgeSet(); if ( vertices1.size() != vertices2.size() || edges1.size() != edges2.size() ) return false; @@ -571,29 +589,35 @@ public class BaseGraph extends DefaultDirectedGraph graph2 ) { + return (this.getEdgeSource(edge1).seqEquals(graph2.getEdgeSource(edge2))) && (this.getEdgeTarget(edge1).seqEquals(graph2.getEdgeTarget(edge2))); + } + + /** * Get the incoming edge of v. Requires that there be only one such edge or throws an error * @param v our vertex * @return the single incoming edge to v, or null if none exists */ - public BaseEdge incomingEdgeOf(final T v) { + public E incomingEdgeOf(final V v) { return getSingletonEdge(incomingEdgesOf(v)); } @@ -602,7 +626,7 @@ public class BaseGraph extends DefaultDirectedGraph extends DefaultDirectedGraph edges) { + private E getSingletonEdge(final Collection edges) { if ( edges.size() > 1 ) throw new IllegalArgumentException("Cannot get a single incoming edge for a vertex with multiple incoming edges " + edges); return edges.isEmpty() ? null : edges.iterator().next(); } @@ -625,12 +649,19 @@ public class BaseGraph extends DefaultDirectedGraph { +public final class DeBruijnGraph extends BaseGraph { + /** + * Edge factory that creates non-reference multiplicity 1 edges + */ + private static class MyEdgeFactory implements EdgeFactory { + @Override + public BaseEdge createEdge(DeBruijnVertex sourceVertex, DeBruijnVertex targetVertex) { + return new BaseEdge(false, 1); + } + } + /** * Create an empty DeBruijnGraph with default kmer size */ public DeBruijnGraph() { - super(); + this(11); } /** @@ -71,7 +82,7 @@ public final class DeBruijnGraph extends BaseGraph { * @param kmerSize kmer size, must be >= 1 */ public DeBruijnGraph(int kmerSize) { - super(kmerSize); + super(kmerSize, new MyEdgeFactory()); } /** diff --git a/protected/java/src/org/broadinstitute/sting/gatk/walkers/haplotypecaller/graphs/DeBruijnVertex.java b/protected/java/src/org/broadinstitute/sting/gatk/walkers/haplotypecaller/graphs/DeBruijnVertex.java index c240949d9..4d9441efe 100644 --- a/protected/java/src/org/broadinstitute/sting/gatk/walkers/haplotypecaller/graphs/DeBruijnVertex.java +++ b/protected/java/src/org/broadinstitute/sting/gatk/walkers/haplotypecaller/graphs/DeBruijnVertex.java @@ -54,7 +54,7 @@ import com.google.java.contract.Ensures; * User: ebanks, mdepristo * Date: Mar 23, 2011 */ -public final class DeBruijnVertex extends BaseVertex { +public class DeBruijnVertex extends BaseVertex { private final static byte[][] sufficesAsByteArray = new byte[256][]; static { for ( int i = 0; i < sufficesAsByteArray.length; i++ ) diff --git a/protected/java/src/org/broadinstitute/sting/gatk/walkers/haplotypecaller/graphs/GraphUtils.java b/protected/java/src/org/broadinstitute/sting/gatk/walkers/haplotypecaller/graphs/GraphUtils.java index 30c5be190..4aa6047a9 100644 --- a/protected/java/src/org/broadinstitute/sting/gatk/walkers/haplotypecaller/graphs/GraphUtils.java +++ b/protected/java/src/org/broadinstitute/sting/gatk/walkers/haplotypecaller/graphs/GraphUtils.java @@ -48,6 +48,7 @@ package org.broadinstitute.sting.gatk.walkers.haplotypecaller.graphs; import com.google.java.contract.Ensures; import com.google.java.contract.Requires; +import org.broadinstitute.sting.utils.collections.PrimitivePair; import java.util.ArrayList; import java.util.Collection; @@ -60,7 +61,7 @@ import java.util.List; * Date: 3/25/13 * Time: 9:42 PM */ -final class GraphUtils { +final public class GraphUtils { private GraphUtils() {} /** @@ -135,4 +136,49 @@ final class GraphUtils { return min; } + /** + * Find the ending position of the longest uniquely matching + * run of bases of kmer in seq. + * + * for example, if seq = ACGT and kmer is NAC, this function returns 1,2 as we have the following + * match: + * + * 0123 + * .ACGT + * NAC.. + * + * @param seq a non-null sequence of bytes + * @param kmer a non-null kmer + * @return the ending position and length where kmer matches uniquely in sequence, or null if no + * unique longest match can be found + */ + public static PrimitivePair.Int findLongestUniqueSuffixMatch(final byte[] seq, final byte[] kmer) { + int longestPos = -1; + int length = 0; + boolean foundDup = false; + + for ( int i = 0; i < seq.length; i++ ) { + final int matchSize = longestSuffixMatch(seq, kmer, i); + if ( matchSize > length ) { + longestPos = i; + length = matchSize; + foundDup = false; + } else if ( matchSize == length ) { + foundDup = true; + } + } + + return foundDup ? null : new PrimitivePair.Int(longestPos, length); + } + + private static int longestSuffixMatch(final byte[] seq, final byte[] kmer, final int seqStart) { + for ( int len = 1; len <= kmer.length; len++ ) { + final int seqI = seqStart - len + 1; + final int kmerI = kmer.length - len; + if ( seqI < 0 || seq[seqI] != kmer[kmerI] ) { + return len - 1; + } + } + return kmer.length; + } } diff --git a/protected/java/src/org/broadinstitute/sting/gatk/walkers/haplotypecaller/graphs/KBestPaths.java b/protected/java/src/org/broadinstitute/sting/gatk/walkers/haplotypecaller/graphs/KBestPaths.java index 466148588..3ba85dd92 100644 --- a/protected/java/src/org/broadinstitute/sting/gatk/walkers/haplotypecaller/graphs/KBestPaths.java +++ b/protected/java/src/org/broadinstitute/sting/gatk/walkers/haplotypecaller/graphs/KBestPaths.java @@ -59,7 +59,7 @@ import java.util.*; * User: ebanks, rpoplin, mdepristo * Date: Mar 23, 2011 */ -public class KBestPaths { +public class KBestPaths { private final boolean allowCycles; /** @@ -93,7 +93,7 @@ public class KBestPaths { /** * @see #getKBestPaths(BaseGraph, int) retriving the best 1000 paths */ - public List> getKBestPaths( final BaseGraph graph ) { + public List> getKBestPaths( final BaseGraph graph ) { return getKBestPaths(graph, 1000); } @@ -101,28 +101,28 @@ public class KBestPaths { * @see #getKBestPaths(BaseGraph, int, java.util.Set, java.util.Set) retriving the first 1000 paths * starting from all source vertices and ending with all sink vertices */ - public List> getKBestPaths( final BaseGraph graph, final int k ) { + public List> getKBestPaths( final BaseGraph graph, final int k ) { return getKBestPaths(graph, k, graph.getSources(), graph.getSinks()); } /** * @see #getKBestPaths(BaseGraph, int, java.util.Set, java.util.Set) with k=1000 */ - public List> getKBestPaths( final BaseGraph graph, final Set sources, final Set sinks ) { + public List> getKBestPaths( final BaseGraph graph, final Set sources, final Set sinks ) { return getKBestPaths(graph, 1000, sources, sinks); } /** * @see #getKBestPaths(BaseGraph, int, java.util.Set, java.util.Set) with k=1000 */ - public List> getKBestPaths( final BaseGraph graph, final T source, final T sink ) { + public List> getKBestPaths( final BaseGraph graph, final T source, final T sink ) { return getKBestPaths(graph, 1000, source, sink); } /** * @see #getKBestPaths(BaseGraph, int, java.util.Set, java.util.Set) with singleton source and sink sets */ - public List> getKBestPaths( final BaseGraph graph, final int k, final T source, final T sink ) { + public List> getKBestPaths( final BaseGraph graph, final int k, final T source, final T sink ) { return getKBestPaths(graph, k, Collections.singleton(source), Collections.singleton(sink)); } @@ -136,20 +136,20 @@ public class KBestPaths { * @return a list with at most k top-scoring paths from the graph */ @Ensures({"result != null", "result.size() <= k"}) - public List> getKBestPaths( final BaseGraph graph, final int k, final Set sources, final Set sinks ) { + public List> getKBestPaths( final BaseGraph graph, final int k, final Set sources, final Set sinks ) { if( graph == null ) { throw new IllegalArgumentException("Attempting to traverse a null graph."); } // a min max queue that will collect the best k paths - final MinMaxPriorityQueue> bestPaths = MinMaxPriorityQueue.orderedBy(new PathComparatorTotalScore()).maximumSize(k).create(); + final MinMaxPriorityQueue> bestPaths = MinMaxPriorityQueue.orderedBy(new PathComparatorTotalScore()).maximumSize(k).create(); // run a DFS for best paths for ( final T source : sources ) { - final Path startingPath = new Path(source, graph); + final Path startingPath = new Path(source, graph); findBestPaths(startingPath, sinks, bestPaths, new MyInt()); } // the MinMaxPriorityQueue iterator returns items in an arbitrary order, so we need to sort the final result - final List> toReturn = new ArrayList>(bestPaths); + final List> toReturn = new ArrayList>(bestPaths); Collections.sort(toReturn, new PathComparatorTotalScore()); return toReturn; } @@ -161,21 +161,21 @@ public class KBestPaths { * @param bestPaths a path to collect completed paths. * @param n used to limit the search by tracking the number of vertices visited across all paths */ - private void findBestPaths( final Path path, final Set sinks, final Collection> bestPaths, final MyInt n ) { + private void findBestPaths( final Path path, final Set sinks, final Collection> bestPaths, final MyInt n ) { if ( sinks.contains(path.getLastVertex())) { bestPaths.add(path); } else if( n.val > 10000 ) { // do nothing, just return, as we've done too much work already } else { // recursively run DFS - final ArrayList edgeArrayList = new ArrayList(path.getOutgoingEdgesOfLastVertex()); + final ArrayList edgeArrayList = new ArrayList(path.getOutgoingEdgesOfLastVertex()); Collections.sort(edgeArrayList, new BaseEdge.EdgeWeightComparator()); - for ( final BaseEdge edge : edgeArrayList ) { + for ( final E edge : edgeArrayList ) { final T target = path.getGraph().getEdgeTarget(edge); // make sure the edge is not already in the path final boolean alreadyVisited = allowCycles ? path.containsEdge(edge) : path.containsVertex(target); if ( ! alreadyVisited ) { - final Path newPath = new Path(path, edge); + final Path newPath = new Path(path, edge); n.val++; findBestPaths(newPath, sinks, bestPaths, n); } diff --git a/protected/java/src/org/broadinstitute/sting/gatk/walkers/haplotypecaller/graphs/LowWeightChainPruner.java b/protected/java/src/org/broadinstitute/sting/gatk/walkers/haplotypecaller/graphs/LowWeightChainPruner.java new file mode 100644 index 000000000..7327b5736 --- /dev/null +++ b/protected/java/src/org/broadinstitute/sting/gatk/walkers/haplotypecaller/graphs/LowWeightChainPruner.java @@ -0,0 +1,170 @@ +/* +* By downloading the PROGRAM you agree to the following terms of use: +* +* BROAD INSTITUTE - SOFTWARE LICENSE AGREEMENT - FOR ACADEMIC NON-COMMERCIAL RESEARCH PURPOSES ONLY +* +* This Agreement is made between the Broad Institute, Inc. with a principal address at 7 Cambridge Center, Cambridge, MA 02142 (BROAD) and the LICENSEE and is effective at the date the downloading is completed (EFFECTIVE DATE). +* +* WHEREAS, LICENSEE desires to license the PROGRAM, as defined hereinafter, and BROAD wishes to have this PROGRAM utilized in the public interest, subject only to the royalty-free, nonexclusive, nontransferable license rights of the United States Government pursuant to 48 CFR 52.227-14; and +* WHEREAS, LICENSEE desires to license the PROGRAM and BROAD desires to grant a license on the following terms and conditions. +* NOW, THEREFORE, in consideration of the promises and covenants made herein, the parties hereto agree as follows: +* +* 1. DEFINITIONS +* 1.1 PROGRAM shall mean copyright in the object code and source code known as GATK2 and related documentation, if any, as they exist on the EFFECTIVE DATE and can be downloaded from http://www.broadinstitute/GATK on the EFFECTIVE DATE. +* +* 2. LICENSE +* 2.1 Grant. Subject to the terms of this Agreement, BROAD hereby grants to LICENSEE, solely for academic non-commercial research purposes, a non-exclusive, non-transferable license to: (a) download, execute and display the PROGRAM and (b) create bug fixes and modify the PROGRAM. +* The LICENSEE may apply the PROGRAM in a pipeline to data owned by users other than the LICENSEE and provide these users the results of the PROGRAM provided LICENSEE does so for academic non-commercial purposes only. For clarification purposes, academic sponsored research is not a commercial use under the terms of this Agreement. +* 2.2 No Sublicensing or Additional Rights. LICENSEE shall not sublicense or distribute the PROGRAM, in whole or in part, without prior written permission from BROAD. LICENSEE shall ensure that all of its users agree to the terms of this Agreement. LICENSEE further agrees that it shall not put the PROGRAM on a network, server, or other similar technology that may be accessed by anyone other than the LICENSEE and its employees and users who have agreed to the terms of this agreement. +* 2.3 License Limitations. Nothing in this Agreement shall be construed to confer any rights upon LICENSEE by implication, estoppel, or otherwise to any computer software, trademark, intellectual property, or patent rights of BROAD, or of any other entity, except as expressly granted herein. LICENSEE agrees that the PROGRAM, in whole or part, shall not be used for any commercial purpose, including without limitation, as the basis of a commercial software or hardware product or to provide services. LICENSEE further agrees that the PROGRAM shall not be copied or otherwise adapted in order to circumvent the need for obtaining a license for use of the PROGRAM. +* +* 3. OWNERSHIP OF INTELLECTUAL PROPERTY +* LICENSEE acknowledges that title to the PROGRAM shall remain with BROAD. The PROGRAM is marked with the following BROAD copyright notice and notice of attribution to contributors. LICENSEE shall retain such notice on all copies. LICENSEE agrees to include appropriate attribution if any results obtained from use of the PROGRAM are included in any publication. +* Copyright 2012 Broad Institute, Inc. +* Notice of attribution: The GATK2 program was made available through the generosity of Medical and Population Genetics program at the Broad Institute, Inc. +* LICENSEE shall not use any trademark or trade name of BROAD, or any variation, adaptation, or abbreviation, of such marks or trade names, or any names of officers, faculty, students, employees, or agents of BROAD except as states above for attribution purposes. +* +* 4. INDEMNIFICATION +* LICENSEE shall indemnify, defend, and hold harmless BROAD, and their respective officers, faculty, students, employees, associated investigators and agents, and their respective successors, heirs and assigns, (Indemnitees), against any liability, damage, loss, or expense (including reasonable attorneys fees and expenses) incurred by or imposed upon any of the Indemnitees in connection with any claims, suits, actions, demands or judgments arising out of any theory of liability (including, without limitation, actions in the form of tort, warranty, or strict liability and regardless of whether such action has any factual basis) pursuant to any right or license granted under this Agreement. +* +* 5. NO REPRESENTATIONS OR WARRANTIES +* THE PROGRAM IS DELIVERED AS IS. BROAD MAKES NO REPRESENTATIONS OR WARRANTIES OF ANY KIND CONCERNING THE PROGRAM OR THE COPYRIGHT, EXPRESS OR IMPLIED, INCLUDING, WITHOUT LIMITATION, WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NONINFRINGEMENT, OR THE ABSENCE OF LATENT OR OTHER DEFECTS, WHETHER OR NOT DISCOVERABLE. BROAD EXTENDS NO WARRANTIES OF ANY KIND AS TO PROGRAM CONFORMITY WITH WHATEVER USER MANUALS OR OTHER LITERATURE MAY BE ISSUED FROM TIME TO TIME. +* IN NO EVENT SHALL BROAD OR ITS RESPECTIVE DIRECTORS, OFFICERS, EMPLOYEES, AFFILIATED INVESTIGATORS AND AFFILIATES BE LIABLE FOR INCIDENTAL OR CONSEQUENTIAL DAMAGES OF ANY KIND, INCLUDING, WITHOUT LIMITATION, ECONOMIC DAMAGES OR INJURY TO PROPERTY AND LOST PROFITS, REGARDLESS OF WHETHER BROAD SHALL BE ADVISED, SHALL HAVE OTHER REASON TO KNOW, OR IN FACT SHALL KNOW OF THE POSSIBILITY OF THE FOREGOING. +* +* 6. ASSIGNMENT +* This Agreement is personal to LICENSEE and any rights or obligations assigned by LICENSEE without the prior written consent of BROAD shall be null and void. +* +* 7. MISCELLANEOUS +* 7.1 Export Control. LICENSEE gives assurance that it will comply with all United States export control laws and regulations controlling the export of the PROGRAM, including, without limitation, all Export Administration Regulations of the United States Department of Commerce. Among other things, these laws and regulations prohibit, or require a license for, the export of certain types of software to specified countries. +* 7.2 Termination. LICENSEE shall have the right to terminate this Agreement for any reason upon prior written notice to BROAD. If LICENSEE breaches any provision hereunder, and fails to cure such breach within thirty (30) days, BROAD may terminate this Agreement immediately. Upon termination, LICENSEE shall provide BROAD with written assurance that the original and all copies of the PROGRAM have been destroyed, except that, upon prior written authorization from BROAD, LICENSEE may retain a copy for archive purposes. +* 7.3 Survival. The following provisions shall survive the expiration or termination of this Agreement: Articles 1, 3, 4, 5 and Sections 2.2, 2.3, 7.3, and 7.4. +* 7.4 Notice. Any notices under this Agreement shall be in writing, shall specifically refer to this Agreement, and shall be sent by hand, recognized national overnight courier, confirmed facsimile transmission, confirmed electronic mail, or registered or certified mail, postage prepaid, return receipt requested. All notices under this Agreement shall be deemed effective upon receipt. +* 7.5 Amendment and Waiver; Entire Agreement. This Agreement may be amended, supplemented, or otherwise modified only by means of a written instrument signed by all parties. Any waiver of any rights or failure to act in a specific instance shall relate only to such instance and shall not be construed as an agreement to waive any rights or fail to act in any other instance, whether or not similar. This Agreement constitutes the entire agreement among the parties with respect to its subject matter and supersedes prior agreements or understandings between the parties relating to its subject matter. +* 7.6 Binding Effect; Headings. This Agreement shall be binding upon and inure to the benefit of the parties and their respective permitted successors and assigns. All headings are for convenience only and shall not affect the meaning of any provision of this Agreement. +* 7.7 Governing Law. This Agreement shall be construed, governed, interpreted and applied in accordance with the internal laws of the Commonwealth of Massachusetts, U.S.A., without regard to conflict of laws principles. +*/ + +package org.broadinstitute.sting.gatk.walkers.haplotypecaller.graphs; + +import java.util.*; + +/** + /** + * Prune all chains from this graph where all edges in the path have multiplicity <= pruneFactor + * + * Unlike pruneGraph, this function will remove only linear chains in the graph where all edges have weight <= pruneFactor. + * + * For A -[1]> B -[1]> C -[1]> D would be removed with pruneFactor 1 + * but A -[1]> B -[2]> C -[1]> D would not be because the linear chain includes an edge with weight >= 2 + * + * User: depristo + * Date: 5/2/13 + * Time: 10:38 AM + */ +public class LowWeightChainPruner { + private final int pruneFactor; + + public LowWeightChainPruner(int pruneFactor) { + if ( pruneFactor < 0 ) throw new IllegalArgumentException("pruneFactor must be >= 0 but got " + pruneFactor); + this.pruneFactor = pruneFactor; + } + + /** + * Prune graph + * @param graph the graph to prune + */ + public void pruneLowWeightChains(final BaseGraph graph) { + if ( graph == null ) throw new IllegalArgumentException("Graph cannot be null"); + + if ( pruneFactor > 0 ) { + final Set edgesToKeep = new LinkedHashSet<>(); + + for ( final Path linearChain : getLinearChains(graph) ) { + if( mustBeKeep(linearChain, pruneFactor) ) { + // we must keep edges in any path that contains a reference edge or an edge with weight > pruneFactor + edgesToKeep.addAll(linearChain.getEdges()); + } + } + + // we want to remove all edges not in the keep set + final Set edgesToRemove = new HashSet<>(graph.edgeSet()); + edgesToRemove.removeAll(edgesToKeep); + graph.removeAllEdges(edgesToRemove); + + graph.removeSingletonOrphanVertices(); + } + } + + /** + * Get the maximum pruning multiplicity seen on any edge in this graph + * @return an integer > 0 + */ + private boolean mustBeKeep(final Path path, final int pruneFactor) { + for ( final E edge : path.getEdges() ) { + if ( edge.getPruningMultiplicity() >= pruneFactor || edge.isRef() ) + return true; + } + return false; + } + + /** + * Get all of the linear chains in graph + * + * A linear chain is a series of vertices that start from either a source of a vertex with + * out-degree > 1 and extend through all vertices accessible via an outgoing edge from this + * vertex that have in == 1 and out degree of 0 or 1. + * + * @param graph the graph + * @return a non-null collection of paths in graph + */ + protected final Collection> getLinearChains(final BaseGraph graph) { + final Set chainStarts = new LinkedHashSet<>(); + + for ( final V v : graph.vertexSet() ) { + // we want a list of all chain start vertices. These are all vertices with out + // degree > 1, or all source vertices. + final int outDegree = graph.outDegreeOf(v); + final int inDegree = graph.inDegreeOf(v); + if ( outDegree > 1 || inDegree > 1 || (inDegree == 0 && outDegree > 0)) // don't add isolated vertices + chainStarts.add(v); + } + + // must be after since we can add duplicate starts in the above finding algorithm + final List> linearChains = new LinkedList<>(); + for ( final V chainStart : chainStarts ) { + for ( final E outEdge : graph.outgoingEdgesOf(chainStart) ) { + // these chains are composed of the starts + their next vertices + linearChains.add(extendLinearChain(new Path<>(new Path<>(chainStart, graph), outEdge))); + } + } + + return linearChains; + } + + /** + * Extend path while the last vertex has in and out degrees of 1 or 0 + * @param path the path to extend + * @return a fully extended linear path + */ + protected final Path extendLinearChain(final Path path) { + final V last = path.getLastVertex(); + final Set outEdges = path.getGraph().outgoingEdgesOf(last); + + final int outDegree = outEdges.size(); + final int inDegree = path.getGraph().inDegreeOf(last); + + if ( outDegree != 1 || inDegree > 1 ) { + // out next vertex has multiple outgoing edges, so we are done with the linear path + return path; + } else { + final V next = path.getGraph().getEdgeTarget(outEdges.iterator().next()); + if ( path.containsVertex(next) ) { + // we are done if the path contains a cycle + return path; + } else { + // we now know that last has outdegree == 1, so we keep extending the chain + return extendLinearChain(new Path<>(path, outEdges.iterator().next())); + } + } + } +} diff --git a/protected/java/src/org/broadinstitute/sting/gatk/walkers/haplotypecaller/graphs/MultiSampleEdge.java b/protected/java/src/org/broadinstitute/sting/gatk/walkers/haplotypecaller/graphs/MultiSampleEdge.java new file mode 100644 index 000000000..c1937e5c8 --- /dev/null +++ b/protected/java/src/org/broadinstitute/sting/gatk/walkers/haplotypecaller/graphs/MultiSampleEdge.java @@ -0,0 +1,123 @@ +/* +* By downloading the PROGRAM you agree to the following terms of use: +* +* BROAD INSTITUTE - SOFTWARE LICENSE AGREEMENT - FOR ACADEMIC NON-COMMERCIAL RESEARCH PURPOSES ONLY +* +* This Agreement is made between the Broad Institute, Inc. with a principal address at 7 Cambridge Center, Cambridge, MA 02142 (BROAD) and the LICENSEE and is effective at the date the downloading is completed (EFFECTIVE DATE). +* +* WHEREAS, LICENSEE desires to license the PROGRAM, as defined hereinafter, and BROAD wishes to have this PROGRAM utilized in the public interest, subject only to the royalty-free, nonexclusive, nontransferable license rights of the United States Government pursuant to 48 CFR 52.227-14; and +* WHEREAS, LICENSEE desires to license the PROGRAM and BROAD desires to grant a license on the following terms and conditions. +* NOW, THEREFORE, in consideration of the promises and covenants made herein, the parties hereto agree as follows: +* +* 1. DEFINITIONS +* 1.1 PROGRAM shall mean copyright in the object code and source code known as GATK2 and related documentation, if any, as they exist on the EFFECTIVE DATE and can be downloaded from http://www.broadinstitute/GATK on the EFFECTIVE DATE. +* +* 2. LICENSE +* 2.1 Grant. Subject to the terms of this Agreement, BROAD hereby grants to LICENSEE, solely for academic non-commercial research purposes, a non-exclusive, non-transferable license to: (a) download, execute and display the PROGRAM and (b) create bug fixes and modify the PROGRAM. +* The LICENSEE may apply the PROGRAM in a pipeline to data owned by users other than the LICENSEE and provide these users the results of the PROGRAM provided LICENSEE does so for academic non-commercial purposes only. For clarification purposes, academic sponsored research is not a commercial use under the terms of this Agreement. +* 2.2 No Sublicensing or Additional Rights. LICENSEE shall not sublicense or distribute the PROGRAM, in whole or in part, without prior written permission from BROAD. LICENSEE shall ensure that all of its users agree to the terms of this Agreement. LICENSEE further agrees that it shall not put the PROGRAM on a network, server, or other similar technology that may be accessed by anyone other than the LICENSEE and its employees and users who have agreed to the terms of this agreement. +* 2.3 License Limitations. Nothing in this Agreement shall be construed to confer any rights upon LICENSEE by implication, estoppel, or otherwise to any computer software, trademark, intellectual property, or patent rights of BROAD, or of any other entity, except as expressly granted herein. LICENSEE agrees that the PROGRAM, in whole or part, shall not be used for any commercial purpose, including without limitation, as the basis of a commercial software or hardware product or to provide services. LICENSEE further agrees that the PROGRAM shall not be copied or otherwise adapted in order to circumvent the need for obtaining a license for use of the PROGRAM. +* +* 3. OWNERSHIP OF INTELLECTUAL PROPERTY +* LICENSEE acknowledges that title to the PROGRAM shall remain with BROAD. The PROGRAM is marked with the following BROAD copyright notice and notice of attribution to contributors. LICENSEE shall retain such notice on all copies. LICENSEE agrees to include appropriate attribution if any results obtained from use of the PROGRAM are included in any publication. +* Copyright 2012 Broad Institute, Inc. +* Notice of attribution: The GATK2 program was made available through the generosity of Medical and Population Genetics program at the Broad Institute, Inc. +* LICENSEE shall not use any trademark or trade name of BROAD, or any variation, adaptation, or abbreviation, of such marks or trade names, or any names of officers, faculty, students, employees, or agents of BROAD except as states above for attribution purposes. +* +* 4. INDEMNIFICATION +* LICENSEE shall indemnify, defend, and hold harmless BROAD, and their respective officers, faculty, students, employees, associated investigators and agents, and their respective successors, heirs and assigns, (Indemnitees), against any liability, damage, loss, or expense (including reasonable attorneys fees and expenses) incurred by or imposed upon any of the Indemnitees in connection with any claims, suits, actions, demands or judgments arising out of any theory of liability (including, without limitation, actions in the form of tort, warranty, or strict liability and regardless of whether such action has any factual basis) pursuant to any right or license granted under this Agreement. +* +* 5. NO REPRESENTATIONS OR WARRANTIES +* THE PROGRAM IS DELIVERED AS IS. BROAD MAKES NO REPRESENTATIONS OR WARRANTIES OF ANY KIND CONCERNING THE PROGRAM OR THE COPYRIGHT, EXPRESS OR IMPLIED, INCLUDING, WITHOUT LIMITATION, WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NONINFRINGEMENT, OR THE ABSENCE OF LATENT OR OTHER DEFECTS, WHETHER OR NOT DISCOVERABLE. BROAD EXTENDS NO WARRANTIES OF ANY KIND AS TO PROGRAM CONFORMITY WITH WHATEVER USER MANUALS OR OTHER LITERATURE MAY BE ISSUED FROM TIME TO TIME. +* IN NO EVENT SHALL BROAD OR ITS RESPECTIVE DIRECTORS, OFFICERS, EMPLOYEES, AFFILIATED INVESTIGATORS AND AFFILIATES BE LIABLE FOR INCIDENTAL OR CONSEQUENTIAL DAMAGES OF ANY KIND, INCLUDING, WITHOUT LIMITATION, ECONOMIC DAMAGES OR INJURY TO PROPERTY AND LOST PROFITS, REGARDLESS OF WHETHER BROAD SHALL BE ADVISED, SHALL HAVE OTHER REASON TO KNOW, OR IN FACT SHALL KNOW OF THE POSSIBILITY OF THE FOREGOING. +* +* 6. ASSIGNMENT +* This Agreement is personal to LICENSEE and any rights or obligations assigned by LICENSEE without the prior written consent of BROAD shall be null and void. +* +* 7. MISCELLANEOUS +* 7.1 Export Control. LICENSEE gives assurance that it will comply with all United States export control laws and regulations controlling the export of the PROGRAM, including, without limitation, all Export Administration Regulations of the United States Department of Commerce. Among other things, these laws and regulations prohibit, or require a license for, the export of certain types of software to specified countries. +* 7.2 Termination. LICENSEE shall have the right to terminate this Agreement for any reason upon prior written notice to BROAD. If LICENSEE breaches any provision hereunder, and fails to cure such breach within thirty (30) days, BROAD may terminate this Agreement immediately. Upon termination, LICENSEE shall provide BROAD with written assurance that the original and all copies of the PROGRAM have been destroyed, except that, upon prior written authorization from BROAD, LICENSEE may retain a copy for archive purposes. +* 7.3 Survival. The following provisions shall survive the expiration or termination of this Agreement: Articles 1, 3, 4, 5 and Sections 2.2, 2.3, 7.3, and 7.4. +* 7.4 Notice. Any notices under this Agreement shall be in writing, shall specifically refer to this Agreement, and shall be sent by hand, recognized national overnight courier, confirmed facsimile transmission, confirmed electronic mail, or registered or certified mail, postage prepaid, return receipt requested. All notices under this Agreement shall be deemed effective upon receipt. +* 7.5 Amendment and Waiver; Entire Agreement. This Agreement may be amended, supplemented, or otherwise modified only by means of a written instrument signed by all parties. Any waiver of any rights or failure to act in a specific instance shall relate only to such instance and shall not be construed as an agreement to waive any rights or fail to act in any other instance, whether or not similar. This Agreement constitutes the entire agreement among the parties with respect to its subject matter and supersedes prior agreements or understandings between the parties relating to its subject matter. +* 7.6 Binding Effect; Headings. This Agreement shall be binding upon and inure to the benefit of the parties and their respective permitted successors and assigns. All headings are for convenience only and shall not affect the meaning of any provision of this Agreement. +* 7.7 Governing Law. This Agreement shall be construed, governed, interpreted and applied in accordance with the internal laws of the Commonwealth of Massachusetts, U.S.A., without regard to conflict of laws principles. +*/ + +package org.broadinstitute.sting.gatk.walkers.haplotypecaller.graphs; + +/** + * edge class for connecting nodes in the graph that tracks some per-sample information + * + * This class extends BaseEdge with the additional functionality of tracking the maximum + * multiplicity seen within any single sample. The workflow for using this class is: + * + * MultiSampleEdge e = new MultiSampleEdge(ref, 1) + * e.incMultiplicity(1) // total is 2, per sample is 2, max per sample is 1 + * e.getPruningMultiplicity() // = 1 + * e.flushSingleSampleMultiplicity() // total is 2, per sample is 0, max per sample is 2 + * e.getPruningMultiplicity() // = 2 + * e.incMultiplicity(3) // total is 5, per sample is 3, max per sample is 2 + * e.getPruningMultiplicity() // = 2 + * e.flushSingleSampleMultiplicity() // total is 5, per sample is 0, max per sample is 3 + * e.getPruningMultiplicity() // = 3 + */ +public class MultiSampleEdge extends BaseEdge { + private int maxSingleSampleMultiplicity, currentSingleSampleMultiplicity; + + /** + * Create a new MultiSampleEdge with weight multiplicity and, if isRef == true, indicates a path through the reference + * + * @param isRef indicates whether this edge is a path through the reference + * @param multiplicity the number of observations of this edge in this sample + */ + public MultiSampleEdge(final boolean isRef, final int multiplicity) { + super(isRef, multiplicity); + maxSingleSampleMultiplicity = multiplicity; + currentSingleSampleMultiplicity = multiplicity; + } + + @Override + public MultiSampleEdge copy() { + return new MultiSampleEdge(isRef(), getMultiplicity()); // TODO -- should I copy values for other features? + } + + /** + * update the max single sample multiplicity based on the current single sample multiplicity, and + * reset the current single sample multiplicity to 0. + */ + public void flushSingleSampleMultiplicity() { + if ( currentSingleSampleMultiplicity > maxSingleSampleMultiplicity ) + maxSingleSampleMultiplicity = currentSingleSampleMultiplicity; + currentSingleSampleMultiplicity = 0; + } + + @Override + public void incMultiplicity(final int incr) { + super.incMultiplicity(incr); + currentSingleSampleMultiplicity += incr; + } + + @Override + public int getPruningMultiplicity() { + return getMaxSingleSampleMultiplicity(); + } + + @Override + public String getDotLabel() { + return super.getDotLabel() + "/" + getMaxSingleSampleMultiplicity(); + } + + /** + * Get the maximum multiplicity for this edge seen in any single sample + * @return an integer >= 0 + */ + public int getMaxSingleSampleMultiplicity() { + return maxSingleSampleMultiplicity; + } + + /** only provided for testing purposes */ + protected int getCurrentSingleSampleMultiplicity() { + return currentSingleSampleMultiplicity; + } +} diff --git a/protected/java/src/org/broadinstitute/sting/gatk/walkers/haplotypecaller/graphs/Path.java b/protected/java/src/org/broadinstitute/sting/gatk/walkers/haplotypecaller/graphs/Path.java index 47676a498..a07b98bb6 100644 --- a/protected/java/src/org/broadinstitute/sting/gatk/walkers/haplotypecaller/graphs/Path.java +++ b/protected/java/src/org/broadinstitute/sting/gatk/walkers/haplotypecaller/graphs/Path.java @@ -52,8 +52,8 @@ import net.sf.samtools.Cigar; import net.sf.samtools.CigarElement; import net.sf.samtools.CigarOperator; import org.apache.commons.lang.ArrayUtils; -import org.broadinstitute.sting.utils.smithwaterman.Parameters; -import org.broadinstitute.sting.utils.smithwaterman.SWPairwiseAlignment; +import org.apache.log4j.Logger; +import org.broadinstitute.sting.utils.smithwaterman.*; import org.broadinstitute.sting.utils.sam.AlignmentUtils; import java.util.*; @@ -68,40 +68,39 @@ import java.util.*; * Time: 2:34 PM * */ -public class Path { - private final static int MAX_CIGAR_ELEMENTS_BEFORE_FAILING_SW = 20; +public class Path { + private final static String SW_PAD = "NNNNNNNNNN"; + private final static Logger logger = Logger.getLogger(Path.class); // the last vertex seen in the path private final T lastVertex; // the list of edges comprising the path - private Set edgesAsSet = null; - private final LinkedList edgesInOrder; + private Set edgesAsSet = null; + private final LinkedList edgesInOrder; // the scores for the path private final int totalScore; // the graph from which this path originated - private final BaseGraph graph; + private final BaseGraph graph; // used in the bubble state machine to apply Smith-Waterman to the bubble sequence // these values were chosen via optimization against the NA12878 knowledge base public static final Parameters NEW_SW_PARAMETERS = new Parameters(20.0, -15.0, -26.0, -1.1); - private static final byte[] STARTING_SW_ANCHOR_BYTES = "XXXXXXXXX".getBytes(); - /** * Create a new Path containing no edges and starting at initialVertex * @param initialVertex the starting vertex of the path * @param graph the graph this path with follow through */ - public Path(final T initialVertex, final BaseGraph graph) { + public Path(final T initialVertex, final BaseGraph graph) { if ( initialVertex == null ) throw new IllegalArgumentException("initialVertex cannot be null"); if ( graph == null ) throw new IllegalArgumentException("graph cannot be null"); if ( ! graph.containsVertex(initialVertex) ) throw new IllegalArgumentException("Vertex " + initialVertex + " must be part of graph " + graph); lastVertex = initialVertex; - edgesInOrder = new LinkedList(); + edgesInOrder = new LinkedList(); totalScore = 0; this.graph = graph; } @@ -109,10 +108,10 @@ public class Path { /** * Convenience constructor for testing that creates a path through vertices in graph */ - protected static Path makePath(final List vertices, final BaseGraph graph) { - Path path = new Path(vertices.get(0), graph); + protected static Path makePath(final List vertices, final BaseGraph graph) { + Path path = new Path(vertices.get(0), graph); for ( int i = 1; i < vertices.size(); i++ ) - path = new Path(path, graph.getEdge(path.lastVertex, vertices.get(i))); + path = new Path(path, graph.getEdge(path.lastVertex, vertices.get(i))); return path; } @@ -122,7 +121,7 @@ public class Path { * @param p the path to extend * @param edge the edge to extend path by */ - public Path(final Path p, final BaseEdge edge) { + public Path(final Path p, final E edge) { if ( p == null ) throw new IllegalArgumentException("Path cannot be null"); if ( edge == null ) throw new IllegalArgumentException("Edge cannot be null"); if ( ! p.graph.containsEdge(edge) ) throw new IllegalArgumentException("Graph must contain edge " + edge + " but it doesn't"); @@ -130,7 +129,7 @@ public class Path { graph = p.graph; lastVertex = p.graph.getEdgeTarget(edge); - edgesInOrder = new LinkedList(p.getEdges()); + edgesInOrder = new LinkedList(p.getEdges()); edgesInOrder.add(edge); totalScore = p.totalScore + edge.getMultiplicity(); } @@ -139,7 +138,7 @@ public class Path { * Get the collection of edges leaving the last vertex of this path * @return a non-null collection */ - public Collection getOutgoingEdgesOfLastVertex() { + public Collection getOutgoingEdgesOfLastVertex() { return getGraph().outgoingEdgesOf(getLastVertex()); } @@ -148,12 +147,12 @@ public class Path { * @param edge the given edge to test * @return true if the edge is found in this path */ - public boolean containsEdge( final BaseEdge edge ) { + public boolean containsEdge( final E edge ) { if( edge == null ) { throw new IllegalArgumentException("Attempting to test null edge."); } if ( edgesInOrder.isEmpty() ) return false; // initialize contains cache if necessary - if ( edgesAsSet == null ) edgesAsSet = new HashSet(edgesInOrder); + if ( edgesAsSet == null ) edgesAsSet = new HashSet(edgesInOrder); return edgesAsSet.contains(edge); } @@ -175,7 +174,7 @@ public class Path { * @param path the other path we might be the same as * @return true if this and path are the same */ - protected boolean pathsAreTheSame(Path path) { + protected boolean pathsAreTheSame(Path path) { return totalScore == path.totalScore && edgesInOrder.equals(path.edgesInOrder); } @@ -199,7 +198,7 @@ public class Path { * @return a non-null graph */ @Ensures("result != null") - public BaseGraph getGraph() { + public BaseGraph getGraph() { return graph; } @@ -208,7 +207,7 @@ public class Path { * @return a non-null list of edges */ @Ensures("result != null") - public List getEdges() { return edgesInOrder; } + public List getEdges() { return edgesInOrder; } /** * Get the list of vertices in this path in order defined by the edges of the path @@ -221,7 +220,7 @@ public class Path { else { final LinkedList vertices = new LinkedList(); boolean first = true; - for ( final BaseEdge e : getEdges() ) { + for ( final E e : getEdges() ) { if ( first ) { vertices.add(graph.getEdgeSource(e)); first = false; @@ -246,6 +245,14 @@ public class Path { @Ensures("result != null") public T getLastVertex() { return lastVertex; } + /** + * Get the first vertex in this path + * @return a non-null vertex + */ + public T getFirstVertex() { + return getGraph().getEdgeSource(edgesInOrder.pollFirst()); + } + /** * The base sequence for this path. Pull the full sequence for source nodes and then the suffix for all subsequent nodes * @return non-null sequence of bases corresponding to this path @@ -255,174 +262,114 @@ public class Path { if( getEdges().isEmpty() ) { return graph.getAdditionalSequence(lastVertex); } byte[] bases = graph.getAdditionalSequence(graph.getEdgeSource(edgesInOrder.getFirst())); - for( final BaseEdge e : edgesInOrder ) { + for( final E e : edgesInOrder ) { bases = ArrayUtils.addAll(bases, graph.getAdditionalSequence(graph.getEdgeTarget(e))); } return bases; } /** - * Calculate the cigar string for this path using a bubble traversal of the assembly graph and running a Smith-Waterman alignment on each bubble - * @return non-null Cigar string with reference length equal to the refHaplotype's reference length + * Calculate the cigar elements for this path against the reference sequence + * + * @param refSeq the reference sequence that all of the bases in this path should align to + * @return a Cigar mapping this path to refSeq, or null if no reasonable alignment could be found */ - @Ensures("result != null") - public Cigar calculateCigar() { - final Cigar cigar = new Cigar(); - // special case for paths that start on reference but not at the reference source node - if( edgesInOrder.getFirst().isRef() && !graph.isRefSource(edgesInOrder.getFirst()) ) { - for( final CigarElement ce : calculateCigarForCompleteBubble(null, null, graph.getEdgeSource(edgesInOrder.getFirst())).getCigarElements() ) { - cigar.add(ce); - } + public Cigar calculateCigar(final byte[] refSeq) { + if ( getBases().length == 0 ) { + // horrible edge case from the unit tests, where this path has no bases + return new Cigar(Arrays.asList(new CigarElement(refSeq.length, CigarOperator.D))); } - // reset the bubble state machine - final BubbleStateMachine bsm = new BubbleStateMachine(cigar); + final byte[] bases = getBases(); + final Cigar nonStandard; - for( final BaseEdge e : getEdges() ) { - if ( e.hasSameSourceAndTarget(graph, edgesInOrder.getFirst()) ) { - advanceBubbleStateMachine( bsm, graph.getEdgeSource(e), null ); - } - advanceBubbleStateMachine( bsm, graph.getEdgeTarget(e), e ); + final String paddedRef = SW_PAD + new String(refSeq) + SW_PAD; + final String paddedPath = SW_PAD + new String(bases) + SW_PAD; + final SmithWaterman alignment = new SWPairwiseAlignment( paddedRef.getBytes(), paddedPath.getBytes(), NEW_SW_PARAMETERS ); + + if ( isSWFailure(alignment) ) + return null; + + // cut off the padding bases + final int baseStart = SW_PAD.length(); + final int baseEnd = paddedPath.length() - SW_PAD.length() - 1; // -1 because it's inclusive + nonStandard = AlignmentUtils.trimCigarByBases(alignment.getCigar(), baseStart, baseEnd); + + if ( nonStandard.getReferenceLength() != refSeq.length ) { + nonStandard.add(new CigarElement(refSeq.length - nonStandard.getReferenceLength(), CigarOperator.D)); } - // special case for paths that don't end on reference - if( bsm.inBubble ) { - for( final CigarElement ce : calculateCigarForCompleteBubble(bsm.bubbleBytes, bsm.lastSeenReferenceNode, null).getCigarElements() ) { - bsm.cigar.add(ce); - } - } else if( edgesInOrder.getLast().isRef() && !graph.isRefSink(edgesInOrder.getLast()) ) { // special case for paths that end of the reference but haven't completed the entire reference circuit - for( final CigarElement ce : calculateCigarForCompleteBubble(bsm.bubbleBytes, graph.getEdgeTarget(edgesInOrder.getLast()), null).getCigarElements() ) { - bsm.cigar.add(ce); - } - } - - return AlignmentUtils.consolidateCigar(bsm.cigar); + // finally, return the cigar with all indels left aligned + return leftAlignCigarSequentially(nonStandard, refSeq, getBases(), 0, 0); } /** - * Advance the bubble state machine by incorporating the next node in the path. - * @param bsm the current bubble state machine - * @param node the node to be incorporated - * @param e the edge which generated this node in the path + * Make sure that the SW didn't fail in some terrible way, and throw exception if it did */ - @Requires({"bsm != null", "graph != null", "node != null"}) - private void advanceBubbleStateMachine( final BubbleStateMachine bsm, final T node, final BaseEdge e ) { - if( graph.isReferenceNode( node ) ) { - if( !bsm.inBubble ) { // just add the ref bases as M's in the Cigar string, and don't do anything else - if( e !=null && !e.isRef() ) { - if( graph.referencePathExists( graph.getEdgeSource(e), node) ) { - for( final CigarElement ce : calculateCigarForCompleteBubble(null, graph.getEdgeSource(e), node).getCigarElements() ) { - bsm.cigar.add(ce); - } - bsm.cigar.add( new CigarElement( graph.getAdditionalSequence(node).length, CigarOperator.M) ); - } else if ( graph.getEdgeSource(e).equals(graph.getEdgeTarget(e)) ) { // alt edge at ref node points to itself - bsm.cigar.add( new CigarElement( graph.getAdditionalSequence(node).length, CigarOperator.I) ); - } else { - bsm.inBubble = true; - bsm.bubbleBytes = null; - bsm.lastSeenReferenceNode = graph.getEdgeSource(e); - bsm.bubbleBytes = ArrayUtils.addAll( bsm.bubbleBytes, graph.getAdditionalSequence(node) ); - } - } else { - bsm.cigar.add( new CigarElement( graph.getAdditionalSequence(node).length, CigarOperator.M) ); - } - } else if( bsm.lastSeenReferenceNode != null && !graph.referencePathExists( bsm.lastSeenReferenceNode, node ) ) { // add bases to the bubble string until we get back to the reference path - bsm.bubbleBytes = ArrayUtils.addAll( bsm.bubbleBytes, graph.getAdditionalSequence(node) ); - } else { // close the bubble and use a local SW to determine the Cigar string - for( final CigarElement ce : calculateCigarForCompleteBubble(bsm.bubbleBytes, bsm.lastSeenReferenceNode, node).getCigarElements() ) { - bsm.cigar.add(ce); - } - bsm.inBubble = false; - bsm.bubbleBytes = null; - bsm.lastSeenReferenceNode = null; - bsm.cigar.add( new CigarElement( graph.getAdditionalSequence(node).length, CigarOperator.M) ); - } - } else { // non-ref vertex - if( bsm.inBubble ) { // just keep accumulating until we get back to the reference path - bsm.bubbleBytes = ArrayUtils.addAll( bsm.bubbleBytes, graph.getAdditionalSequence(node) ); - } else { // open up a bubble - bsm.inBubble = true; - bsm.bubbleBytes = null; - bsm.lastSeenReferenceNode = (e != null ? graph.getEdgeSource(e) : null ); - bsm.bubbleBytes = ArrayUtils.addAll( bsm.bubbleBytes, graph.getAdditionalSequence(node) ); - } + private boolean isSWFailure(final SmithWaterman alignment) { + // check that the alignment starts at the first base, which it should given the padding + if ( alignment.getAlignmentStart2wrt1() > 0 ) { + return true; +// throw new IllegalStateException("SW failure ref " + paddedRef + " vs. " + paddedPath + " should always start at 0, but got " + alignment.getAlignmentStart2wrt1() + " with cigar " + alignment.getCigar()); } + + // check that we aren't getting any S operators (which would be very bad downstream) + for ( final CigarElement ce : alignment.getCigar().getCigarElements() ) { + if ( ce.getOperator() == CigarOperator.S ) + return true; + // soft clips at the end of the alignment are really insertions +// throw new IllegalStateException("SW failure ref " + paddedRef + " vs. " + paddedPath + " should never contain S operators but got cigar " + alignment.getCigar()); + } + + return false; } /** - * Now that we have a completed bubble run a Smith-Waterman alignment to determine the cigar string for this bubble - * @param bubbleBytes the bytes that comprise the alternate allele path in this bubble - * @param fromVertex the vertex that marks the beginning of the reference path in this bubble (null indicates ref source vertex) - * @param toVertex the vertex that marks the end of the reference path in this bubble (null indicates ref sink vertex) - * @return the cigar string generated by running a SW alignment between the reference and alternate paths in this bubble + * Left align the given cigar sequentially. This is needed because AlignmentUtils doesn't accept cigars with more than one indel in them. + * This is a target of future work to incorporate and generalize into AlignmentUtils for use by others. + * @param cigar the cigar to left align + * @param refSeq the reference byte array + * @param readSeq the read byte array + * @param refIndex 0-based alignment start position on ref + * @param readIndex 0-based alignment start position on read + * @return the left-aligned cigar */ - @Requires({"graph != null"}) - @Ensures({"result != null"}) - private Cigar calculateCigarForCompleteBubble( final byte[] bubbleBytes, final T fromVertex, final T toVertex ) { - final byte[] refBytes = graph.getReferenceBytes(fromVertex == null ? graph.getReferenceSourceVertex() : fromVertex, toVertex == null ? graph.getReferenceSinkVertex() : toVertex, fromVertex == null, toVertex == null); - - final Cigar returnCigar = new Cigar(); - - // add padding to anchor ref/alt bases in the SW matrix - byte[] padding = STARTING_SW_ANCHOR_BYTES; - boolean goodAlignment = false; - SWPairwiseAlignment swConsensus = null; - while( !goodAlignment && padding.length < 1000 ) { - padding = ArrayUtils.addAll(padding, padding); // double the size of the padding each time - final byte[] reference = ArrayUtils.addAll( ArrayUtils.addAll(padding, refBytes), padding ); - final byte[] alternate = ArrayUtils.addAll( ArrayUtils.addAll(padding, bubbleBytes), padding ); - swConsensus = new SWPairwiseAlignment( reference, alternate, NEW_SW_PARAMETERS ); - if( swConsensus.getAlignmentStart2wrt1() == 0 && !swConsensus.getCigar().toString().contains("S") && swConsensus.getCigar().getReferenceLength() == reference.length ) { - goodAlignment = true; + @Ensures({"cigar != null", "refSeq != null", "readSeq != null", "refIndex >= 0", "readIndex >= 0"}) + protected static Cigar leftAlignCigarSequentially(final Cigar cigar, final byte[] refSeq, final byte[] readSeq, int refIndex, int readIndex) { + final Cigar cigarToReturn = new Cigar(); + Cigar cigarToAlign = new Cigar(); + for (int i = 0; i < cigar.numCigarElements(); i++) { + final CigarElement ce = cigar.getCigarElement(i); + if (ce.getOperator() == CigarOperator.D || ce.getOperator() == CigarOperator.I) { + cigarToAlign.add(ce); + final Cigar leftAligned = AlignmentUtils.leftAlignSingleIndel(cigarToAlign, refSeq, readSeq, refIndex, readIndex, false); + for ( final CigarElement toAdd : leftAligned.getCigarElements() ) { cigarToReturn.add(toAdd); } + refIndex += cigarToAlign.getReferenceLength(); + readIndex += cigarToAlign.getReadLength(); + cigarToAlign = new Cigar(); + } else { + cigarToAlign.add(ce); } } - if( !goodAlignment ) { - returnCigar.add(new CigarElement(1, CigarOperator.N)); - return returnCigar; - } - - final Cigar swCigar = swConsensus.getCigar(); - if( swCigar.numCigarElements() > MAX_CIGAR_ELEMENTS_BEFORE_FAILING_SW ) { // this bubble is too divergent from the reference - returnCigar.add(new CigarElement(1, CigarOperator.N)); - } else { - for( int iii = 0; iii < swCigar.numCigarElements(); iii++ ) { - // now we need to remove the padding from the cigar string - int length = swCigar.getCigarElement(iii).getLength(); - if( iii == 0 ) { length -= padding.length; } - if( iii == swCigar.numCigarElements() - 1 ) { length -= padding.length; } - if( length > 0 ) { - returnCigar.add(new CigarElement(length, swCigar.getCigarElement(iii).getOperator())); - } - } - if( (refBytes == null && returnCigar.getReferenceLength() != 0) || ( refBytes != null && returnCigar.getReferenceLength() != refBytes.length ) ) { - throw new IllegalStateException("SmithWaterman cigar failure: " + (refBytes == null ? "-" : new String(refBytes)) + " against " + new String(bubbleBytes) + " = " + swConsensus.getCigar()); + if( !cigarToAlign.isEmpty() ) { + for( final CigarElement toAdd : cigarToAlign.getCigarElements() ) { + cigarToReturn.add(toAdd); } } - return returnCigar; + final Cigar result = AlignmentUtils.consolidateCigar(cigarToReturn); + if( result.getReferenceLength() != cigar.getReferenceLength() ) + throw new IllegalStateException("leftAlignCigarSequentially failed to produce a valid CIGAR. Reference lengths differ. Initial cigar " + cigar + " left aligned into " + result); + return result; } - // class to keep track of the bubble state machine - private static class BubbleStateMachine { - public boolean inBubble = false; - public byte[] bubbleBytes = null; - public T lastSeenReferenceNode = null; - public Cigar cigar = null; - - public BubbleStateMachine( final Cigar initialCigar ) { - inBubble = false; - bubbleBytes = null; - lastSeenReferenceNode = null; - cigar = initialCigar; - } - } /** * Tests that this and other have the same score and vertices in the same order with the same seq * @param other the other path to consider. Cannot be null * @return true if this and path are equal, false otherwise */ - public boolean equalScoreAndSequence(final Path other) { + public boolean equalScoreAndSequence(final Path other) { if ( other == null ) throw new IllegalArgumentException("other cannot be null"); return getScore() == other.getScore() && equalSequence(other); } @@ -432,7 +379,7 @@ public class Path { * @param other the other path to consider. Cannot be null * @return true if this and path are equal, false otherwise */ - public boolean equalSequence(final Path other) { + public boolean equalSequence(final Path other) { final List mine = getVertices(); final List yours = other.getVertices(); if ( mine.size() == yours.size() ) { // hehehe diff --git a/protected/java/src/org/broadinstitute/sting/gatk/walkers/haplotypecaller/graphs/SeqGraph.java b/protected/java/src/org/broadinstitute/sting/gatk/walkers/haplotypecaller/graphs/SeqGraph.java index bb4b26257..20edcb39b 100644 --- a/protected/java/src/org/broadinstitute/sting/gatk/walkers/haplotypecaller/graphs/SeqGraph.java +++ b/protected/java/src/org/broadinstitute/sting/gatk/walkers/haplotypecaller/graphs/SeqGraph.java @@ -48,6 +48,7 @@ package org.broadinstitute.sting.gatk.walkers.haplotypecaller.graphs; import com.google.java.contract.Ensures; import com.google.java.contract.Requires; +import org.jgrapht.EdgeFactory; import java.io.File; import java.util.HashSet; @@ -61,7 +62,17 @@ import java.util.Set; * @author: depristo * @since 03/2013 */ -public final class SeqGraph extends BaseGraph { +public final class SeqGraph extends BaseGraph { + /** + * Edge factory that creates non-reference multiplicity 1 edges + */ + private static class MyEdgeFactory implements EdgeFactory { + @Override + public BaseEdge createEdge(SeqVertex sourceVertex, SeqVertex targetVertex) { + return new BaseEdge(false, 1); + } + } + private final static boolean PRINT_SIMPLIFY_GRAPHS = false; /** @@ -82,7 +93,7 @@ public final class SeqGraph extends BaseGraph { * Construct an empty SeqGraph */ public SeqGraph() { - super(); + this(11); } /** @@ -94,7 +105,7 @@ public final class SeqGraph extends BaseGraph { * @param kmer kmer */ public SeqGraph(final int kmer) { - super(kmer); + super(kmer, new MyEdgeFactory()); } /** @@ -154,7 +165,6 @@ public final class SeqGraph extends BaseGraph { didSomeWork |= new MergeCommonSuffices().transformUntilComplete(); if ( PRINT_SIMPLIFY_GRAPHS ) printGraph(new File("simplifyGraph." + iteration + ".4.merge_suffix.dot"), 0); - didSomeWork |= new MergeHeadlessIncomingSources().transformUntilComplete(); didSomeWork |= zipLinearChains(); return didSomeWork; } @@ -289,8 +299,8 @@ public final class SeqGraph extends BaseGraph { final BaseEdge inc = new BaseEdge(false, sharedWeightAmongEdges); // template to make .add function call easy // update the incoming and outgoing edges to point to the new vertex - for( final BaseEdge edge : outEdges ) { addEdge(addedVertex, getEdgeTarget(edge), new BaseEdge(edge).add(inc)); } - for( final BaseEdge edge : inEdges ) { addEdge(getEdgeSource(edge), addedVertex, new BaseEdge(edge).add(inc)); } + for( final BaseEdge edge : outEdges ) { addEdge(addedVertex, getEdgeTarget(edge), edge.copy().add(inc)); } + for( final BaseEdge edge : inEdges ) { addEdge(getEdgeSource(edge), addedVertex, edge.copy().add(inc)); } removeAllVertices(linearChain); return true; @@ -505,40 +515,4 @@ public final class SeqGraph extends BaseGraph { } } } - - /** - * Merge headless configurations: - * - * Performs the transformation: - * - * { x + S_i + y -> Z } - * - * goes to: - * - * { x -> S_i -> y -> Z } - * - * for all nodes that match this configuration. - * - * Differs from the diamond transform in that no top node is required - */ - protected class MergeHeadlessIncomingSources extends VertexBasedTransformer { - @Override - boolean tryToTransform(final SeqVertex bottom) { - final Set incoming = incomingVerticesOf(bottom); - if ( incoming.size() <= 1 ) - return false; - - for ( final SeqVertex inc : incoming ) - if ( ! isSource(inc) || outDegreeOf(inc) > 1 ) - return false; - - if ( dontModifyGraphEvenIfPossible() ) return true; - - final SharedVertexSequenceSplitter splitter = new SharedVertexSequenceSplitter(SeqGraph.this, incoming); - if (splitter.meetsMinMergableSequenceForPrefix(MIN_COMMON_SEQUENCE_TO_MERGE_SOURCE_SINK_VERTICES)) - return splitter.splitAndUpdate(null, bottom); - else - return false; - } - } } diff --git a/protected/java/src/org/broadinstitute/sting/gatk/walkers/haplotypecaller/graphs/SharedSequenceMerger.java b/protected/java/src/org/broadinstitute/sting/gatk/walkers/haplotypecaller/graphs/SharedSequenceMerger.java index 1c53f2332..0babd8d56 100644 --- a/protected/java/src/org/broadinstitute/sting/gatk/walkers/haplotypecaller/graphs/SharedSequenceMerger.java +++ b/protected/java/src/org/broadinstitute/sting/gatk/walkers/haplotypecaller/graphs/SharedSequenceMerger.java @@ -88,13 +88,13 @@ public class SharedSequenceMerger { for ( final SeqVertex prev : prevs ) { for ( final BaseEdge prevIn : graph.incomingEdgesOf(prev) ) { - graph.addEdge(graph.getEdgeSource(prevIn), newV, new BaseEdge(prevIn)); + graph.addEdge(graph.getEdgeSource(prevIn), newV, prevIn.copy()); edgesToRemove.add(prevIn); } } for ( final BaseEdge e : graph.outgoingEdgesOf(v) ) { - graph.addEdge(newV, graph.getEdgeTarget(e), new BaseEdge(e)); + graph.addEdge(newV, graph.getEdgeTarget(e), e.copy()); } graph.removeAllVertices(prevs); diff --git a/protected/java/src/org/broadinstitute/sting/gatk/walkers/haplotypecaller/graphs/SharedVertexSequenceSplitter.java b/protected/java/src/org/broadinstitute/sting/gatk/walkers/haplotypecaller/graphs/SharedVertexSequenceSplitter.java index f6ee4c3c3..205d0027a 100644 --- a/protected/java/src/org/broadinstitute/sting/gatk/walkers/haplotypecaller/graphs/SharedVertexSequenceSplitter.java +++ b/protected/java/src/org/broadinstitute/sting/gatk/walkers/haplotypecaller/graphs/SharedVertexSequenceSplitter.java @@ -209,7 +209,7 @@ public class SharedVertexSequenceSplitter { splitGraph.addEdge(remaining, suffixV, fromMid); } else { // prefix + suffix completely explain this node - splitGraph.addOrUpdateEdge(prefixV, suffixV, new BaseEdge(toMid).add(fromMid)); + splitGraph.addOrUpdateEdge(prefixV, suffixV, toMid.copy().add(fromMid)); } } } @@ -323,7 +323,7 @@ public class SharedVertexSequenceSplitter { } else { // schedule edge for removal, and return a freshly allocated one for our graph to use edgesToRemove.add(e); - return new BaseEdge(e); + return e.copy(); } } } \ No newline at end of file diff --git a/protected/java/src/org/broadinstitute/sting/gatk/walkers/haplotypecaller/readthreading/MultiDeBruijnVertex.java b/protected/java/src/org/broadinstitute/sting/gatk/walkers/haplotypecaller/readthreading/MultiDeBruijnVertex.java new file mode 100644 index 000000000..814b3b9a7 --- /dev/null +++ b/protected/java/src/org/broadinstitute/sting/gatk/walkers/haplotypecaller/readthreading/MultiDeBruijnVertex.java @@ -0,0 +1,118 @@ +/* +* By downloading the PROGRAM you agree to the following terms of use: +* +* BROAD INSTITUTE - SOFTWARE LICENSE AGREEMENT - FOR ACADEMIC NON-COMMERCIAL RESEARCH PURPOSES ONLY +* +* This Agreement is made between the Broad Institute, Inc. with a principal address at 7 Cambridge Center, Cambridge, MA 02142 (BROAD) and the LICENSEE and is effective at the date the downloading is completed (EFFECTIVE DATE). +* +* WHEREAS, LICENSEE desires to license the PROGRAM, as defined hereinafter, and BROAD wishes to have this PROGRAM utilized in the public interest, subject only to the royalty-free, nonexclusive, nontransferable license rights of the United States Government pursuant to 48 CFR 52.227-14; and +* WHEREAS, LICENSEE desires to license the PROGRAM and BROAD desires to grant a license on the following terms and conditions. +* NOW, THEREFORE, in consideration of the promises and covenants made herein, the parties hereto agree as follows: +* +* 1. DEFINITIONS +* 1.1 PROGRAM shall mean copyright in the object code and source code known as GATK2 and related documentation, if any, as they exist on the EFFECTIVE DATE and can be downloaded from http://www.broadinstitute/GATK on the EFFECTIVE DATE. +* +* 2. LICENSE +* 2.1 Grant. Subject to the terms of this Agreement, BROAD hereby grants to LICENSEE, solely for academic non-commercial research purposes, a non-exclusive, non-transferable license to: (a) download, execute and display the PROGRAM and (b) create bug fixes and modify the PROGRAM. +* The LICENSEE may apply the PROGRAM in a pipeline to data owned by users other than the LICENSEE and provide these users the results of the PROGRAM provided LICENSEE does so for academic non-commercial purposes only. For clarification purposes, academic sponsored research is not a commercial use under the terms of this Agreement. +* 2.2 No Sublicensing or Additional Rights. LICENSEE shall not sublicense or distribute the PROGRAM, in whole or in part, without prior written permission from BROAD. LICENSEE shall ensure that all of its users agree to the terms of this Agreement. LICENSEE further agrees that it shall not put the PROGRAM on a network, server, or other similar technology that may be accessed by anyone other than the LICENSEE and its employees and users who have agreed to the terms of this agreement. +* 2.3 License Limitations. Nothing in this Agreement shall be construed to confer any rights upon LICENSEE by implication, estoppel, or otherwise to any computer software, trademark, intellectual property, or patent rights of BROAD, or of any other entity, except as expressly granted herein. LICENSEE agrees that the PROGRAM, in whole or part, shall not be used for any commercial purpose, including without limitation, as the basis of a commercial software or hardware product or to provide services. LICENSEE further agrees that the PROGRAM shall not be copied or otherwise adapted in order to circumvent the need for obtaining a license for use of the PROGRAM. +* +* 3. OWNERSHIP OF INTELLECTUAL PROPERTY +* LICENSEE acknowledges that title to the PROGRAM shall remain with BROAD. The PROGRAM is marked with the following BROAD copyright notice and notice of attribution to contributors. LICENSEE shall retain such notice on all copies. LICENSEE agrees to include appropriate attribution if any results obtained from use of the PROGRAM are included in any publication. +* Copyright 2012 Broad Institute, Inc. +* Notice of attribution: The GATK2 program was made available through the generosity of Medical and Population Genetics program at the Broad Institute, Inc. +* LICENSEE shall not use any trademark or trade name of BROAD, or any variation, adaptation, or abbreviation, of such marks or trade names, or any names of officers, faculty, students, employees, or agents of BROAD except as states above for attribution purposes. +* +* 4. INDEMNIFICATION +* LICENSEE shall indemnify, defend, and hold harmless BROAD, and their respective officers, faculty, students, employees, associated investigators and agents, and their respective successors, heirs and assigns, (Indemnitees), against any liability, damage, loss, or expense (including reasonable attorneys fees and expenses) incurred by or imposed upon any of the Indemnitees in connection with any claims, suits, actions, demands or judgments arising out of any theory of liability (including, without limitation, actions in the form of tort, warranty, or strict liability and regardless of whether such action has any factual basis) pursuant to any right or license granted under this Agreement. +* +* 5. NO REPRESENTATIONS OR WARRANTIES +* THE PROGRAM IS DELIVERED AS IS. BROAD MAKES NO REPRESENTATIONS OR WARRANTIES OF ANY KIND CONCERNING THE PROGRAM OR THE COPYRIGHT, EXPRESS OR IMPLIED, INCLUDING, WITHOUT LIMITATION, WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NONINFRINGEMENT, OR THE ABSENCE OF LATENT OR OTHER DEFECTS, WHETHER OR NOT DISCOVERABLE. BROAD EXTENDS NO WARRANTIES OF ANY KIND AS TO PROGRAM CONFORMITY WITH WHATEVER USER MANUALS OR OTHER LITERATURE MAY BE ISSUED FROM TIME TO TIME. +* IN NO EVENT SHALL BROAD OR ITS RESPECTIVE DIRECTORS, OFFICERS, EMPLOYEES, AFFILIATED INVESTIGATORS AND AFFILIATES BE LIABLE FOR INCIDENTAL OR CONSEQUENTIAL DAMAGES OF ANY KIND, INCLUDING, WITHOUT LIMITATION, ECONOMIC DAMAGES OR INJURY TO PROPERTY AND LOST PROFITS, REGARDLESS OF WHETHER BROAD SHALL BE ADVISED, SHALL HAVE OTHER REASON TO KNOW, OR IN FACT SHALL KNOW OF THE POSSIBILITY OF THE FOREGOING. +* +* 6. ASSIGNMENT +* This Agreement is personal to LICENSEE and any rights or obligations assigned by LICENSEE without the prior written consent of BROAD shall be null and void. +* +* 7. MISCELLANEOUS +* 7.1 Export Control. LICENSEE gives assurance that it will comply with all United States export control laws and regulations controlling the export of the PROGRAM, including, without limitation, all Export Administration Regulations of the United States Department of Commerce. Among other things, these laws and regulations prohibit, or require a license for, the export of certain types of software to specified countries. +* 7.2 Termination. LICENSEE shall have the right to terminate this Agreement for any reason upon prior written notice to BROAD. If LICENSEE breaches any provision hereunder, and fails to cure such breach within thirty (30) days, BROAD may terminate this Agreement immediately. Upon termination, LICENSEE shall provide BROAD with written assurance that the original and all copies of the PROGRAM have been destroyed, except that, upon prior written authorization from BROAD, LICENSEE may retain a copy for archive purposes. +* 7.3 Survival. The following provisions shall survive the expiration or termination of this Agreement: Articles 1, 3, 4, 5 and Sections 2.2, 2.3, 7.3, and 7.4. +* 7.4 Notice. Any notices under this Agreement shall be in writing, shall specifically refer to this Agreement, and shall be sent by hand, recognized national overnight courier, confirmed facsimile transmission, confirmed electronic mail, or registered or certified mail, postage prepaid, return receipt requested. All notices under this Agreement shall be deemed effective upon receipt. +* 7.5 Amendment and Waiver; Entire Agreement. This Agreement may be amended, supplemented, or otherwise modified only by means of a written instrument signed by all parties. Any waiver of any rights or failure to act in a specific instance shall relate only to such instance and shall not be construed as an agreement to waive any rights or fail to act in any other instance, whether or not similar. This Agreement constitutes the entire agreement among the parties with respect to its subject matter and supersedes prior agreements or understandings between the parties relating to its subject matter. +* 7.6 Binding Effect; Headings. This Agreement shall be binding upon and inure to the benefit of the parties and their respective permitted successors and assigns. All headings are for convenience only and shall not affect the meaning of any provision of this Agreement. +* 7.7 Governing Law. This Agreement shall be construed, governed, interpreted and applied in accordance with the internal laws of the Commonwealth of Massachusetts, U.S.A., without regard to conflict of laws principles. +*/ + +package org.broadinstitute.sting.gatk.walkers.haplotypecaller.readthreading; + +import org.broadinstitute.sting.gatk.walkers.haplotypecaller.graphs.DeBruijnVertex; +import org.broadinstitute.sting.utils.Utils; + +import java.util.LinkedList; +import java.util.List; + +/** + * A DeBruijnVertex that supports multiple copies of the same kmer + * + * This is implemented through the same mechanism as SeqVertex, where each + * created MultiDeBruijnVertex has a unique id assigned upon creation. Two + * MultiDeBruijnVertex are equal iff they have the same ID + * + * User: depristo + * Date: 4/17/13 + * Time: 3:20 PM + */ +final class MultiDeBruijnVertex extends DeBruijnVertex { + private final static boolean KEEP_TRACK_OF_READS = false; + private static int idCounter = 0; + + private final List reads = new LinkedList(); + private int id = idCounter++; // TODO -- potential race condition problem here + + /** + * Create a new MultiDeBruijnVertex with kmer sequence + * @param sequence the kmer sequence + */ + MultiDeBruijnVertex(byte[] sequence) { + super(sequence); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + MultiDeBruijnVertex that = (MultiDeBruijnVertex) o; + + return id == that.id; + } + + @Override + public String toString() { + return "MultiDeBruijnVertex_id_" + id + "_seq_" + getSequenceString(); + } + + /** + * Add name information to this vertex for debugging + * + * This information will be captured as a list of strings, and displayed in DOT if this + * graph is written out to disk + * + * This functionality is only enabled when KEEP_TRACK_OF_READS is true + * + * @param name a non-null string + */ + protected void addRead(final String name) { + if ( name == null ) throw new IllegalArgumentException("name cannot be null"); + if ( KEEP_TRACK_OF_READS ) reads.add(name); + } + + @Override + public int hashCode() { return id; } + + @Override + public String additionalInfo() { + return KEEP_TRACK_OF_READS ? (! reads.contains("ref") ? "__" + Utils.join(",", reads) : "") : ""; + } +} diff --git a/protected/java/src/org/broadinstitute/sting/gatk/walkers/haplotypecaller/readthreading/ReadThreadingAssembler.java b/protected/java/src/org/broadinstitute/sting/gatk/walkers/haplotypecaller/readthreading/ReadThreadingAssembler.java new file mode 100644 index 000000000..db0ce0880 --- /dev/null +++ b/protected/java/src/org/broadinstitute/sting/gatk/walkers/haplotypecaller/readthreading/ReadThreadingAssembler.java @@ -0,0 +1,162 @@ +/* +* By downloading the PROGRAM you agree to the following terms of use: +* +* BROAD INSTITUTE - SOFTWARE LICENSE AGREEMENT - FOR ACADEMIC NON-COMMERCIAL RESEARCH PURPOSES ONLY +* +* This Agreement is made between the Broad Institute, Inc. with a principal address at 7 Cambridge Center, Cambridge, MA 02142 (BROAD) and the LICENSEE and is effective at the date the downloading is completed (EFFECTIVE DATE). +* +* WHEREAS, LICENSEE desires to license the PROGRAM, as defined hereinafter, and BROAD wishes to have this PROGRAM utilized in the public interest, subject only to the royalty-free, nonexclusive, nontransferable license rights of the United States Government pursuant to 48 CFR 52.227-14; and +* WHEREAS, LICENSEE desires to license the PROGRAM and BROAD desires to grant a license on the following terms and conditions. +* NOW, THEREFORE, in consideration of the promises and covenants made herein, the parties hereto agree as follows: +* +* 1. DEFINITIONS +* 1.1 PROGRAM shall mean copyright in the object code and source code known as GATK2 and related documentation, if any, as they exist on the EFFECTIVE DATE and can be downloaded from http://www.broadinstitute/GATK on the EFFECTIVE DATE. +* +* 2. LICENSE +* 2.1 Grant. Subject to the terms of this Agreement, BROAD hereby grants to LICENSEE, solely for academic non-commercial research purposes, a non-exclusive, non-transferable license to: (a) download, execute and display the PROGRAM and (b) create bug fixes and modify the PROGRAM. +* The LICENSEE may apply the PROGRAM in a pipeline to data owned by users other than the LICENSEE and provide these users the results of the PROGRAM provided LICENSEE does so for academic non-commercial purposes only. For clarification purposes, academic sponsored research is not a commercial use under the terms of this Agreement. +* 2.2 No Sublicensing or Additional Rights. LICENSEE shall not sublicense or distribute the PROGRAM, in whole or in part, without prior written permission from BROAD. LICENSEE shall ensure that all of its users agree to the terms of this Agreement. LICENSEE further agrees that it shall not put the PROGRAM on a network, server, or other similar technology that may be accessed by anyone other than the LICENSEE and its employees and users who have agreed to the terms of this agreement. +* 2.3 License Limitations. Nothing in this Agreement shall be construed to confer any rights upon LICENSEE by implication, estoppel, or otherwise to any computer software, trademark, intellectual property, or patent rights of BROAD, or of any other entity, except as expressly granted herein. LICENSEE agrees that the PROGRAM, in whole or part, shall not be used for any commercial purpose, including without limitation, as the basis of a commercial software or hardware product or to provide services. LICENSEE further agrees that the PROGRAM shall not be copied or otherwise adapted in order to circumvent the need for obtaining a license for use of the PROGRAM. +* +* 3. OWNERSHIP OF INTELLECTUAL PROPERTY +* LICENSEE acknowledges that title to the PROGRAM shall remain with BROAD. The PROGRAM is marked with the following BROAD copyright notice and notice of attribution to contributors. LICENSEE shall retain such notice on all copies. LICENSEE agrees to include appropriate attribution if any results obtained from use of the PROGRAM are included in any publication. +* Copyright 2012 Broad Institute, Inc. +* Notice of attribution: The GATK2 program was made available through the generosity of Medical and Population Genetics program at the Broad Institute, Inc. +* LICENSEE shall not use any trademark or trade name of BROAD, or any variation, adaptation, or abbreviation, of such marks or trade names, or any names of officers, faculty, students, employees, or agents of BROAD except as states above for attribution purposes. +* +* 4. INDEMNIFICATION +* LICENSEE shall indemnify, defend, and hold harmless BROAD, and their respective officers, faculty, students, employees, associated investigators and agents, and their respective successors, heirs and assigns, (Indemnitees), against any liability, damage, loss, or expense (including reasonable attorneys fees and expenses) incurred by or imposed upon any of the Indemnitees in connection with any claims, suits, actions, demands or judgments arising out of any theory of liability (including, without limitation, actions in the form of tort, warranty, or strict liability and regardless of whether such action has any factual basis) pursuant to any right or license granted under this Agreement. +* +* 5. NO REPRESENTATIONS OR WARRANTIES +* THE PROGRAM IS DELIVERED AS IS. BROAD MAKES NO REPRESENTATIONS OR WARRANTIES OF ANY KIND CONCERNING THE PROGRAM OR THE COPYRIGHT, EXPRESS OR IMPLIED, INCLUDING, WITHOUT LIMITATION, WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NONINFRINGEMENT, OR THE ABSENCE OF LATENT OR OTHER DEFECTS, WHETHER OR NOT DISCOVERABLE. BROAD EXTENDS NO WARRANTIES OF ANY KIND AS TO PROGRAM CONFORMITY WITH WHATEVER USER MANUALS OR OTHER LITERATURE MAY BE ISSUED FROM TIME TO TIME. +* IN NO EVENT SHALL BROAD OR ITS RESPECTIVE DIRECTORS, OFFICERS, EMPLOYEES, AFFILIATED INVESTIGATORS AND AFFILIATES BE LIABLE FOR INCIDENTAL OR CONSEQUENTIAL DAMAGES OF ANY KIND, INCLUDING, WITHOUT LIMITATION, ECONOMIC DAMAGES OR INJURY TO PROPERTY AND LOST PROFITS, REGARDLESS OF WHETHER BROAD SHALL BE ADVISED, SHALL HAVE OTHER REASON TO KNOW, OR IN FACT SHALL KNOW OF THE POSSIBILITY OF THE FOREGOING. +* +* 6. ASSIGNMENT +* This Agreement is personal to LICENSEE and any rights or obligations assigned by LICENSEE without the prior written consent of BROAD shall be null and void. +* +* 7. MISCELLANEOUS +* 7.1 Export Control. LICENSEE gives assurance that it will comply with all United States export control laws and regulations controlling the export of the PROGRAM, including, without limitation, all Export Administration Regulations of the United States Department of Commerce. Among other things, these laws and regulations prohibit, or require a license for, the export of certain types of software to specified countries. +* 7.2 Termination. LICENSEE shall have the right to terminate this Agreement for any reason upon prior written notice to BROAD. If LICENSEE breaches any provision hereunder, and fails to cure such breach within thirty (30) days, BROAD may terminate this Agreement immediately. Upon termination, LICENSEE shall provide BROAD with written assurance that the original and all copies of the PROGRAM have been destroyed, except that, upon prior written authorization from BROAD, LICENSEE may retain a copy for archive purposes. +* 7.3 Survival. The following provisions shall survive the expiration or termination of this Agreement: Articles 1, 3, 4, 5 and Sections 2.2, 2.3, 7.3, and 7.4. +* 7.4 Notice. Any notices under this Agreement shall be in writing, shall specifically refer to this Agreement, and shall be sent by hand, recognized national overnight courier, confirmed facsimile transmission, confirmed electronic mail, or registered or certified mail, postage prepaid, return receipt requested. All notices under this Agreement shall be deemed effective upon receipt. +* 7.5 Amendment and Waiver; Entire Agreement. This Agreement may be amended, supplemented, or otherwise modified only by means of a written instrument signed by all parties. Any waiver of any rights or failure to act in a specific instance shall relate only to such instance and shall not be construed as an agreement to waive any rights or fail to act in any other instance, whether or not similar. This Agreement constitutes the entire agreement among the parties with respect to its subject matter and supersedes prior agreements or understandings between the parties relating to its subject matter. +* 7.6 Binding Effect; Headings. This Agreement shall be binding upon and inure to the benefit of the parties and their respective permitted successors and assigns. All headings are for convenience only and shall not affect the meaning of any provision of this Agreement. +* 7.7 Governing Law. This Agreement shall be construed, governed, interpreted and applied in accordance with the internal laws of the Commonwealth of Massachusetts, U.S.A., without regard to conflict of laws principles. +*/ + +package org.broadinstitute.sting.gatk.walkers.haplotypecaller.readthreading; + +import org.apache.log4j.Logger; +import org.broadinstitute.sting.gatk.walkers.haplotypecaller.LocalAssemblyEngine; +import org.broadinstitute.sting.gatk.walkers.haplotypecaller.graphs.*; +import org.broadinstitute.sting.utils.haplotype.Haplotype; +import org.broadinstitute.sting.utils.sam.GATKSAMRecord; + +import java.io.File; +import java.util.Arrays; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; + +public class ReadThreadingAssembler extends LocalAssemblyEngine { + private final static Logger logger = Logger.getLogger(ReadThreadingAssembler.class); + + private final static int DEFAULT_NUM_PATHS_PER_GRAPH = 128; + + /** The min and max kmer sizes to try when building the graph. */ + private final List kmerSizes; + private final int maxAllowedPathsForReadThreadingAssembler; + + private boolean requireReasonableNumberOfPaths = false; + protected boolean removePathsNotConnectedToRef = true; + private boolean justReturnRawGraph = false; + + /** for testing only */ + public ReadThreadingAssembler() { + this(DEFAULT_NUM_PATHS_PER_GRAPH, Arrays.asList(25)); + } + + public ReadThreadingAssembler(final int maxAllowedPathsForReadThreadingAssembler, final List kmerSizes) { + super(maxAllowedPathsForReadThreadingAssembler); + this.kmerSizes = kmerSizes; + this.maxAllowedPathsForReadThreadingAssembler = maxAllowedPathsForReadThreadingAssembler; + } + + /** for testing purposes */ + protected void setJustReturnRawGraph(boolean justReturnRawGraph) { + this.justReturnRawGraph = justReturnRawGraph; + } + + @Override + public List assemble( final List reads, final Haplotype refHaplotype) { + final List graphs = new LinkedList<>(); + + for ( final int kmerSize : kmerSizes ) { + final ReadThreadingGraph rtgraph = new ReadThreadingGraph(kmerSize, debugGraphTransformations, minBaseQualityToUseInAssembly); + + // add the reference sequence to the graph + rtgraph.addSequence("ref", refHaplotype.getBases(), null, true); + + // Next pull kmers out of every read and throw them on the graph + for( final GATKSAMRecord read : reads ) { + rtgraph.addRead(read); + } + + // actually build the read threading graph + rtgraph.buildGraphIfNecessary(); + if ( debugGraphTransformations ) rtgraph.printGraph(new File("sequenceGraph.0.0.raw_readthreading_graph.dot"), pruneFactor); + + // go through and prune all of the chains where all edges have <= pruneFactor. This must occur + // before recoverDanglingTails in the graph, so that we don't spend a ton of time recovering + // tails that we'll ultimately just trim away anyway, as the dangling tail edges have weight of 1 + rtgraph.pruneLowWeightChains(pruneFactor); + + // look at all chains in the graph that terminate in a non-ref node (dangling sinks) and see if + // we can recover them by merging some N bases from the chain back into the reference uniquely, for + // N < kmerSize + if ( recoverDanglingTails ) rtgraph.recoverDanglingTails(); + + // remove all heading and trailing paths + if ( removePathsNotConnectedToRef ) rtgraph.removePathsNotConnectedToRef(); + + if ( debugGraphTransformations ) rtgraph.printGraph(new File("sequenceGraph.0.1.cleaned_readthreading_graph.dot"), pruneFactor); + + final SeqGraph initialSeqGraph = rtgraph.convertToSequenceGraph(); + + // if the unit tests don't want us to cleanup the graph, just return the raw sequence graph + if ( justReturnRawGraph ) return Collections.singletonList(initialSeqGraph); + + if ( debug ) logger.info("Using kmer size of " + rtgraph.getKmerSize() + " in read threading assembler"); + if ( debugGraphTransformations ) initialSeqGraph.printGraph(new File("sequenceGraph.0.2.initial_seqgraph.dot"), pruneFactor); + initialSeqGraph.cleanNonRefPaths(); // TODO -- I don't this is possible by construction + + final SeqGraph seqGraph = cleanupSeqGraph(initialSeqGraph); + if ( seqGraph != null ) { + if ( ! requireReasonableNumberOfPaths || reasonableNumberOfPaths(seqGraph) ) { + graphs.add(seqGraph); + } + } + } + + return graphs; + } + + /** + * Did we find a reasonable number of paths in this graph? + * @param graph + * @return + */ + private boolean reasonableNumberOfPaths(final SeqGraph graph) { + final KBestPaths pathFinder = new KBestPaths(false); + final List> allPaths = pathFinder.getKBestPaths(graph, 100000); + logger.info("Found " + allPaths.size() + " paths through " + graph + " with maximum " + maxAllowedPathsForReadThreadingAssembler); + return allPaths.size() <= maxAllowedPathsForReadThreadingAssembler; + } + + @Override + public String toString() { + return "ReadThreadingAssembler{" + + "kmerSizes=" + kmerSizes + + '}'; + } +} \ No newline at end of file diff --git a/protected/java/src/org/broadinstitute/sting/gatk/walkers/haplotypecaller/readthreading/ReadThreadingGraph.java b/protected/java/src/org/broadinstitute/sting/gatk/walkers/haplotypecaller/readthreading/ReadThreadingGraph.java new file mode 100644 index 000000000..6e9223afb --- /dev/null +++ b/protected/java/src/org/broadinstitute/sting/gatk/walkers/haplotypecaller/readthreading/ReadThreadingGraph.java @@ -0,0 +1,640 @@ +/* +* By downloading the PROGRAM you agree to the following terms of use: +* +* BROAD INSTITUTE - SOFTWARE LICENSE AGREEMENT - FOR ACADEMIC NON-COMMERCIAL RESEARCH PURPOSES ONLY +* +* This Agreement is made between the Broad Institute, Inc. with a principal address at 7 Cambridge Center, Cambridge, MA 02142 (BROAD) and the LICENSEE and is effective at the date the downloading is completed (EFFECTIVE DATE). +* +* WHEREAS, LICENSEE desires to license the PROGRAM, as defined hereinafter, and BROAD wishes to have this PROGRAM utilized in the public interest, subject only to the royalty-free, nonexclusive, nontransferable license rights of the United States Government pursuant to 48 CFR 52.227-14; and +* WHEREAS, LICENSEE desires to license the PROGRAM and BROAD desires to grant a license on the following terms and conditions. +* NOW, THEREFORE, in consideration of the promises and covenants made herein, the parties hereto agree as follows: +* +* 1. DEFINITIONS +* 1.1 PROGRAM shall mean copyright in the object code and source code known as GATK2 and related documentation, if any, as they exist on the EFFECTIVE DATE and can be downloaded from http://www.broadinstitute/GATK on the EFFECTIVE DATE. +* +* 2. LICENSE +* 2.1 Grant. Subject to the terms of this Agreement, BROAD hereby grants to LICENSEE, solely for academic non-commercial research purposes, a non-exclusive, non-transferable license to: (a) download, execute and display the PROGRAM and (b) create bug fixes and modify the PROGRAM. +* The LICENSEE may apply the PROGRAM in a pipeline to data owned by users other than the LICENSEE and provide these users the results of the PROGRAM provided LICENSEE does so for academic non-commercial purposes only. For clarification purposes, academic sponsored research is not a commercial use under the terms of this Agreement. +* 2.2 No Sublicensing or Additional Rights. LICENSEE shall not sublicense or distribute the PROGRAM, in whole or in part, without prior written permission from BROAD. LICENSEE shall ensure that all of its users agree to the terms of this Agreement. LICENSEE further agrees that it shall not put the PROGRAM on a network, server, or other similar technology that may be accessed by anyone other than the LICENSEE and its employees and users who have agreed to the terms of this agreement. +* 2.3 License Limitations. Nothing in this Agreement shall be construed to confer any rights upon LICENSEE by implication, estoppel, or otherwise to any computer software, trademark, intellectual property, or patent rights of BROAD, or of any other entity, except as expressly granted herein. LICENSEE agrees that the PROGRAM, in whole or part, shall not be used for any commercial purpose, including without limitation, as the basis of a commercial software or hardware product or to provide services. LICENSEE further agrees that the PROGRAM shall not be copied or otherwise adapted in order to circumvent the need for obtaining a license for use of the PROGRAM. +* +* 3. OWNERSHIP OF INTELLECTUAL PROPERTY +* LICENSEE acknowledges that title to the PROGRAM shall remain with BROAD. The PROGRAM is marked with the following BROAD copyright notice and notice of attribution to contributors. LICENSEE shall retain such notice on all copies. LICENSEE agrees to include appropriate attribution if any results obtained from use of the PROGRAM are included in any publication. +* Copyright 2012 Broad Institute, Inc. +* Notice of attribution: The GATK2 program was made available through the generosity of Medical and Population Genetics program at the Broad Institute, Inc. +* LICENSEE shall not use any trademark or trade name of BROAD, or any variation, adaptation, or abbreviation, of such marks or trade names, or any names of officers, faculty, students, employees, or agents of BROAD except as states above for attribution purposes. +* +* 4. INDEMNIFICATION +* LICENSEE shall indemnify, defend, and hold harmless BROAD, and their respective officers, faculty, students, employees, associated investigators and agents, and their respective successors, heirs and assigns, (Indemnitees), against any liability, damage, loss, or expense (including reasonable attorneys fees and expenses) incurred by or imposed upon any of the Indemnitees in connection with any claims, suits, actions, demands or judgments arising out of any theory of liability (including, without limitation, actions in the form of tort, warranty, or strict liability and regardless of whether such action has any factual basis) pursuant to any right or license granted under this Agreement. +* +* 5. NO REPRESENTATIONS OR WARRANTIES +* THE PROGRAM IS DELIVERED AS IS. BROAD MAKES NO REPRESENTATIONS OR WARRANTIES OF ANY KIND CONCERNING THE PROGRAM OR THE COPYRIGHT, EXPRESS OR IMPLIED, INCLUDING, WITHOUT LIMITATION, WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NONINFRINGEMENT, OR THE ABSENCE OF LATENT OR OTHER DEFECTS, WHETHER OR NOT DISCOVERABLE. BROAD EXTENDS NO WARRANTIES OF ANY KIND AS TO PROGRAM CONFORMITY WITH WHATEVER USER MANUALS OR OTHER LITERATURE MAY BE ISSUED FROM TIME TO TIME. +* IN NO EVENT SHALL BROAD OR ITS RESPECTIVE DIRECTORS, OFFICERS, EMPLOYEES, AFFILIATED INVESTIGATORS AND AFFILIATES BE LIABLE FOR INCIDENTAL OR CONSEQUENTIAL DAMAGES OF ANY KIND, INCLUDING, WITHOUT LIMITATION, ECONOMIC DAMAGES OR INJURY TO PROPERTY AND LOST PROFITS, REGARDLESS OF WHETHER BROAD SHALL BE ADVISED, SHALL HAVE OTHER REASON TO KNOW, OR IN FACT SHALL KNOW OF THE POSSIBILITY OF THE FOREGOING. +* +* 6. ASSIGNMENT +* This Agreement is personal to LICENSEE and any rights or obligations assigned by LICENSEE without the prior written consent of BROAD shall be null and void. +* +* 7. MISCELLANEOUS +* 7.1 Export Control. LICENSEE gives assurance that it will comply with all United States export control laws and regulations controlling the export of the PROGRAM, including, without limitation, all Export Administration Regulations of the United States Department of Commerce. Among other things, these laws and regulations prohibit, or require a license for, the export of certain types of software to specified countries. +* 7.2 Termination. LICENSEE shall have the right to terminate this Agreement for any reason upon prior written notice to BROAD. If LICENSEE breaches any provision hereunder, and fails to cure such breach within thirty (30) days, BROAD may terminate this Agreement immediately. Upon termination, LICENSEE shall provide BROAD with written assurance that the original and all copies of the PROGRAM have been destroyed, except that, upon prior written authorization from BROAD, LICENSEE may retain a copy for archive purposes. +* 7.3 Survival. The following provisions shall survive the expiration or termination of this Agreement: Articles 1, 3, 4, 5 and Sections 2.2, 2.3, 7.3, and 7.4. +* 7.4 Notice. Any notices under this Agreement shall be in writing, shall specifically refer to this Agreement, and shall be sent by hand, recognized national overnight courier, confirmed facsimile transmission, confirmed electronic mail, or registered or certified mail, postage prepaid, return receipt requested. All notices under this Agreement shall be deemed effective upon receipt. +* 7.5 Amendment and Waiver; Entire Agreement. This Agreement may be amended, supplemented, or otherwise modified only by means of a written instrument signed by all parties. Any waiver of any rights or failure to act in a specific instance shall relate only to such instance and shall not be construed as an agreement to waive any rights or fail to act in any other instance, whether or not similar. This Agreement constitutes the entire agreement among the parties with respect to its subject matter and supersedes prior agreements or understandings between the parties relating to its subject matter. +* 7.6 Binding Effect; Headings. This Agreement shall be binding upon and inure to the benefit of the parties and their respective permitted successors and assigns. All headings are for convenience only and shall not affect the meaning of any provision of this Agreement. +* 7.7 Governing Law. This Agreement shall be construed, governed, interpreted and applied in accordance with the internal laws of the Commonwealth of Massachusetts, U.S.A., without regard to conflict of laws principles. +*/ + +package org.broadinstitute.sting.gatk.walkers.haplotypecaller.readthreading; + +import org.apache.log4j.Logger; +import org.broadinstitute.sting.gatk.walkers.haplotypecaller.KMerCounter; +import org.broadinstitute.sting.gatk.walkers.haplotypecaller.Kmer; +import org.broadinstitute.sting.gatk.walkers.haplotypecaller.graphs.*; +import org.broadinstitute.sting.utils.collections.Pair; +import org.broadinstitute.sting.utils.collections.PrimitivePair; +import org.broadinstitute.sting.utils.sam.GATKSAMRecord; +import org.jgrapht.EdgeFactory; + +import java.io.File; +import java.util.*; + +public class ReadThreadingGraph extends BaseGraph { + /** + * Edge factory that creates non-reference multiplicity 1 edges + */ + private static class MyEdgeFactory implements EdgeFactory { + @Override + public MultiSampleEdge createEdge(MultiDeBruijnVertex sourceVertex, MultiDeBruijnVertex targetVertex) { + return new MultiSampleEdge(false, 1); + } + } + + private final static Logger logger = Logger.getLogger(ReadThreadingGraph.class); + + private final static String ANONYMOUS_SAMPLE = "XXX_UNNAMED_XXX"; + private final static boolean WRITE_GRAPH = false; + private final static boolean DEBUG_NON_UNIQUE_CALC = false; + + /** for debugging info printing */ + private static int counter = 0; + + /** we require at least this many bases to be uniquely matching to merge a dangling tail */ + private final static int MIN_MATCH_LENGTH_TO_RECOVER_DANGLING_TAIL = 5; + + /** + * Sequences added for read threading before we've actually built the graph + */ + private final Map> pending = new LinkedHashMap>(); + + /** + * A set of non-unique kmers that cannot be used as merge points in the graph + */ + private Set nonUniqueKmers; + + /** + * A map from kmers -> their corresponding vertex in the graph + */ + private Map uniqueKmers = new LinkedHashMap(); + + /** + * + */ + final int kmerSize; + final boolean debugGraphTransformations; + final byte minBaseQualityToUseInAssembly; + + protected boolean increaseCountsBackwards = true; + protected boolean increaseCountsThroughBranches = false; // this may increase the branches without bounds + + // -------------------------------------------------------------------------------- + // state variables, initialized in resetToInitialState() + // -------------------------------------------------------------------------------- + private Kmer refSource; + private boolean alreadyBuilt; + byte[] refSeq; + MultiDeBruijnVertex[] refKmers; + + public ReadThreadingGraph() { + this(25, false, (byte)6); + } + + public ReadThreadingGraph(final int kmerSize) { + this(kmerSize, false, (byte)6); + } + + /** + * Create a new ReadThreadingAssembler using kmerSize for matching + * @param kmerSize must be >= 1 + */ + protected ReadThreadingGraph(final int kmerSize, final boolean debugGraphTransformations, final byte minBaseQualityToUseInAssembly) { + super(kmerSize, new MyEdgeFactory()); + + if ( kmerSize < 1 ) throw new IllegalArgumentException("bad minkKmerSize " + kmerSize); + this.kmerSize = kmerSize; + this.debugGraphTransformations = debugGraphTransformations; + this.minBaseQualityToUseInAssembly = minBaseQualityToUseInAssembly; + + resetToInitialState(); + } + + /** + * Reset this assembler to its initial state, so we can create another assembly with a different set of reads + */ + private void resetToInitialState() { + pending.clear(); + nonUniqueKmers = null; + uniqueKmers.clear(); + refSource = null; + alreadyBuilt = false; + refSeq = null; + refKmers = null; + } + + /** + * Add the all bases in sequence to the graph + * @param sequence a non-null sequence + * @param isRef is this the reference sequence? + */ + protected void addSequence(final byte[] sequence, final boolean isRef) { + addSequence("anonymous", sequence, null, isRef); + } + + /** + * Add all bases in sequence to this graph + * + * @see #addSequence(String, String, byte[], int, int, int[], boolean) for full information + */ + public void addSequence(final String seqName, final byte[] sequence, final int[] counts, final boolean isRef) { + addSequence(seqName, ANONYMOUS_SAMPLE, sequence, 0, sequence.length, counts, isRef); + } + + /** + * Add bases in sequence to this graph + * + * @param seqName a useful seqName for this read, for debugging purposes + * @param sequence non-null sequence of bases + * @param counts a vector of counts for each bases, indicating how many times that base was observed in the sequence. + * This allows us to support reduced reads in the ReadThreadingAssembler. Can be null, meaning that + * each base is only observed once. If not null, must have length == sequence.length. + * @param start the first base offset in sequence that we should use for constructing the graph using this sequence, inclusive + * @param stop the last base offset in sequence that we should use for constructing the graph using this sequence, exclusive + * @param isRef is this the reference sequence. + */ + public void addSequence(final String seqName, final String sampleName, final byte[] sequence, final int start, final int stop, final int[] counts, final boolean isRef) { + // note that argument testing is taken care of in SequenceForKmers + if ( alreadyBuilt ) throw new IllegalStateException("Graph already built"); + + // get the list of sequences for this sample + List sampleSequences = pending.get(sampleName); + if ( sampleSequences == null ) { // need to create + sampleSequences = new LinkedList<>(); + pending.put(sampleName, sampleSequences); + } + + // add the new sequence to the list of sequences for sample + sampleSequences.add(new SequenceForKmers(seqName, sequence, start, stop, counts, isRef)); + } + + /** + * Return a count appropriate for a kmer starting at kmerStart in sequence for kmers + * + * @param seqForKmers a non-null sequence for kmers object + * @param kmerStart the position where the kmer starts in sequence + * @return a count for a kmer from start -> start + kmerSize in seqForKmers + */ + private int getCountGivenKmerStart(final SequenceForKmers seqForKmers, final int kmerStart) { + return seqForKmers.getCount(kmerStart + kmerSize - 1); + } + + /** + * Thread sequence seqForKmers through the current graph, updating the graph as appropriate + * @param seqForKmers a non-null sequence + */ + private void threadSequence(final SequenceForKmers seqForKmers) { + final Pair startingInfo = findStart(seqForKmers); + if ( startingInfo == null ) + return; + + final MultiDeBruijnVertex startingVertex = startingInfo.getFirst(); + final int uniqueStartPos = startingInfo.getSecond(); + + // increase the counts of all edges incoming into the starting vertex supported by going back in sequence + if ( increaseCountsBackwards ) + increaseCountsInMatchedKmers(seqForKmers, startingVertex, startingVertex.getSequence(), kmerSize - 2); + + if ( debugGraphTransformations ) startingVertex.addRead(seqForKmers.name); + + // keep track of information about the reference kmers for merging dangling tails + if ( seqForKmers.isRef ) { + if ( refSource != null ) throw new IllegalStateException("Found two refSources! prev " + refSource + " new is " + startingVertex); + refSource = new Kmer(seqForKmers.sequence, seqForKmers.start, kmerSize); + refSeq = seqForKmers.sequence; + refKmers = new MultiDeBruijnVertex[refSeq.length]; + for ( int i = 0; i < kmerSize; i++ ) refKmers[i] = null; + } + + // loop over all of the bases in sequence, extending the graph by one base at each point, as appropriate + MultiDeBruijnVertex vertex = startingVertex; + for ( int i = uniqueStartPos + 1; i <= seqForKmers.stop - kmerSize; i++ ) { + final int count = getCountGivenKmerStart(seqForKmers, i); + + vertex = extendChainByOne(vertex, seqForKmers.sequence, i, count, seqForKmers.isRef); + if ( debugGraphTransformations ) vertex.addRead(seqForKmers.name); + + // keep track of the reference kmers for merging dangling tails + if ( seqForKmers.isRef ) refKmers[i + kmerSize - 1] = vertex; + } + } + + /** + * Attempt to attach vertex with out-degree == 0 to the graph by finding a unique matching kmer to the reference + * @param vertex the vertex to recover + */ + protected int recoverDanglingChain(final MultiDeBruijnVertex vertex) { + if ( outDegreeOf(vertex) != 0 ) throw new IllegalStateException("Attempting to recover a dangling tail for " + vertex + " but it has out-degree > 0"); + + final byte[] kmer = vertex.getSequence(); + if ( ! nonUniqueKmers.contains(new Kmer(kmer)) ) { + // don't attempt to fix non-unique kmers! + final MultiDeBruijnVertex uniqueMergePoint = danglingTailMergePoint(kmer); + if ( uniqueMergePoint != null ) { + addEdge(vertex, uniqueMergePoint, new MultiSampleEdge(false, 1)); + return 1; + } + } + + return 0; + } + + /** + * Find a unique merge point for kmer in the reference sequence + * @param kmer the full kmer of the dangling tail + * @return a vertex appropriate to merge kmer into, or null if none could be found + */ + private MultiDeBruijnVertex danglingTailMergePoint(final byte[] kmer) { + final PrimitivePair.Int endAndLength = GraphUtils.findLongestUniqueSuffixMatch(refSeq, kmer); + if ( endAndLength != null && endAndLength.second >= MIN_MATCH_LENGTH_TO_RECOVER_DANGLING_TAIL && endAndLength.first + 1 < refKmers.length) { + final int len = endAndLength.second; + final MultiDeBruijnVertex mergePoint = refKmers[endAndLength.first + 1]; +// logger.info("recoverDanglingChain of kmer " + new String(kmer) + " merged to " + mergePoint + " with match size " + len); + final Set nonUniquesAtLength = determineKmerSizeAndNonUniques(len, len).nonUniques; + final Kmer matchedKmer = new Kmer(kmer, kmer.length - len, len); + if ( nonUniquesAtLength.contains(matchedKmer) ) { +// logger.info("Rejecting merge " + new String(kmer) + " because match kmer " + matchedKmer + " isn't unique across all reads"); + return null; + } else { + return mergePoint; + } + } + + return null; + } + + /** + * Build the read threaded assembly graph if it hasn't already been constructed from the sequences that have + * been added to the graph. + */ + public void buildGraphIfNecessary() { + if ( alreadyBuilt ) return; + + // determine the kmer size we'll uses, and capture the set of nonUniques for that kmer size + final NonUniqueResult result = determineKmerSizeAndNonUniques(kmerSize, kmerSize); + nonUniqueKmers = result.nonUniques; + + if ( DEBUG_NON_UNIQUE_CALC ) { + logger.info("using " + kmerSize + " kmer size for this assembly with the following non-uniques"); + } + + // go through the pending sequences, and add them to the graph + for ( final List sequencesForSample : pending.values() ) { + for ( final SequenceForKmers sequenceForKmers : sequencesForSample ) { + threadSequence(sequenceForKmers); + if ( WRITE_GRAPH ) printGraph(new File("threading." + counter++ + "." + sequenceForKmers.name.replace(" ", "_") + ".dot"), 0); + } + + // flush the single sample edge values from the graph + for ( final MultiSampleEdge e : edgeSet() ) e.flushSingleSampleMultiplicity(); + } + + // clear + pending.clear(); + alreadyBuilt = true; + } + + public void recoverDanglingTails() { + if ( ! alreadyBuilt ) throw new IllegalStateException("recoverDanglingTails requires the graph be already built"); + + int attempted = 0; + int nRecovered = 0; + for ( final MultiDeBruijnVertex v : vertexSet() ) { + if ( outDegreeOf(v) == 0 && ! isRefNodeAndRefSink(v) ) { + attempted++; + nRecovered += recoverDanglingChain(v); + } + } + //logger.info("Recovered " + nRecovered + " of " + attempted + " dangling tails"); + } + + /** structure that keeps track of the non-unique kmers for a given kmer size */ + private static class NonUniqueResult { + final Set nonUniques; + final int kmerSize; + + private NonUniqueResult(Set nonUniques, int kmerSize) { + this.nonUniques = nonUniques; + this.kmerSize = kmerSize; + } + } + + /** + * Compute the smallest kmer size >= minKmerSize and <= maxKmerSize that has no non-unique kmers + * among all sequences added to the current graph. Will always return a result for maxKmerSize if + * all smaller kmers had non-unique kmers. + * + * @param minKmerSize the minimum kmer size to consider when constructing the graph + * @param maxKmerSize the maximum kmer size to consider + * @return a non-null NonUniqueResult + */ + protected NonUniqueResult determineKmerSizeAndNonUniques(final int minKmerSize, final int maxKmerSize) { + final Collection withNonUniques = getAllPendingSequences(); + final Set nonUniqueKmers = new HashSet(); + + // go through the sequences and determine which kmers aren't unique within each read + int kmerSize = minKmerSize; + for ( ; kmerSize <= maxKmerSize; kmerSize++) { + // clear out set of non-unique kmers + nonUniqueKmers.clear(); + + // loop over all sequences that have non-unique kmers in them from the previous iterator + final Iterator it = withNonUniques.iterator(); + while ( it.hasNext() ) { + final SequenceForKmers sequenceForKmers = it.next(); + + // determine the non-unique kmers for this sequence + final Collection nonUniquesFromSeq = determineNonUniqueKmers(sequenceForKmers, kmerSize); + if ( nonUniquesFromSeq.isEmpty() ) { + // remove this sequence from future consideration + it.remove(); + } else { + // keep track of the non-uniques for this kmerSize, and keep it in the list of sequences that have non-uniques + nonUniqueKmers.addAll(nonUniquesFromSeq); + } + } + + if ( nonUniqueKmers.isEmpty() ) + // this kmerSize produces no non-unique sequences, so go ahead and use it for our assembly + break; + } + + // necessary because the loop breaks with kmerSize = max + 1 + return new NonUniqueResult(nonUniqueKmers, Math.min(kmerSize, maxKmerSize)); + } + + /** + * Get the collection of all sequences for kmers across all samples in no particular order + * @return non-null Collection + */ + private Collection getAllPendingSequences() { + final LinkedList result = new LinkedList(); + for ( final List oneSampleWorth : pending.values() ) result.addAll(oneSampleWorth); + return result; + } + + /** + * Get the collection of non-unique kmers from sequence for kmer size kmerSize + * @param seqForKmers a sequence to get kmers from + * @param kmerSize the size of the kmers + * @return a non-null collection of non-unique kmers in sequence + */ + private Collection determineNonUniqueKmers(final SequenceForKmers seqForKmers, final int kmerSize) { + // count up occurrences of kmers within each read + final KMerCounter counter = new KMerCounter(kmerSize); + for ( int i = 0; i <= seqForKmers.stop - kmerSize; i++ ) { + final Kmer kmer = new Kmer(seqForKmers.sequence, i, kmerSize); + counter.addKmer(kmer, 1); + } + + return counter.getKmersWithCountsAtLeast(2); + } + + /** + * Convert this kmer graph to a simple sequence graph. + * + * Each kmer suffix shows up as a distinct SeqVertex, attached in the same structure as in the kmer + * graph. Nodes that are sources are mapped to SeqVertex nodes that contain all of their sequence + * + * @return a newly allocated SequenceGraph + */ + // TODO -- should override base class method + public SeqGraph convertToSequenceGraph() { + buildGraphIfNecessary(); + + final SeqGraph seqGraph = new SeqGraph(kmerSize); + final Map vertexMap = new HashMap(); + + // create all of the equivalent seq graph vertices + for ( final MultiDeBruijnVertex dv : vertexSet() ) { + final SeqVertex sv = new SeqVertex(dv.getAdditionalSequence(isSource(dv))); + sv.setAdditionalInfo(dv.additionalInfo()); + vertexMap.put(dv, sv); + seqGraph.addVertex(sv); + } + + // walk through the nodes and connect them to their equivalent seq vertices + for( final MultiSampleEdge e : edgeSet() ) { + final SeqVertex seqInV = vertexMap.get(getEdgeSource(e)); + final SeqVertex seqOutV = vertexMap.get(getEdgeTarget(e)); + //logger.info("Adding edge " + seqInV + " -> " + seqOutV); + seqGraph.addEdge(seqInV, seqOutV, new BaseEdge(e.isRef(), e.getMultiplicity())); + } + + return seqGraph; + } + + private void increaseCountsInMatchedKmers(final SequenceForKmers seqForKmers, + final MultiDeBruijnVertex vertex, + final byte[] originalKmer, + final int offset) { + if ( offset == -1 ) return; + + for ( final MultiSampleEdge edge : incomingEdgesOf(vertex) ) { + final MultiDeBruijnVertex prev = getEdgeSource(edge); + final byte suffix = prev.getSuffix(); + final byte seqBase = originalKmer[offset]; +// logger.warn(String.format("Increasing counts for %s -> %s via %s at %d with suffix %s vs. %s", +// prev, vertex, edge, offset, (char)suffix, (char)seqBase)); + if ( suffix == seqBase && (increaseCountsThroughBranches || inDegreeOf(vertex) == 1) ) { + edge.incMultiplicity(seqForKmers.getCount(offset)); + increaseCountsInMatchedKmers(seqForKmers, prev, originalKmer, offset-1); + } + } + } + + /** + * Find vertex and its position in seqForKmers where we should start assembling seqForKmers + * + * @param seqForKmers the sequence we want to thread into the graph + * @return a pair of the starting vertex and its position in seqForKmer + */ + private Pair findStart(final SequenceForKmers seqForKmers) { + final int uniqueStartPos = seqForKmers.isRef ? 0 : findUniqueStartPosition(seqForKmers.sequence, seqForKmers.start, seqForKmers.stop); + + if ( uniqueStartPos == -1 ) + return null; + + return getOrCreateKmerVertex(seqForKmers.sequence, uniqueStartPos, true); + } + + /** + * Find a starting point in sequence that begins a unique kmer among all kmers in the graph + * @param sequence the sequence of bases + * @param start the first base to use in sequence + * @param stop the last base to use in sequence + * @return the index into sequence that begins a unique kmer of size kmerSize, or -1 if none could be found + */ + private int findUniqueStartPosition(final byte[] sequence, final int start, final int stop) { + for ( int i = start; i < stop - kmerSize; i++ ) { + final Kmer kmer1 = new Kmer(sequence, i, kmerSize); + if ( uniqueKmers.containsKey(kmer1) ) + return i; + } + return -1; + } + + /** + * Get the vertex for the kmer in sequence starting at start + * @param sequence the sequence + * @param start the position of the kmer start + * @param allowRefSource if true, we will allow matches to the kmer that represents the reference starting kmer + * @return a non-null vertex + */ + private Pair getOrCreateKmerVertex(final byte[] sequence, final int start, final boolean allowRefSource) { + final Kmer kmer = new Kmer(sequence, start, kmerSize); + final MultiDeBruijnVertex vertex = getUniqueKmerVertex(kmer, allowRefSource); + if ( vertex != null ) { + return new Pair<>(vertex, start); + } else { + return new Pair<>(createVertex(kmer), start); + } + } + + /** + * Get the unique vertex for kmer, or null if not possible. + * + * @param allowRefSource if true, we will allow kmer to match the reference source vertex + * @return a vertex for kmer, or null if it's not unique + */ + private MultiDeBruijnVertex getUniqueKmerVertex(final Kmer kmer, final boolean allowRefSource) { + if ( ! allowRefSource && kmer.equals(refSource) ) return null; + return uniqueKmers.get(kmer); + } + + /** + * Create a new vertex for kmer. Add it to the uniqueKmers map if appropriate. + * + * kmer must not have a entry in unique kmers, or an error will be thrown + * + * @param kmer the kmer we want to create a vertex for + * @return the non-null created vertex + */ + private MultiDeBruijnVertex createVertex(final Kmer kmer) { + final MultiDeBruijnVertex newVertex = new MultiDeBruijnVertex(kmer.bases()); + final int prevSize = vertexSet().size(); + addVertex(newVertex); + + // make sure we aren't adding duplicates (would be a bug) + if ( vertexSet().size() != prevSize + 1) throw new IllegalStateException("Adding vertex " + newVertex + " to graph didn't increase the graph size"); + + // add the vertex to the unique kmer map, if it is in fact unique + if ( ! nonUniqueKmers.contains(kmer) && ! uniqueKmers.containsKey(kmer) ) // TODO -- not sure this last test is necessary + uniqueKmers.put(kmer, newVertex); + + return newVertex; + } + + /** + * Workhorse routine of the assembler. Given a sequence whose last vertex is anchored in the graph, extend + * the graph one bp according to the bases in sequence. + * + * @param prevVertex a non-null vertex where sequence was last anchored in the graph + * @param sequence the sequence we're threading through the graph + * @param kmerStart the start of the current kmer in graph we'd like to add + * @param count the number of observations of this kmer in graph (can be > 1 for reduced reads) + * @param isRef is this the reference sequence? + * @return a non-null vertex connecting prevVertex to in the graph based on sequence + */ + private MultiDeBruijnVertex extendChainByOne(final MultiDeBruijnVertex prevVertex, final byte[] sequence, final int kmerStart, final int count, final boolean isRef) { + final Set outgoingEdges = outgoingEdgesOf(prevVertex); + + final int nextPos = kmerStart + kmerSize - 1; + for ( final MultiSampleEdge outgoingEdge : outgoingEdges ) { + final MultiDeBruijnVertex target = getEdgeTarget(outgoingEdge); + if ( target.getSuffix() == sequence[nextPos] ) { + // we've got a match in the chain, so simply increase the count of the edge by 1 and continue + outgoingEdge.incMultiplicity(count); + return target; + } + } + + // none of our outgoing edges had our unique suffix base, so we check for an opportunity to merge back in + final Kmer kmer = new Kmer(sequence, kmerStart, kmerSize); + MultiDeBruijnVertex uniqueMergeVertex = getUniqueKmerVertex(kmer, false); + + if ( isRef && uniqueMergeVertex != null ) + throw new IllegalStateException("Found a unique vertex to merge into the reference graph " + prevVertex + " -> " + uniqueMergeVertex); + + // either use our unique merge vertex, or create a new one in the chain + final MultiDeBruijnVertex nextVertex = uniqueMergeVertex == null ? createVertex(kmer) : uniqueMergeVertex; + addEdge(prevVertex, nextVertex, new MultiSampleEdge(isRef, count)); + return nextVertex; + } + + /** + * Get the start and stop positions (exclusive) of the longest stretch of high quality bases + * in read + * + * @param read a non-null read + * @return the start and stop for high quality bases in read, or null if none exist + */ + protected void addRead(final GATKSAMRecord read) { + final byte[] sequence = read.getReadBases(); + final byte[] qualities = read.getBaseQualities(); + final int[] reducedReadCounts = read.getReducedReadCounts(); // will be null if read is not reduced + + int lastGood = -1; // the index of the last good base we've seen + for( int end = 0; end <= sequence.length; end++ ) { + if ( end == sequence.length || qualities[end] < minBaseQualityToUseInAssembly ) { + // the first good base is at lastGood, can be -1 if last base was bad + final int start = lastGood; + // the stop base is end - 1 (if we're not at the end of the sequence) + final int stop = end == sequence.length ? sequence.length : end; + final int len = stop - start + 1; + + if ( start != -1 && len >= kmerSize ) { + // if the sequence is long enough to get some value out of, add it to the graph + final String name = read.getReadName() + "_" + start + "_" + end; + addSequence(name, read.getReadGroup().getSample(), read.getReadBases(), start, stop, reducedReadCounts, false); + } + + lastGood = -1; // reset the last good base + } else if ( lastGood == -1 ) { + lastGood = end; // we're at a good base, the last good one is us + } + } + } + + /** + * Get the set of non-unique kmers in this graph. For debugging purposes + * @return a non-null set of kmers + */ + protected Set getNonUniqueKmers() { + return nonUniqueKmers; + } + + @Override + public String toString() { + return "ReadThreadingAssembler{" + + "kmerSize=" + kmerSize + + '}'; + } +} \ No newline at end of file diff --git a/protected/java/src/org/broadinstitute/sting/gatk/walkers/haplotypecaller/readthreading/SequenceForKmers.java b/protected/java/src/org/broadinstitute/sting/gatk/walkers/haplotypecaller/readthreading/SequenceForKmers.java new file mode 100644 index 000000000..a4bc0c1c8 --- /dev/null +++ b/protected/java/src/org/broadinstitute/sting/gatk/walkers/haplotypecaller/readthreading/SequenceForKmers.java @@ -0,0 +1,93 @@ +/* +* By downloading the PROGRAM you agree to the following terms of use: +* +* BROAD INSTITUTE - SOFTWARE LICENSE AGREEMENT - FOR ACADEMIC NON-COMMERCIAL RESEARCH PURPOSES ONLY +* +* This Agreement is made between the Broad Institute, Inc. with a principal address at 7 Cambridge Center, Cambridge, MA 02142 (BROAD) and the LICENSEE and is effective at the date the downloading is completed (EFFECTIVE DATE). +* +* WHEREAS, LICENSEE desires to license the PROGRAM, as defined hereinafter, and BROAD wishes to have this PROGRAM utilized in the public interest, subject only to the royalty-free, nonexclusive, nontransferable license rights of the United States Government pursuant to 48 CFR 52.227-14; and +* WHEREAS, LICENSEE desires to license the PROGRAM and BROAD desires to grant a license on the following terms and conditions. +* NOW, THEREFORE, in consideration of the promises and covenants made herein, the parties hereto agree as follows: +* +* 1. DEFINITIONS +* 1.1 PROGRAM shall mean copyright in the object code and source code known as GATK2 and related documentation, if any, as they exist on the EFFECTIVE DATE and can be downloaded from http://www.broadinstitute/GATK on the EFFECTIVE DATE. +* +* 2. LICENSE +* 2.1 Grant. Subject to the terms of this Agreement, BROAD hereby grants to LICENSEE, solely for academic non-commercial research purposes, a non-exclusive, non-transferable license to: (a) download, execute and display the PROGRAM and (b) create bug fixes and modify the PROGRAM. +* The LICENSEE may apply the PROGRAM in a pipeline to data owned by users other than the LICENSEE and provide these users the results of the PROGRAM provided LICENSEE does so for academic non-commercial purposes only. For clarification purposes, academic sponsored research is not a commercial use under the terms of this Agreement. +* 2.2 No Sublicensing or Additional Rights. LICENSEE shall not sublicense or distribute the PROGRAM, in whole or in part, without prior written permission from BROAD. LICENSEE shall ensure that all of its users agree to the terms of this Agreement. LICENSEE further agrees that it shall not put the PROGRAM on a network, server, or other similar technology that may be accessed by anyone other than the LICENSEE and its employees and users who have agreed to the terms of this agreement. +* 2.3 License Limitations. Nothing in this Agreement shall be construed to confer any rights upon LICENSEE by implication, estoppel, or otherwise to any computer software, trademark, intellectual property, or patent rights of BROAD, or of any other entity, except as expressly granted herein. LICENSEE agrees that the PROGRAM, in whole or part, shall not be used for any commercial purpose, including without limitation, as the basis of a commercial software or hardware product or to provide services. LICENSEE further agrees that the PROGRAM shall not be copied or otherwise adapted in order to circumvent the need for obtaining a license for use of the PROGRAM. +* +* 3. OWNERSHIP OF INTELLECTUAL PROPERTY +* LICENSEE acknowledges that title to the PROGRAM shall remain with BROAD. The PROGRAM is marked with the following BROAD copyright notice and notice of attribution to contributors. LICENSEE shall retain such notice on all copies. LICENSEE agrees to include appropriate attribution if any results obtained from use of the PROGRAM are included in any publication. +* Copyright 2012 Broad Institute, Inc. +* Notice of attribution: The GATK2 program was made available through the generosity of Medical and Population Genetics program at the Broad Institute, Inc. +* LICENSEE shall not use any trademark or trade name of BROAD, or any variation, adaptation, or abbreviation, of such marks or trade names, or any names of officers, faculty, students, employees, or agents of BROAD except as states above for attribution purposes. +* +* 4. INDEMNIFICATION +* LICENSEE shall indemnify, defend, and hold harmless BROAD, and their respective officers, faculty, students, employees, associated investigators and agents, and their respective successors, heirs and assigns, (Indemnitees), against any liability, damage, loss, or expense (including reasonable attorneys fees and expenses) incurred by or imposed upon any of the Indemnitees in connection with any claims, suits, actions, demands or judgments arising out of any theory of liability (including, without limitation, actions in the form of tort, warranty, or strict liability and regardless of whether such action has any factual basis) pursuant to any right or license granted under this Agreement. +* +* 5. NO REPRESENTATIONS OR WARRANTIES +* THE PROGRAM IS DELIVERED AS IS. BROAD MAKES NO REPRESENTATIONS OR WARRANTIES OF ANY KIND CONCERNING THE PROGRAM OR THE COPYRIGHT, EXPRESS OR IMPLIED, INCLUDING, WITHOUT LIMITATION, WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NONINFRINGEMENT, OR THE ABSENCE OF LATENT OR OTHER DEFECTS, WHETHER OR NOT DISCOVERABLE. BROAD EXTENDS NO WARRANTIES OF ANY KIND AS TO PROGRAM CONFORMITY WITH WHATEVER USER MANUALS OR OTHER LITERATURE MAY BE ISSUED FROM TIME TO TIME. +* IN NO EVENT SHALL BROAD OR ITS RESPECTIVE DIRECTORS, OFFICERS, EMPLOYEES, AFFILIATED INVESTIGATORS AND AFFILIATES BE LIABLE FOR INCIDENTAL OR CONSEQUENTIAL DAMAGES OF ANY KIND, INCLUDING, WITHOUT LIMITATION, ECONOMIC DAMAGES OR INJURY TO PROPERTY AND LOST PROFITS, REGARDLESS OF WHETHER BROAD SHALL BE ADVISED, SHALL HAVE OTHER REASON TO KNOW, OR IN FACT SHALL KNOW OF THE POSSIBILITY OF THE FOREGOING. +* +* 6. ASSIGNMENT +* This Agreement is personal to LICENSEE and any rights or obligations assigned by LICENSEE without the prior written consent of BROAD shall be null and void. +* +* 7. MISCELLANEOUS +* 7.1 Export Control. LICENSEE gives assurance that it will comply with all United States export control laws and regulations controlling the export of the PROGRAM, including, without limitation, all Export Administration Regulations of the United States Department of Commerce. Among other things, these laws and regulations prohibit, or require a license for, the export of certain types of software to specified countries. +* 7.2 Termination. LICENSEE shall have the right to terminate this Agreement for any reason upon prior written notice to BROAD. If LICENSEE breaches any provision hereunder, and fails to cure such breach within thirty (30) days, BROAD may terminate this Agreement immediately. Upon termination, LICENSEE shall provide BROAD with written assurance that the original and all copies of the PROGRAM have been destroyed, except that, upon prior written authorization from BROAD, LICENSEE may retain a copy for archive purposes. +* 7.3 Survival. The following provisions shall survive the expiration or termination of this Agreement: Articles 1, 3, 4, 5 and Sections 2.2, 2.3, 7.3, and 7.4. +* 7.4 Notice. Any notices under this Agreement shall be in writing, shall specifically refer to this Agreement, and shall be sent by hand, recognized national overnight courier, confirmed facsimile transmission, confirmed electronic mail, or registered or certified mail, postage prepaid, return receipt requested. All notices under this Agreement shall be deemed effective upon receipt. +* 7.5 Amendment and Waiver; Entire Agreement. This Agreement may be amended, supplemented, or otherwise modified only by means of a written instrument signed by all parties. Any waiver of any rights or failure to act in a specific instance shall relate only to such instance and shall not be construed as an agreement to waive any rights or fail to act in any other instance, whether or not similar. This Agreement constitutes the entire agreement among the parties with respect to its subject matter and supersedes prior agreements or understandings between the parties relating to its subject matter. +* 7.6 Binding Effect; Headings. This Agreement shall be binding upon and inure to the benefit of the parties and their respective permitted successors and assigns. All headings are for convenience only and shall not affect the meaning of any provision of this Agreement. +* 7.7 Governing Law. This Agreement shall be construed, governed, interpreted and applied in accordance with the internal laws of the Commonwealth of Massachusetts, U.S.A., without regard to conflict of laws principles. +*/ + +package org.broadinstitute.sting.gatk.walkers.haplotypecaller.readthreading; + +/** + * Keeps track of the information needed to add a sequence to the read threading assembly graph + * + * User: depristo + * Date: 4/18/13 + * Time: 8:59 AM + * To change this template use File | Settings | File Templates. + */ +final class SequenceForKmers { + final String name; + final byte[] sequence; + final int start, stop; + final private int[] counts; + final boolean isRef; + + /** + * Create a new sequence for creating kmers + */ + SequenceForKmers(final String name, byte[] sequence, int start, int stop, int[] counts, boolean ref) { + if ( start < 0 ) throw new IllegalArgumentException("Invalid start " + start); + if ( stop < start ) throw new IllegalArgumentException("Invalid stop " + stop); + if ( sequence == null ) throw new IllegalArgumentException("Sequence is null "); + if ( counts != null && counts.length != sequence.length ) throw new IllegalArgumentException("Sequence and counts don't have the same length " + sequence.length + " vs " + counts.length); + + this.name = name; + this.sequence = sequence; + this.start = start; + this.stop = stop; + this.isRef = ref; + this.counts = counts; + } + + /** + * Get the number of observations of the kmer starting at i in this sequence + * + * Can we > 1 because sequence may be a reduced read and therefore count as N observations + * + * @param i the offset into sequence for the start of the kmer + * @return a count >= 1 that indicates the number of observations of kmer starting at i in this sequence. + */ + public int getCount(final int i) { + if ( i < 0 || i > sequence.length ) throw new ArrayIndexOutOfBoundsException("i must be >= 0 and <= " + sequence.length + " but got " + i); + return counts == null ? 1 : counts[i]; + } +} diff --git a/protected/java/test/org/broadinstitute/sting/gatk/walkers/haplotypecaller/DeBruijnAssemblerUnitTest.java b/protected/java/test/org/broadinstitute/sting/gatk/walkers/haplotypecaller/DeBruijnAssemblerUnitTest.java index e1559a13a..c5574577d 100644 --- a/protected/java/test/org/broadinstitute/sting/gatk/walkers/haplotypecaller/DeBruijnAssemblerUnitTest.java +++ b/protected/java/test/org/broadinstitute/sting/gatk/walkers/haplotypecaller/DeBruijnAssemblerUnitTest.java @@ -80,59 +80,6 @@ public class DeBruijnAssemblerUnitTest extends BaseTest { Assert.assertTrue(g2 != null, "Reference non-cycle graph should not return null during creation."); } - @Test(enabled = !DEBUG) - public void testLeftAlignCigarSequentially() { - String preRefString = "GATCGATCGATC"; - String postRefString = "TTT"; - String refString = "ATCGAGGAGAGCGCCCCG"; - String indelString1 = "X"; - String indelString2 = "YZ"; - int refIndel1 = 10; - int refIndel2 = 12; - - for ( final int indelSize1 : Arrays.asList(1, 2, 3, 4) ) { - for ( final int indelOp1 : Arrays.asList(1, -1) ) { - for ( final int indelSize2 : Arrays.asList(1, 2, 3, 4) ) { - for ( final int indelOp2 : Arrays.asList(1, -1) ) { - - Cigar expectedCigar = new Cigar(); - expectedCigar.add(new CigarElement(refString.length(), CigarOperator.M)); - expectedCigar.add(new CigarElement(indelSize1, (indelOp1 > 0 ? CigarOperator.I : CigarOperator.D))); - expectedCigar.add(new CigarElement((indelOp1 < 0 ? refIndel1 - indelSize1 : refIndel1), CigarOperator.M)); - expectedCigar.add(new CigarElement(refString.length(), CigarOperator.M)); - expectedCigar.add(new CigarElement(indelSize2 * 2, (indelOp2 > 0 ? CigarOperator.I : CigarOperator.D))); - expectedCigar.add(new CigarElement((indelOp2 < 0 ? (refIndel2 - indelSize2) * 2 : refIndel2 * 2), CigarOperator.M)); - expectedCigar.add(new CigarElement(refString.length(), CigarOperator.M)); - - Cigar givenCigar = new Cigar(); - givenCigar.add(new CigarElement(refString.length() + refIndel1/2, CigarOperator.M)); - givenCigar.add(new CigarElement(indelSize1, (indelOp1 > 0 ? CigarOperator.I : CigarOperator.D))); - givenCigar.add(new CigarElement((indelOp1 < 0 ? (refIndel1/2 - indelSize1) : refIndel1/2) + refString.length() + refIndel2/2 * 2, CigarOperator.M)); - givenCigar.add(new CigarElement(indelSize2 * 2, (indelOp2 > 0 ? CigarOperator.I : CigarOperator.D))); - givenCigar.add(new CigarElement((indelOp2 < 0 ? (refIndel2/2 - indelSize2) * 2 : refIndel2/2 * 2) + refString.length(), CigarOperator.M)); - - String theRef = preRefString + refString + Utils.dupString(indelString1, refIndel1) + refString + Utils.dupString(indelString2, refIndel2) + refString + postRefString; - String theRead = refString + Utils.dupString(indelString1, refIndel1 + indelOp1 * indelSize1) + refString + Utils.dupString(indelString2, refIndel2 + indelOp2 * indelSize2) + refString; - - Cigar calculatedCigar = new DeBruijnAssembler().leftAlignCigarSequentially(AlignmentUtils.consolidateCigar(givenCigar), theRef.getBytes(), theRead.getBytes(), preRefString.length(), 0); - Assert.assertEquals(AlignmentUtils.consolidateCigar(calculatedCigar).toString(), AlignmentUtils.consolidateCigar(expectedCigar).toString(), "Cigar strings do not match!"); - } - } - } - } - } - - @Test(enabled = true) - public void testLeftAlignCigarSequentiallyAdjacentID() { - final String ref = "GTCTCTCTCTCTCTCTCTATATATATATATATATTT"; - final String hap = "GTCTCTCTCTCTCTCTCTCTCTATATATATATATTT"; - final Cigar originalCigar = TextCigarCodec.getSingleton().decode("18M4I12M4D2M"); - - final Cigar result = new DeBruijnAssembler().leftAlignCigarSequentially(originalCigar, ref.getBytes(), hap.getBytes(), 0, 0); - logger.warn("Result is " + result); - Assert.assertEquals(originalCigar.getReferenceLength(), result.getReferenceLength(), "Reference lengths are different"); - } - private static class MockBuilder extends DeBruijnGraphBuilder { public final List addedPairs = new LinkedList(); diff --git a/protected/java/test/org/broadinstitute/sting/gatk/walkers/haplotypecaller/HaplotypeCallerComplexAndSymbolicVariantsIntegrationTest.java b/protected/java/test/org/broadinstitute/sting/gatk/walkers/haplotypecaller/HaplotypeCallerComplexAndSymbolicVariantsIntegrationTest.java index 9d4c52798..d6c6a4f33 100644 --- a/protected/java/test/org/broadinstitute/sting/gatk/walkers/haplotypecaller/HaplotypeCallerComplexAndSymbolicVariantsIntegrationTest.java +++ b/protected/java/test/org/broadinstitute/sting/gatk/walkers/haplotypecaller/HaplotypeCallerComplexAndSymbolicVariantsIntegrationTest.java @@ -64,7 +64,7 @@ public class HaplotypeCallerComplexAndSymbolicVariantsIntegrationTest extends Wa @Test public void testHaplotypeCallerMultiSampleComplex1() { - HCTestComplexVariants(privateTestDir + "AFR.complex.variants.bam", "", "0bf5ae740bf9bd14c8d60d7849c45eb3"); + HCTestComplexVariants(privateTestDir + "AFR.complex.variants.bam", "", "fc11b553fbf16beac0da04a69f419365"); } private void HCTestSymbolicVariants(String bam, String args, String md5) { @@ -88,12 +88,12 @@ public class HaplotypeCallerComplexAndSymbolicVariantsIntegrationTest extends Wa @Test public void testHaplotypeCallerMultiSampleGGAComplex() { HCTestComplexGGA(NA12878_CHR20_BAM, "-L 20:119673-119823 -L 20:121408-121538", - "7d2cc5c4ece386beedf6b07dfbe5bf26"); + "90cbcc7e959eb591fb7c5e12d65e0e40"); } @Test public void testHaplotypeCallerMultiSampleGGAMultiAllelic() { HCTestComplexGGA(NA12878_CHR20_BAM, "-L 20:133041-133161 -L 20:300207-300337", - "a17856f709b546eaed486841d78248d2"); + "50894abb9d156bf480881cb5cb2a8a7d"); } } diff --git a/protected/java/test/org/broadinstitute/sting/gatk/walkers/haplotypecaller/HaplotypeCallerIntegrationTest.java b/protected/java/test/org/broadinstitute/sting/gatk/walkers/haplotypecaller/HaplotypeCallerIntegrationTest.java index d5e163a88..15516d090 100644 --- a/protected/java/test/org/broadinstitute/sting/gatk/walkers/haplotypecaller/HaplotypeCallerIntegrationTest.java +++ b/protected/java/test/org/broadinstitute/sting/gatk/walkers/haplotypecaller/HaplotypeCallerIntegrationTest.java @@ -80,12 +80,12 @@ public class HaplotypeCallerIntegrationTest extends WalkerTest { @Test public void testHaplotypeCallerMultiSample() { - HCTest(CEUTRIO_BAM, "", "2e10ab97afd4492c2a153b85871a2c2d"); + HCTest(CEUTRIO_BAM, "", "37e462379de17bc6c8aeeed6e9735dd3"); } @Test public void testHaplotypeCallerSingleSample() { - HCTest(NA12878_BAM, "", "affed81386dfe60e0b0d4e7e0525918f"); + HCTest(NA12878_BAM, "", "983a0d122714d4aa0ff7af20cc686703"); } @Test(enabled = false) // can't annotate the rsID's yet @@ -96,7 +96,7 @@ public class HaplotypeCallerIntegrationTest extends WalkerTest { @Test public void testHaplotypeCallerMultiSampleGGA() { HCTest(CEUTRIO_BAM, "--max_alternate_alleles 3 -gt_mode GENOTYPE_GIVEN_ALLELES -out_mode EMIT_ALL_SITES -alleles " + validationDataLocation + "combined.phase1.chr20.raw.indels.sites.vcf", - "e2d32d0dce2c5502a8e877f6bbb65a10"); + "dbbc884a975587d8e7255ce47b58f438"); } @Test @@ -112,7 +112,7 @@ public class HaplotypeCallerIntegrationTest extends WalkerTest { @Test public void testHaplotypeCallerSingleSampleIndelQualityScores() { - HCTestIndelQualityScores(NA12878_RECALIBRATED_BAM, "", "125e91ebe43108b2b514c58a9b6d3a4f"); + HCTestIndelQualityScores(NA12878_RECALIBRATED_BAM, "", "ce602282e80cca6d4272f940e20e90c3"); } private void HCTestNearbySmallIntervals(String bam, String args, String md5) { @@ -149,7 +149,7 @@ public class HaplotypeCallerIntegrationTest extends WalkerTest { @Test public void testHaplotypeCallerNearbySmallIntervals() { - HCTestNearbySmallIntervals(NA12878_BAM, "", "2d295ce36066d9d8d9ee9c67e6e2cbd1"); + HCTestNearbySmallIntervals(NA12878_BAM, "", "09335c01d2e90714af7f4c91156da0b1"); } // This problem bam came from a user on the forum and it spotted a problem where the ReadClipper @@ -159,14 +159,14 @@ public class HaplotypeCallerIntegrationTest extends WalkerTest { @Test public void HCTestProblematicReadsModifiedInActiveRegions() { final String base = String.format("-T HaplotypeCaller --disableDithering -R %s -I %s", REF, privateTestDir + "haplotype-problem-4.bam") + " --no_cmdline_in_header -o %s -minPruning 3 -L 4:49139026-49139965"; - final WalkerTestSpec spec = new WalkerTestSpec(base, Arrays.asList("0689d2c202849fd05617648eaf429b9a")); + final WalkerTestSpec spec = new WalkerTestSpec(base, Arrays.asList("b34ddc93a7b9919e05da499508f44dd9")); executeTest("HCTestProblematicReadsModifiedInActiveRegions: ", spec); } @Test public void HCTestStructuralIndels() { final String base = String.format("-T HaplotypeCaller --disableDithering -R %s -I %s", REF, privateTestDir + "AFR.structural.indels.bam") + " --no_cmdline_in_header -o %s -minPruning 6 -L 20:8187565-8187800 -L 20:18670537-18670730"; - final WalkerTestSpec spec = new WalkerTestSpec(base, Arrays.asList("153d2251de7d22f423cd282b1505fbc0")); + final WalkerTestSpec spec = new WalkerTestSpec(base, Arrays.asList("98a78b9f58ab197b827ef2ce3ab043d3")); executeTest("HCTestStructuralIndels: ", spec); } @@ -188,7 +188,7 @@ public class HaplotypeCallerIntegrationTest extends WalkerTest { public void HCTestReducedBam() { WalkerTest.WalkerTestSpec spec = new WalkerTest.WalkerTestSpec( "-T HaplotypeCaller --disableDithering -R " + b37KGReference + " --no_cmdline_in_header -I " + privateTestDir + "bamExample.ReducedRead.ADAnnotation.bam -o %s -L 1:67,225,396-67,288,518", 1, - Arrays.asList("0c29e4049908ec47a3159dce33d477c3")); + Arrays.asList("6e6ef6e0326bee6d20d9fd37349fdb8c")); executeTest("HC calling on a ReducedRead BAM", spec); } @@ -196,7 +196,7 @@ public class HaplotypeCallerIntegrationTest extends WalkerTest { public void testReducedBamWithReadsNotFullySpanningDeletion() { WalkerTest.WalkerTestSpec spec = new WalkerTest.WalkerTestSpec( "-T HaplotypeCaller --disableDithering -R " + b37KGReference + " --no_cmdline_in_header -I " + privateTestDir + "reduced.readNotFullySpanningDeletion.bam -o %s -L 1:167871297", 1, - Arrays.asList("3306889b8d0735ce575bee281c1b8846")); + Arrays.asList("5e535983b2f7e5fb6c84fecffa092324")); executeTest("test calling on a ReducedRead BAM where the reads do not fully span a deletion", spec); } } diff --git a/protected/java/test/org/broadinstitute/sting/gatk/walkers/haplotypecaller/KMerCounterCaseFixUnitTest.java b/protected/java/test/org/broadinstitute/sting/gatk/walkers/haplotypecaller/KMerCounterCaseFixUnitTest.java index c049121a3..9b08e8214 100644 --- a/protected/java/test/org/broadinstitute/sting/gatk/walkers/haplotypecaller/KMerCounterCaseFixUnitTest.java +++ b/protected/java/test/org/broadinstitute/sting/gatk/walkers/haplotypecaller/KMerCounterCaseFixUnitTest.java @@ -1,48 +1,48 @@ /* - * By downloading the PROGRAM you agree to the following terms of use: - * - * BROAD INSTITUTE - SOFTWARE LICENSE AGREEMENT - FOR ACADEMIC NON-COMMERCIAL RESEARCH PURPOSES ONLY - * - * This Agreement is made between the Broad Institute, Inc. with a principal address at 7 Cambridge Center, Cambridge, MA 02142 (BROAD) and the LICENSEE and is effective at the date the downloading is completed (EFFECTIVE DATE). - * - * WHEREAS, LICENSEE desires to license the PROGRAM, as defined hereinafter, and BROAD wishes to have this PROGRAM utilized in the public interest, subject only to the royalty-free, nonexclusive, nontransferable license rights of the United States Government pursuant to 48 CFR 52.227-14; and - * WHEREAS, LICENSEE desires to license the PROGRAM and BROAD desires to grant a license on the following terms and conditions. - * NOW, THEREFORE, in consideration of the promises and covenants made herein, the parties hereto agree as follows: - * - * 1. DEFINITIONS - * 1.1 PROGRAM shall mean copyright in the object code and source code known as GATK2 and related documentation, if any, as they exist on the EFFECTIVE DATE and can be downloaded from http://www.broadinstitute/GATK on the EFFECTIVE DATE. - * - * 2. LICENSE - * 2.1 Grant. Subject to the terms of this Agreement, BROAD hereby grants to LICENSEE, solely for academic non-commercial research purposes, a non-exclusive, non-transferable license to: (a) download, execute and display the PROGRAM and (b) create bug fixes and modify the PROGRAM. - * The LICENSEE may apply the PROGRAM in a pipeline to data owned by users other than the LICENSEE and provide these users the results of the PROGRAM provided LICENSEE does so for academic non-commercial purposes only. For clarification purposes, academic sponsored research is not a commercial use under the terms of this Agreement. - * 2.2 No Sublicensing or Additional Rights. LICENSEE shall not sublicense or distribute the PROGRAM, in whole or in part, without prior written permission from BROAD. LICENSEE shall ensure that all of its users agree to the terms of this Agreement. LICENSEE further agrees that it shall not put the PROGRAM on a network, server, or other similar technology that may be accessed by anyone other than the LICENSEE and its employees and users who have agreed to the terms of this agreement. - * 2.3 License Limitations. Nothing in this Agreement shall be construed to confer any rights upon LICENSEE by implication, estoppel, or otherwise to any computer software, trademark, intellectual property, or patent rights of BROAD, or of any other entity, except as expressly granted herein. LICENSEE agrees that the PROGRAM, in whole or part, shall not be used for any commercial purpose, including without limitation, as the basis of a commercial software or hardware product or to provide services. LICENSEE further agrees that the PROGRAM shall not be copied or otherwise adapted in order to circumvent the need for obtaining a license for use of the PROGRAM. - * - * 3. OWNERSHIP OF INTELLECTUAL PROPERTY - * LICENSEE acknowledges that title to the PROGRAM shall remain with BROAD. The PROGRAM is marked with the following BROAD copyright notice and notice of attribution to contributors. LICENSEE shall retain such notice on all copies. LICENSEE agrees to include appropriate attribution if any results obtained from use of the PROGRAM are included in any publication. - * Copyright 2012 Broad Institute, Inc. - * Notice of attribution: The GATK2 program was made available through the generosity of Medical and Population Genetics program at the Broad Institute, Inc. - * LICENSEE shall not use any trademark or trade name of BROAD, or any variation, adaptation, or abbreviation, of such marks or trade names, or any names of officers, faculty, students, employees, or agents of BROAD except as states above for attribution purposes. - * - * 4. INDEMNIFICATION - * LICENSEE shall indemnify, defend, and hold harmless BROAD, and their respective officers, faculty, students, employees, associated investigators and agents, and their respective successors, heirs and assigns, (Indemnitees), against any liability, damage, loss, or expense (including reasonable attorneys fees and expenses) incurred by or imposed upon any of the Indemnitees in connection with any claims, suits, actions, demands or judgments arising out of any theory of liability (including, without limitation, actions in the form of tort, warranty, or strict liability and regardless of whether such action has any factual basis) pursuant to any right or license granted under this Agreement. - * - * 5. NO REPRESENTATIONS OR WARRANTIES - * THE PROGRAM IS DELIVERED AS IS. BROAD MAKES NO REPRESENTATIONS OR WARRANTIES OF ANY KIND CONCERNING THE PROGRAM OR THE COPYRIGHT, EXPRESS OR IMPLIED, INCLUDING, WITHOUT LIMITATION, WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NONINFRINGEMENT, OR THE ABSENCE OF LATENT OR OTHER DEFECTS, WHETHER OR NOT DISCOVERABLE. BROAD EXTENDS NO WARRANTIES OF ANY KIND AS TO PROGRAM CONFORMITY WITH WHATEVER USER MANUALS OR OTHER LITERATURE MAY BE ISSUED FROM TIME TO TIME. - * IN NO EVENT SHALL BROAD OR ITS RESPECTIVE DIRECTORS, OFFICERS, EMPLOYEES, AFFILIATED INVESTIGATORS AND AFFILIATES BE LIABLE FOR INCIDENTAL OR CONSEQUENTIAL DAMAGES OF ANY KIND, INCLUDING, WITHOUT LIMITATION, ECONOMIC DAMAGES OR INJURY TO PROPERTY AND LOST PROFITS, REGARDLESS OF WHETHER BROAD SHALL BE ADVISED, SHALL HAVE OTHER REASON TO KNOW, OR IN FACT SHALL KNOW OF THE POSSIBILITY OF THE FOREGOING. - * - * 6. ASSIGNMENT - * This Agreement is personal to LICENSEE and any rights or obligations assigned by LICENSEE without the prior written consent of BROAD shall be null and void. - * - * 7. MISCELLANEOUS - * 7.1 Export Control. LICENSEE gives assurance that it will comply with all United States export control laws and regulations controlling the export of the PROGRAM, including, without limitation, all Export Administration Regulations of the United States Department of Commerce. Among other things, these laws and regulations prohibit, or require a license for, the export of certain types of software to specified countries. - * 7.2 Termination. LICENSEE shall have the right to terminate this Agreement for any reason upon prior written notice to BROAD. If LICENSEE breaches any provision hereunder, and fails to cure such breach within thirty (30) days, BROAD may terminate this Agreement immediately. Upon termination, LICENSEE shall provide BROAD with written assurance that the original and all copies of the PROGRAM have been destroyed, except that, upon prior written authorization from BROAD, LICENSEE may retain a copy for archive purposes. - * 7.3 Survival. The following provisions shall survive the expiration or termination of this Agreement: Articles 1, 3, 4, 5 and Sections 2.2, 2.3, 7.3, and 7.4. - * 7.4 Notice. Any notices under this Agreement shall be in writing, shall specifically refer to this Agreement, and shall be sent by hand, recognized national overnight courier, confirmed facsimile transmission, confirmed electronic mail, or registered or certified mail, postage prepaid, return receipt requested. All notices under this Agreement shall be deemed effective upon receipt. - * 7.5 Amendment and Waiver; Entire Agreement. This Agreement may be amended, supplemented, or otherwise modified only by means of a written instrument signed by all parties. Any waiver of any rights or failure to act in a specific instance shall relate only to such instance and shall not be construed as an agreement to waive any rights or fail to act in any other instance, whether or not similar. This Agreement constitutes the entire agreement among the parties with respect to its subject matter and supersedes prior agreements or understandings between the parties relating to its subject matter. - * 7.6 Binding Effect; Headings. This Agreement shall be binding upon and inure to the benefit of the parties and their respective permitted successors and assigns. All headings are for convenience only and shall not affect the meaning of any provision of this Agreement. - * 7.7 Governing Law. This Agreement shall be construed, governed, interpreted and applied in accordance with the internal laws of the Commonwealth of Massachusetts, U.S.A., without regard to conflict of laws principles. - */ +* By downloading the PROGRAM you agree to the following terms of use: +* +* BROAD INSTITUTE - SOFTWARE LICENSE AGREEMENT - FOR ACADEMIC NON-COMMERCIAL RESEARCH PURPOSES ONLY +* +* This Agreement is made between the Broad Institute, Inc. with a principal address at 7 Cambridge Center, Cambridge, MA 02142 (BROAD) and the LICENSEE and is effective at the date the downloading is completed (EFFECTIVE DATE). +* +* WHEREAS, LICENSEE desires to license the PROGRAM, as defined hereinafter, and BROAD wishes to have this PROGRAM utilized in the public interest, subject only to the royalty-free, nonexclusive, nontransferable license rights of the United States Government pursuant to 48 CFR 52.227-14; and +* WHEREAS, LICENSEE desires to license the PROGRAM and BROAD desires to grant a license on the following terms and conditions. +* NOW, THEREFORE, in consideration of the promises and covenants made herein, the parties hereto agree as follows: +* +* 1. DEFINITIONS +* 1.1 PROGRAM shall mean copyright in the object code and source code known as GATK2 and related documentation, if any, as they exist on the EFFECTIVE DATE and can be downloaded from http://www.broadinstitute/GATK on the EFFECTIVE DATE. +* +* 2. LICENSE +* 2.1 Grant. Subject to the terms of this Agreement, BROAD hereby grants to LICENSEE, solely for academic non-commercial research purposes, a non-exclusive, non-transferable license to: (a) download, execute and display the PROGRAM and (b) create bug fixes and modify the PROGRAM. +* The LICENSEE may apply the PROGRAM in a pipeline to data owned by users other than the LICENSEE and provide these users the results of the PROGRAM provided LICENSEE does so for academic non-commercial purposes only. For clarification purposes, academic sponsored research is not a commercial use under the terms of this Agreement. +* 2.2 No Sublicensing or Additional Rights. LICENSEE shall not sublicense or distribute the PROGRAM, in whole or in part, without prior written permission from BROAD. LICENSEE shall ensure that all of its users agree to the terms of this Agreement. LICENSEE further agrees that it shall not put the PROGRAM on a network, server, or other similar technology that may be accessed by anyone other than the LICENSEE and its employees and users who have agreed to the terms of this agreement. +* 2.3 License Limitations. Nothing in this Agreement shall be construed to confer any rights upon LICENSEE by implication, estoppel, or otherwise to any computer software, trademark, intellectual property, or patent rights of BROAD, or of any other entity, except as expressly granted herein. LICENSEE agrees that the PROGRAM, in whole or part, shall not be used for any commercial purpose, including without limitation, as the basis of a commercial software or hardware product or to provide services. LICENSEE further agrees that the PROGRAM shall not be copied or otherwise adapted in order to circumvent the need for obtaining a license for use of the PROGRAM. +* +* 3. OWNERSHIP OF INTELLECTUAL PROPERTY +* LICENSEE acknowledges that title to the PROGRAM shall remain with BROAD. The PROGRAM is marked with the following BROAD copyright notice and notice of attribution to contributors. LICENSEE shall retain such notice on all copies. LICENSEE agrees to include appropriate attribution if any results obtained from use of the PROGRAM are included in any publication. +* Copyright 2012 Broad Institute, Inc. +* Notice of attribution: The GATK2 program was made available through the generosity of Medical and Population Genetics program at the Broad Institute, Inc. +* LICENSEE shall not use any trademark or trade name of BROAD, or any variation, adaptation, or abbreviation, of such marks or trade names, or any names of officers, faculty, students, employees, or agents of BROAD except as states above for attribution purposes. +* +* 4. INDEMNIFICATION +* LICENSEE shall indemnify, defend, and hold harmless BROAD, and their respective officers, faculty, students, employees, associated investigators and agents, and their respective successors, heirs and assigns, (Indemnitees), against any liability, damage, loss, or expense (including reasonable attorneys fees and expenses) incurred by or imposed upon any of the Indemnitees in connection with any claims, suits, actions, demands or judgments arising out of any theory of liability (including, without limitation, actions in the form of tort, warranty, or strict liability and regardless of whether such action has any factual basis) pursuant to any right or license granted under this Agreement. +* +* 5. NO REPRESENTATIONS OR WARRANTIES +* THE PROGRAM IS DELIVERED AS IS. BROAD MAKES NO REPRESENTATIONS OR WARRANTIES OF ANY KIND CONCERNING THE PROGRAM OR THE COPYRIGHT, EXPRESS OR IMPLIED, INCLUDING, WITHOUT LIMITATION, WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NONINFRINGEMENT, OR THE ABSENCE OF LATENT OR OTHER DEFECTS, WHETHER OR NOT DISCOVERABLE. BROAD EXTENDS NO WARRANTIES OF ANY KIND AS TO PROGRAM CONFORMITY WITH WHATEVER USER MANUALS OR OTHER LITERATURE MAY BE ISSUED FROM TIME TO TIME. +* IN NO EVENT SHALL BROAD OR ITS RESPECTIVE DIRECTORS, OFFICERS, EMPLOYEES, AFFILIATED INVESTIGATORS AND AFFILIATES BE LIABLE FOR INCIDENTAL OR CONSEQUENTIAL DAMAGES OF ANY KIND, INCLUDING, WITHOUT LIMITATION, ECONOMIC DAMAGES OR INJURY TO PROPERTY AND LOST PROFITS, REGARDLESS OF WHETHER BROAD SHALL BE ADVISED, SHALL HAVE OTHER REASON TO KNOW, OR IN FACT SHALL KNOW OF THE POSSIBILITY OF THE FOREGOING. +* +* 6. ASSIGNMENT +* This Agreement is personal to LICENSEE and any rights or obligations assigned by LICENSEE without the prior written consent of BROAD shall be null and void. +* +* 7. MISCELLANEOUS +* 7.1 Export Control. LICENSEE gives assurance that it will comply with all United States export control laws and regulations controlling the export of the PROGRAM, including, without limitation, all Export Administration Regulations of the United States Department of Commerce. Among other things, these laws and regulations prohibit, or require a license for, the export of certain types of software to specified countries. +* 7.2 Termination. LICENSEE shall have the right to terminate this Agreement for any reason upon prior written notice to BROAD. If LICENSEE breaches any provision hereunder, and fails to cure such breach within thirty (30) days, BROAD may terminate this Agreement immediately. Upon termination, LICENSEE shall provide BROAD with written assurance that the original and all copies of the PROGRAM have been destroyed, except that, upon prior written authorization from BROAD, LICENSEE may retain a copy for archive purposes. +* 7.3 Survival. The following provisions shall survive the expiration or termination of this Agreement: Articles 1, 3, 4, 5 and Sections 2.2, 2.3, 7.3, and 7.4. +* 7.4 Notice. Any notices under this Agreement shall be in writing, shall specifically refer to this Agreement, and shall be sent by hand, recognized national overnight courier, confirmed facsimile transmission, confirmed electronic mail, or registered or certified mail, postage prepaid, return receipt requested. All notices under this Agreement shall be deemed effective upon receipt. +* 7.5 Amendment and Waiver; Entire Agreement. This Agreement may be amended, supplemented, or otherwise modified only by means of a written instrument signed by all parties. Any waiver of any rights or failure to act in a specific instance shall relate only to such instance and shall not be construed as an agreement to waive any rights or fail to act in any other instance, whether or not similar. This Agreement constitutes the entire agreement among the parties with respect to its subject matter and supersedes prior agreements or understandings between the parties relating to its subject matter. +* 7.6 Binding Effect; Headings. This Agreement shall be binding upon and inure to the benefit of the parties and their respective permitted successors and assigns. All headings are for convenience only and shall not affect the meaning of any provision of this Agreement. +* 7.7 Governing Law. This Agreement shall be construed, governed, interpreted and applied in accordance with the internal laws of the Commonwealth of Massachusetts, U.S.A., without regard to conflict of laws principles. +*/ package org.broadinstitute.sting.gatk.walkers.haplotypecaller; @@ -50,6 +50,9 @@ import org.broadinstitute.sting.BaseTest; import org.testng.Assert; import org.testng.annotations.Test; +import java.util.HashSet; +import java.util.Set; + public class KMerCounterCaseFixUnitTest extends BaseTest { @Test public void testMyData() { @@ -76,6 +79,18 @@ public class KMerCounterCaseFixUnitTest extends BaseTest { testCounting(counter, "NNC", 0); Assert.assertNotNull(counter.toString()); + + assertCounts(counter, 5); + assertCounts(counter, 4, "ATG"); + assertCounts(counter, 3, "ATG", "ACC"); + assertCounts(counter, 2, "ATG", "ACC", "AAA"); + assertCounts(counter, 1, "ATG", "ACC", "AAA", "CTG", "NNA", "CCC"); + } + + private void assertCounts(final KMerCounter counter, final int minCount, final String ... expecteds) { + final Set expected = new HashSet(); + for ( final String one : expecteds ) expected.add(new Kmer(one)); + Assert.assertEquals(new HashSet(counter.getKmersWithCountsAtLeast(minCount)), expected); } private void testCounting(final KMerCounter counter, final String in, final int expectedCount) { diff --git a/protected/java/test/org/broadinstitute/sting/gatk/walkers/haplotypecaller/LocalAssemblyEngineUnitTest.java b/protected/java/test/org/broadinstitute/sting/gatk/walkers/haplotypecaller/LocalAssemblyEngineUnitTest.java new file mode 100644 index 000000000..a517e1cb1 --- /dev/null +++ b/protected/java/test/org/broadinstitute/sting/gatk/walkers/haplotypecaller/LocalAssemblyEngineUnitTest.java @@ -0,0 +1,280 @@ +/* +* By downloading the PROGRAM you agree to the following terms of use: +* +* BROAD INSTITUTE - SOFTWARE LICENSE AGREEMENT - FOR ACADEMIC NON-COMMERCIAL RESEARCH PURPOSES ONLY +* +* This Agreement is made between the Broad Institute, Inc. with a principal address at 7 Cambridge Center, Cambridge, MA 02142 (BROAD) and the LICENSEE and is effective at the date the downloading is completed (EFFECTIVE DATE). +* +* WHEREAS, LICENSEE desires to license the PROGRAM, as defined hereinafter, and BROAD wishes to have this PROGRAM utilized in the public interest, subject only to the royalty-free, nonexclusive, nontransferable license rights of the United States Government pursuant to 48 CFR 52.227-14; and +* WHEREAS, LICENSEE desires to license the PROGRAM and BROAD desires to grant a license on the following terms and conditions. +* NOW, THEREFORE, in consideration of the promises and covenants made herein, the parties hereto agree as follows: +* +* 1. DEFINITIONS +* 1.1 PROGRAM shall mean copyright in the object code and source code known as GATK2 and related documentation, if any, as they exist on the EFFECTIVE DATE and can be downloaded from http://www.broadinstitute/GATK on the EFFECTIVE DATE. +* +* 2. LICENSE +* 2.1 Grant. Subject to the terms of this Agreement, BROAD hereby grants to LICENSEE, solely for academic non-commercial research purposes, a non-exclusive, non-transferable license to: (a) download, execute and display the PROGRAM and (b) create bug fixes and modify the PROGRAM. +* The LICENSEE may apply the PROGRAM in a pipeline to data owned by users other than the LICENSEE and provide these users the results of the PROGRAM provided LICENSEE does so for academic non-commercial purposes only. For clarification purposes, academic sponsored research is not a commercial use under the terms of this Agreement. +* 2.2 No Sublicensing or Additional Rights. LICENSEE shall not sublicense or distribute the PROGRAM, in whole or in part, without prior written permission from BROAD. LICENSEE shall ensure that all of its users agree to the terms of this Agreement. LICENSEE further agrees that it shall not put the PROGRAM on a network, server, or other similar technology that may be accessed by anyone other than the LICENSEE and its employees and users who have agreed to the terms of this agreement. +* 2.3 License Limitations. Nothing in this Agreement shall be construed to confer any rights upon LICENSEE by implication, estoppel, or otherwise to any computer software, trademark, intellectual property, or patent rights of BROAD, or of any other entity, except as expressly granted herein. LICENSEE agrees that the PROGRAM, in whole or part, shall not be used for any commercial purpose, including without limitation, as the basis of a commercial software or hardware product or to provide services. LICENSEE further agrees that the PROGRAM shall not be copied or otherwise adapted in order to circumvent the need for obtaining a license for use of the PROGRAM. +* +* 3. OWNERSHIP OF INTELLECTUAL PROPERTY +* LICENSEE acknowledges that title to the PROGRAM shall remain with BROAD. The PROGRAM is marked with the following BROAD copyright notice and notice of attribution to contributors. LICENSEE shall retain such notice on all copies. LICENSEE agrees to include appropriate attribution if any results obtained from use of the PROGRAM are included in any publication. +* Copyright 2012 Broad Institute, Inc. +* Notice of attribution: The GATK2 program was made available through the generosity of Medical and Population Genetics program at the Broad Institute, Inc. +* LICENSEE shall not use any trademark or trade name of BROAD, or any variation, adaptation, or abbreviation, of such marks or trade names, or any names of officers, faculty, students, employees, or agents of BROAD except as states above for attribution purposes. +* +* 4. INDEMNIFICATION +* LICENSEE shall indemnify, defend, and hold harmless BROAD, and their respective officers, faculty, students, employees, associated investigators and agents, and their respective successors, heirs and assigns, (Indemnitees), against any liability, damage, loss, or expense (including reasonable attorneys fees and expenses) incurred by or imposed upon any of the Indemnitees in connection with any claims, suits, actions, demands or judgments arising out of any theory of liability (including, without limitation, actions in the form of tort, warranty, or strict liability and regardless of whether such action has any factual basis) pursuant to any right or license granted under this Agreement. +* +* 5. NO REPRESENTATIONS OR WARRANTIES +* THE PROGRAM IS DELIVERED AS IS. BROAD MAKES NO REPRESENTATIONS OR WARRANTIES OF ANY KIND CONCERNING THE PROGRAM OR THE COPYRIGHT, EXPRESS OR IMPLIED, INCLUDING, WITHOUT LIMITATION, WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NONINFRINGEMENT, OR THE ABSENCE OF LATENT OR OTHER DEFECTS, WHETHER OR NOT DISCOVERABLE. BROAD EXTENDS NO WARRANTIES OF ANY KIND AS TO PROGRAM CONFORMITY WITH WHATEVER USER MANUALS OR OTHER LITERATURE MAY BE ISSUED FROM TIME TO TIME. +* IN NO EVENT SHALL BROAD OR ITS RESPECTIVE DIRECTORS, OFFICERS, EMPLOYEES, AFFILIATED INVESTIGATORS AND AFFILIATES BE LIABLE FOR INCIDENTAL OR CONSEQUENTIAL DAMAGES OF ANY KIND, INCLUDING, WITHOUT LIMITATION, ECONOMIC DAMAGES OR INJURY TO PROPERTY AND LOST PROFITS, REGARDLESS OF WHETHER BROAD SHALL BE ADVISED, SHALL HAVE OTHER REASON TO KNOW, OR IN FACT SHALL KNOW OF THE POSSIBILITY OF THE FOREGOING. +* +* 6. ASSIGNMENT +* This Agreement is personal to LICENSEE and any rights or obligations assigned by LICENSEE without the prior written consent of BROAD shall be null and void. +* +* 7. MISCELLANEOUS +* 7.1 Export Control. LICENSEE gives assurance that it will comply with all United States export control laws and regulations controlling the export of the PROGRAM, including, without limitation, all Export Administration Regulations of the United States Department of Commerce. Among other things, these laws and regulations prohibit, or require a license for, the export of certain types of software to specified countries. +* 7.2 Termination. LICENSEE shall have the right to terminate this Agreement for any reason upon prior written notice to BROAD. If LICENSEE breaches any provision hereunder, and fails to cure such breach within thirty (30) days, BROAD may terminate this Agreement immediately. Upon termination, LICENSEE shall provide BROAD with written assurance that the original and all copies of the PROGRAM have been destroyed, except that, upon prior written authorization from BROAD, LICENSEE may retain a copy for archive purposes. +* 7.3 Survival. The following provisions shall survive the expiration or termination of this Agreement: Articles 1, 3, 4, 5 and Sections 2.2, 2.3, 7.3, and 7.4. +* 7.4 Notice. Any notices under this Agreement shall be in writing, shall specifically refer to this Agreement, and shall be sent by hand, recognized national overnight courier, confirmed facsimile transmission, confirmed electronic mail, or registered or certified mail, postage prepaid, return receipt requested. All notices under this Agreement shall be deemed effective upon receipt. +* 7.5 Amendment and Waiver; Entire Agreement. This Agreement may be amended, supplemented, or otherwise modified only by means of a written instrument signed by all parties. Any waiver of any rights or failure to act in a specific instance shall relate only to such instance and shall not be construed as an agreement to waive any rights or fail to act in any other instance, whether or not similar. This Agreement constitutes the entire agreement among the parties with respect to its subject matter and supersedes prior agreements or understandings between the parties relating to its subject matter. +* 7.6 Binding Effect; Headings. This Agreement shall be binding upon and inure to the benefit of the parties and their respective permitted successors and assigns. All headings are for convenience only and shall not affect the meaning of any provision of this Agreement. +* 7.7 Governing Law. This Agreement shall be construed, governed, interpreted and applied in accordance with the internal laws of the Commonwealth of Massachusetts, U.S.A., without regard to conflict of laws principles. +*/ + +package org.broadinstitute.sting.gatk.walkers.haplotypecaller; + +import net.sf.picard.reference.IndexedFastaSequenceFile; +import net.sf.samtools.SAMFileHeader; +import org.broadinstitute.sting.BaseTest; +import org.broadinstitute.sting.gatk.walkers.haplotypecaller.readthreading.ReadThreadingAssembler; +import org.broadinstitute.sting.utils.GenomeLoc; +import org.broadinstitute.sting.utils.GenomeLocParser; +import org.broadinstitute.sting.utils.UnvalidatingGenomeLoc; +import org.broadinstitute.sting.utils.Utils; +import org.broadinstitute.sting.utils.activeregion.ActiveRegion; +import org.broadinstitute.sting.utils.collections.PrimitivePair; +import org.broadinstitute.sting.utils.fasta.CachingIndexedFastaSequenceFile; +import org.broadinstitute.sting.utils.haplotype.Haplotype; +import org.broadinstitute.sting.utils.sam.ArtificialSAMUtils; +import org.broadinstitute.sting.utils.sam.GATKSAMRecord; +import org.broadinstitute.variant.variantcontext.Allele; +import org.broadinstitute.variant.variantcontext.VariantContext; +import org.broadinstitute.variant.variantcontext.VariantContextBuilder; +import org.testng.Assert; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + +import java.io.File; +import java.io.FileNotFoundException; +import java.util.*; + +public class LocalAssemblyEngineUnitTest extends BaseTest { + private GenomeLocParser genomeLocParser; + private IndexedFastaSequenceFile seq; + private SAMFileHeader header; + + @BeforeClass + public void setup() throws FileNotFoundException { + seq = new CachingIndexedFastaSequenceFile(new File(b37KGReference)); + genomeLocParser = new GenomeLocParser(seq); + header = ArtificialSAMUtils.createArtificialSamHeader(seq.getSequenceDictionary()); + } + + private enum Assembler {DEBRUIJN_ASSEMBLER, READ_THREADING_ASSEMBLER} + private LocalAssemblyEngine createAssembler(final Assembler type) { + switch ( type ) { + case DEBRUIJN_ASSEMBLER: return new DeBruijnAssembler(); + case READ_THREADING_ASSEMBLER: return new ReadThreadingAssembler(); + default: throw new IllegalStateException("Unexpected " + type); + } + } + + @DataProvider(name = "AssembleIntervalsData") + public Object[][] makeAssembleIntervalsData() { + List tests = new ArrayList(); + + final String contig = "20"; + final int start = 10000000; + final int end = 10100000; + final int windowSize = 100; + final int stepSize = 200; + final int nReadsToUse = 5; + + for ( final Assembler assembler : Assembler.values() ) { + for ( int startI = start; startI < end; startI += stepSize) { + final int endI = startI + windowSize; + final GenomeLoc refLoc = genomeLocParser.createGenomeLoc(contig, startI, endI); + tests.add(new Object[]{assembler, refLoc, nReadsToUse}); + } + } + + return tests.toArray(new Object[][]{}); + } + + @DataProvider(name = "AssembleIntervalsWithVariantData") + public Object[][] makeAssembleIntervalsWithVariantData() { + List tests = new ArrayList(); + + final String contig = "20"; + final int start = 10000000; + final int end = 10001000; + final int windowSize = 100; + final int stepSize = 200; + final int variantStepSize = 1; + final int nReadsToUse = 5; + + for ( final Assembler assembler : Assembler.values() ) { + for ( int startI = start; startI < end; startI += stepSize) { + final int endI = startI + windowSize; + final GenomeLoc refLoc = genomeLocParser.createGenomeLoc(contig, startI, endI); + for ( int variantStart = windowSize / 2 - 10; variantStart < windowSize / 2 + 10; variantStart += variantStepSize ) { + tests.add(new Object[]{assembler, refLoc, nReadsToUse, variantStart}); + } + } + } + + return tests.toArray(new Object[][]{}); + } + + @Test(dataProvider = "AssembleIntervalsData") + public void testAssembleRef(final Assembler assembler, final GenomeLoc loc, final int nReadsToUse) { + final byte[] refBases = seq.getSubsequenceAt(loc.getContig(), loc.getStart(), loc.getStop()).getBases(); + + final List reads = new LinkedList(); + for ( int i = 0; i < nReadsToUse; i++ ) { + final byte[] bases = refBases.clone(); + final byte[] quals = Utils.dupBytes((byte) 30, refBases.length); + final String cigar = refBases.length + "M"; + final GATKSAMRecord read = ArtificialSAMUtils.createArtificialRead(header, loc.getContig(), loc.getContigIndex(), loc.getStart(), bases, quals, cigar); + reads.add(read); + } + + // TODO -- generalize to all assemblers + final Haplotype refHaplotype = new Haplotype(refBases, true); + final List haplotypes = assemble(assembler, refBases, loc, reads); + Assert.assertEquals(haplotypes, Collections.singletonList(refHaplotype)); + } + + @Test(dataProvider = "AssembleIntervalsWithVariantData") + public void testAssembleRefAndSNP(final Assembler assembler, final GenomeLoc loc, final int nReadsToUse, final int variantSite) { + final byte[] refBases = seq.getSubsequenceAt(loc.getContig(), loc.getStart(), loc.getStop()).getBases(); + final Allele refBase = Allele.create(refBases[variantSite], true); + final Allele altBase = Allele.create((byte)(refBase.getBases()[0] == 'A' ? 'C' : 'A'), false); + final VariantContextBuilder vcb = new VariantContextBuilder("x", loc.getContig(), variantSite, variantSite, Arrays.asList(refBase, altBase)); + testAssemblyWithVariant(assembler, refBases, loc, nReadsToUse, vcb.make()); + } + + @Test(dataProvider = "AssembleIntervalsWithVariantData") + public void testAssembleRefAndDeletion(final Assembler assembler, final GenomeLoc loc, final int nReadsToUse, final int variantSite) { + final byte[] refBases = seq.getSubsequenceAt(loc.getContig(), loc.getStart(), loc.getStop()).getBases(); + for ( int deletionLength = 1; deletionLength < 10; deletionLength++ ) { + final Allele refBase = Allele.create(new String(refBases).substring(variantSite, variantSite + deletionLength + 1), true); + final Allele altBase = Allele.create(refBase.getBases()[0], false); + final VariantContextBuilder vcb = new VariantContextBuilder("x", loc.getContig(), variantSite, variantSite + deletionLength, Arrays.asList(refBase, altBase)); + testAssemblyWithVariant(assembler, refBases, loc, nReadsToUse, vcb.make()); + } + } + + @Test(dataProvider = "AssembleIntervalsWithVariantData") + public void testAssembleRefAndInsertion(final Assembler assembler, final GenomeLoc loc, final int nReadsToUse, final int variantSite) { + final byte[] refBases = seq.getSubsequenceAt(loc.getContig(), loc.getStart(), loc.getStop()).getBases(); + for ( int insertionLength = 1; insertionLength < 10; insertionLength++ ) { + final Allele refBase = Allele.create(refBases[variantSite], false); + final Allele altBase = Allele.create(new String(refBases).substring(variantSite, variantSite + insertionLength + 1), true); + final VariantContextBuilder vcb = new VariantContextBuilder("x", loc.getContig(), variantSite, variantSite + insertionLength, Arrays.asList(refBase, altBase)); + testAssemblyWithVariant(assembler, refBases, loc, nReadsToUse, vcb.make()); + } + } + + private void testAssemblyWithVariant(final Assembler assembler, final byte[] refBases, final GenomeLoc loc, final int nReadsToUse, final VariantContext site) { + final String preRef = new String(refBases).substring(0, site.getStart()); + final String postRef = new String(refBases).substring(site.getEnd() + 1, refBases.length); + final byte[] altBases = (preRef + site.getAlternateAllele(0).getBaseString() + postRef).getBytes(); + +// logger.warn("ref " + new String(refBases)); +// logger.warn("alt " + new String(altBases)); + + final List reads = new LinkedList(); + for ( int i = 0; i < nReadsToUse; i++ ) { + final byte[] bases = altBases.clone(); + final byte[] quals = Utils.dupBytes((byte) 30, altBases.length); + final String cigar = altBases.length + "M"; + final GATKSAMRecord read = ArtificialSAMUtils.createArtificialRead(header, loc.getContig(), loc.getContigIndex(), loc.getStart(), bases, quals, cigar); + reads.add(read); + } + + final Haplotype refHaplotype = new Haplotype(refBases, true); + final Haplotype altHaplotype = new Haplotype(altBases, false); + final List haplotypes = assemble(assembler, refBases, loc, reads); + Assert.assertEquals(haplotypes, Arrays.asList(refHaplotype, altHaplotype)); + } + + + private List assemble(final Assembler assembler, final byte[] refBases, final GenomeLoc loc, final List reads) { + final Haplotype refHaplotype = new Haplotype(refBases, true); + final ActiveRegion activeRegion = new ActiveRegion(loc, null, true, genomeLocParser, 0); + activeRegion.addAll(reads); + final LocalAssemblyEngine engine = createAssembler(assembler); +// logger.warn("Assembling " + activeRegion + " with " + engine); + return engine.runLocalAssembly(activeRegion, refHaplotype, refBases, loc, Collections.emptyList()); + } + + @DataProvider(name = "SimpleAssemblyTestData") + public Object[][] makeSimpleAssemblyTestData() { + List tests = new ArrayList(); + + final String contig = "20"; + final int start = 10000000; + final int windowSize = 200; + final int end = start + windowSize; + + final Map edgeExcludesByAssembler = new EnumMap<>(Assembler.class); + edgeExcludesByAssembler.put(Assembler.DEBRUIJN_ASSEMBLER, 26); + edgeExcludesByAssembler.put(Assembler.READ_THREADING_ASSEMBLER, 25); // TODO -- decrease to zero when the edge calling problem is fixed + + final String ref = new String(seq.getSubsequenceAt(contig, start, end).getBases()); + final GenomeLoc refLoc = genomeLocParser.createGenomeLoc(contig, start, end); + + for ( final Assembler assembler : Assembler.values() ) { + final int excludeVariantsWithXbp = edgeExcludesByAssembler.get(assembler); + for ( int snpPos = 0; snpPos < windowSize; snpPos++) { + if ( snpPos > excludeVariantsWithXbp && (windowSize - snpPos) >= excludeVariantsWithXbp ) { + final byte[] altBases = ref.getBytes(); + altBases[snpPos] = 'N'; + final String alt = new String(altBases); + tests.add(new Object[]{"SNP at " + snpPos, assembler, refLoc, ref, alt}); + } + } + } + + return tests.toArray(new Object[][]{}); + } + + @Test(dataProvider = "SimpleAssemblyTestData") + public void testSimpleAssembly(final String name, final Assembler assembler, final GenomeLoc loc, final String ref, final String alt) { + final byte[] refBases = ref.getBytes(); + final byte[] altBases = alt.getBytes(); + + final List reads = new LinkedList<>(); + for ( int i = 0; i < 20; i++ ) { + final byte[] bases = altBases.clone(); + final byte[] quals = Utils.dupBytes((byte) 30, altBases.length); + final String cigar = altBases.length + "M"; + final GATKSAMRecord read = ArtificialSAMUtils.createArtificialRead(header, loc.getContig(), loc.getContigIndex(), loc.getStart(), bases, quals, cigar); + reads.add(read); + } + + final Haplotype refHaplotype = new Haplotype(refBases, true); + final Haplotype altHaplotype = new Haplotype(altBases, false); + final List haplotypes = assemble(assembler, refBases, loc, reads); + Assert.assertTrue(haplotypes.size() > 0, "Failed to find ref haplotype"); + Assert.assertEquals(haplotypes.get(0), refHaplotype); + + Assert.assertEquals(haplotypes.size(), 2, "Failed to find single alt haplotype"); + Assert.assertEquals(haplotypes.get(1), altHaplotype); + } +} diff --git a/protected/java/test/org/broadinstitute/sting/gatk/walkers/haplotypecaller/graphs/BaseEdgeUnitTest.java b/protected/java/test/org/broadinstitute/sting/gatk/walkers/haplotypecaller/graphs/BaseEdgeUnitTest.java index 7df6ee6c8..ea1d120b6 100644 --- a/protected/java/test/org/broadinstitute/sting/gatk/walkers/haplotypecaller/graphs/BaseEdgeUnitTest.java +++ b/protected/java/test/org/broadinstitute/sting/gatk/walkers/haplotypecaller/graphs/BaseEdgeUnitTest.java @@ -83,7 +83,10 @@ public class BaseEdgeUnitTest extends BaseTest { e.setMultiplicity(mult + 1); Assert.assertEquals(e.getMultiplicity(), mult + 1); - final BaseEdge copy = new BaseEdge(e); + e.incMultiplicity(2); + Assert.assertEquals(e.getMultiplicity(), mult + 3); + + final BaseEdge copy = e.copy(); Assert.assertEquals(copy.isRef(), e.isRef()); Assert.assertEquals(copy.getMultiplicity(), e.getMultiplicity()); } diff --git a/protected/java/test/org/broadinstitute/sting/gatk/walkers/haplotypecaller/graphs/BaseGraphUnitTest.java b/protected/java/test/org/broadinstitute/sting/gatk/walkers/haplotypecaller/graphs/BaseGraphUnitTest.java index c829488ba..e57f5d6e0 100644 --- a/protected/java/test/org/broadinstitute/sting/gatk/walkers/haplotypecaller/graphs/BaseGraphUnitTest.java +++ b/protected/java/test/org/broadinstitute/sting/gatk/walkers/haplotypecaller/graphs/BaseGraphUnitTest.java @@ -49,8 +49,8 @@ package org.broadinstitute.sting.gatk.walkers.haplotypecaller.graphs; import org.broadinstitute.sting.BaseTest; import org.testng.Assert; import org.testng.annotations.BeforeMethod; +import org.testng.annotations.DataProvider; import org.testng.annotations.Test; -import scala.actors.threadpool.Arrays; import java.io.File; import java.util.*; diff --git a/protected/java/test/org/broadinstitute/sting/gatk/walkers/haplotypecaller/graphs/CommonSuffixMergerUnitTest.java b/protected/java/test/org/broadinstitute/sting/gatk/walkers/haplotypecaller/graphs/CommonSuffixMergerUnitTest.java index 8682ae5e4..cfed2f0b8 100644 --- a/protected/java/test/org/broadinstitute/sting/gatk/walkers/haplotypecaller/graphs/CommonSuffixMergerUnitTest.java +++ b/protected/java/test/org/broadinstitute/sting/gatk/walkers/haplotypecaller/graphs/CommonSuffixMergerUnitTest.java @@ -137,12 +137,12 @@ public class CommonSuffixMergerUnitTest extends BaseTest { public static void assertSameHaplotypes(final String name, final SeqGraph actual, final SeqGraph original) { try { final Set haplotypes = new HashSet(); - final List> originalPaths = new KBestPaths().getKBestPaths(original); - for ( final Path path : originalPaths ) + final List> originalPaths = new KBestPaths().getKBestPaths(original); + for ( final Path path : originalPaths ) haplotypes.add(new String(path.getBases())); - final List> splitPaths = new KBestPaths().getKBestPaths(actual); - for ( final Path path : splitPaths ) { + final List> splitPaths = new KBestPaths().getKBestPaths(actual); + for ( final Path path : splitPaths ) { final String h = new String(path.getBases()); Assert.assertTrue(haplotypes.contains(h), "Failed to find haplotype " + h); } diff --git a/protected/java/test/org/broadinstitute/sting/gatk/walkers/haplotypecaller/graphs/CommonSuffixSplitterUnitTest.java b/protected/java/test/org/broadinstitute/sting/gatk/walkers/haplotypecaller/graphs/CommonSuffixSplitterUnitTest.java index 1ed20e5f4..9703d76cb 100644 --- a/protected/java/test/org/broadinstitute/sting/gatk/walkers/haplotypecaller/graphs/CommonSuffixSplitterUnitTest.java +++ b/protected/java/test/org/broadinstitute/sting/gatk/walkers/haplotypecaller/graphs/CommonSuffixSplitterUnitTest.java @@ -154,16 +154,16 @@ public class CommonSuffixSplitterUnitTest extends BaseTest { original.addEdge(v3, v4, new BaseEdge(false, 34)); original.addEdge(v4, v2, new BaseEdge(false, 42)); - original.printGraph(new File("testSplitInfiniteCycleFailure.dot"), 0); +// original.printGraph(new File("testSplitInfiniteCycleFailure.dot"), 0); final SeqGraph graph = (SeqGraph)original.clone(); final boolean success = new CommonSuffixSplitter().split(graph, v2); Assert.assertTrue(success); for ( final SeqVertex v : graph.vertexSet() ) { - graph.printGraph(new File("testSplitInfiniteCycleFailure.first_split.dot"), 0); +// graph.printGraph(new File("testSplitInfiniteCycleFailure.first_split.dot"), 0); final boolean success2 = new CommonSuffixSplitter().split((SeqGraph)graph.clone(), v); - if ( success2 ) graph.printGraph(new File("testSplitInfiniteCycleFailure.fail.dot"), 0); +// if ( success2 ) graph.printGraph(new File("testSplitInfiniteCycleFailure.fail.dot"), 0); Assert.assertFalse(success2, "Shouldn't be able to split any vertices but CommonSuffixSplitter says it could for " + v); } } diff --git a/protected/java/test/org/broadinstitute/sting/gatk/walkers/haplotypecaller/graphs/GraphUtilsUnitTest.java b/protected/java/test/org/broadinstitute/sting/gatk/walkers/haplotypecaller/graphs/GraphUtilsUnitTest.java new file mode 100644 index 000000000..01a6b5dbb --- /dev/null +++ b/protected/java/test/org/broadinstitute/sting/gatk/walkers/haplotypecaller/graphs/GraphUtilsUnitTest.java @@ -0,0 +1,120 @@ +/* +* By downloading the PROGRAM you agree to the following terms of use: +* +* BROAD INSTITUTE - SOFTWARE LICENSE AGREEMENT - FOR ACADEMIC NON-COMMERCIAL RESEARCH PURPOSES ONLY +* +* This Agreement is made between the Broad Institute, Inc. with a principal address at 7 Cambridge Center, Cambridge, MA 02142 (BROAD) and the LICENSEE and is effective at the date the downloading is completed (EFFECTIVE DATE). +* +* WHEREAS, LICENSEE desires to license the PROGRAM, as defined hereinafter, and BROAD wishes to have this PROGRAM utilized in the public interest, subject only to the royalty-free, nonexclusive, nontransferable license rights of the United States Government pursuant to 48 CFR 52.227-14; and +* WHEREAS, LICENSEE desires to license the PROGRAM and BROAD desires to grant a license on the following terms and conditions. +* NOW, THEREFORE, in consideration of the promises and covenants made herein, the parties hereto agree as follows: +* +* 1. DEFINITIONS +* 1.1 PROGRAM shall mean copyright in the object code and source code known as GATK2 and related documentation, if any, as they exist on the EFFECTIVE DATE and can be downloaded from http://www.broadinstitute/GATK on the EFFECTIVE DATE. +* +* 2. LICENSE +* 2.1 Grant. Subject to the terms of this Agreement, BROAD hereby grants to LICENSEE, solely for academic non-commercial research purposes, a non-exclusive, non-transferable license to: (a) download, execute and display the PROGRAM and (b) create bug fixes and modify the PROGRAM. +* The LICENSEE may apply the PROGRAM in a pipeline to data owned by users other than the LICENSEE and provide these users the results of the PROGRAM provided LICENSEE does so for academic non-commercial purposes only. For clarification purposes, academic sponsored research is not a commercial use under the terms of this Agreement. +* 2.2 No Sublicensing or Additional Rights. LICENSEE shall not sublicense or distribute the PROGRAM, in whole or in part, without prior written permission from BROAD. LICENSEE shall ensure that all of its users agree to the terms of this Agreement. LICENSEE further agrees that it shall not put the PROGRAM on a network, server, or other similar technology that may be accessed by anyone other than the LICENSEE and its employees and users who have agreed to the terms of this agreement. +* 2.3 License Limitations. Nothing in this Agreement shall be construed to confer any rights upon LICENSEE by implication, estoppel, or otherwise to any computer software, trademark, intellectual property, or patent rights of BROAD, or of any other entity, except as expressly granted herein. LICENSEE agrees that the PROGRAM, in whole or part, shall not be used for any commercial purpose, including without limitation, as the basis of a commercial software or hardware product or to provide services. LICENSEE further agrees that the PROGRAM shall not be copied or otherwise adapted in order to circumvent the need for obtaining a license for use of the PROGRAM. +* +* 3. OWNERSHIP OF INTELLECTUAL PROPERTY +* LICENSEE acknowledges that title to the PROGRAM shall remain with BROAD. The PROGRAM is marked with the following BROAD copyright notice and notice of attribution to contributors. LICENSEE shall retain such notice on all copies. LICENSEE agrees to include appropriate attribution if any results obtained from use of the PROGRAM are included in any publication. +* Copyright 2012 Broad Institute, Inc. +* Notice of attribution: The GATK2 program was made available through the generosity of Medical and Population Genetics program at the Broad Institute, Inc. +* LICENSEE shall not use any trademark or trade name of BROAD, or any variation, adaptation, or abbreviation, of such marks or trade names, or any names of officers, faculty, students, employees, or agents of BROAD except as states above for attribution purposes. +* +* 4. INDEMNIFICATION +* LICENSEE shall indemnify, defend, and hold harmless BROAD, and their respective officers, faculty, students, employees, associated investigators and agents, and their respective successors, heirs and assigns, (Indemnitees), against any liability, damage, loss, or expense (including reasonable attorneys fees and expenses) incurred by or imposed upon any of the Indemnitees in connection with any claims, suits, actions, demands or judgments arising out of any theory of liability (including, without limitation, actions in the form of tort, warranty, or strict liability and regardless of whether such action has any factual basis) pursuant to any right or license granted under this Agreement. +* +* 5. NO REPRESENTATIONS OR WARRANTIES +* THE PROGRAM IS DELIVERED AS IS. BROAD MAKES NO REPRESENTATIONS OR WARRANTIES OF ANY KIND CONCERNING THE PROGRAM OR THE COPYRIGHT, EXPRESS OR IMPLIED, INCLUDING, WITHOUT LIMITATION, WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NONINFRINGEMENT, OR THE ABSENCE OF LATENT OR OTHER DEFECTS, WHETHER OR NOT DISCOVERABLE. BROAD EXTENDS NO WARRANTIES OF ANY KIND AS TO PROGRAM CONFORMITY WITH WHATEVER USER MANUALS OR OTHER LITERATURE MAY BE ISSUED FROM TIME TO TIME. +* IN NO EVENT SHALL BROAD OR ITS RESPECTIVE DIRECTORS, OFFICERS, EMPLOYEES, AFFILIATED INVESTIGATORS AND AFFILIATES BE LIABLE FOR INCIDENTAL OR CONSEQUENTIAL DAMAGES OF ANY KIND, INCLUDING, WITHOUT LIMITATION, ECONOMIC DAMAGES OR INJURY TO PROPERTY AND LOST PROFITS, REGARDLESS OF WHETHER BROAD SHALL BE ADVISED, SHALL HAVE OTHER REASON TO KNOW, OR IN FACT SHALL KNOW OF THE POSSIBILITY OF THE FOREGOING. +* +* 6. ASSIGNMENT +* This Agreement is personal to LICENSEE and any rights or obligations assigned by LICENSEE without the prior written consent of BROAD shall be null and void. +* +* 7. MISCELLANEOUS +* 7.1 Export Control. LICENSEE gives assurance that it will comply with all United States export control laws and regulations controlling the export of the PROGRAM, including, without limitation, all Export Administration Regulations of the United States Department of Commerce. Among other things, these laws and regulations prohibit, or require a license for, the export of certain types of software to specified countries. +* 7.2 Termination. LICENSEE shall have the right to terminate this Agreement for any reason upon prior written notice to BROAD. If LICENSEE breaches any provision hereunder, and fails to cure such breach within thirty (30) days, BROAD may terminate this Agreement immediately. Upon termination, LICENSEE shall provide BROAD with written assurance that the original and all copies of the PROGRAM have been destroyed, except that, upon prior written authorization from BROAD, LICENSEE may retain a copy for archive purposes. +* 7.3 Survival. The following provisions shall survive the expiration or termination of this Agreement: Articles 1, 3, 4, 5 and Sections 2.2, 2.3, 7.3, and 7.4. +* 7.4 Notice. Any notices under this Agreement shall be in writing, shall specifically refer to this Agreement, and shall be sent by hand, recognized national overnight courier, confirmed facsimile transmission, confirmed electronic mail, or registered or certified mail, postage prepaid, return receipt requested. All notices under this Agreement shall be deemed effective upon receipt. +* 7.5 Amendment and Waiver; Entire Agreement. This Agreement may be amended, supplemented, or otherwise modified only by means of a written instrument signed by all parties. Any waiver of any rights or failure to act in a specific instance shall relate only to such instance and shall not be construed as an agreement to waive any rights or fail to act in any other instance, whether or not similar. This Agreement constitutes the entire agreement among the parties with respect to its subject matter and supersedes prior agreements or understandings between the parties relating to its subject matter. +* 7.6 Binding Effect; Headings. This Agreement shall be binding upon and inure to the benefit of the parties and their respective permitted successors and assigns. All headings are for convenience only and shall not affect the meaning of any provision of this Agreement. +* 7.7 Governing Law. This Agreement shall be construed, governed, interpreted and applied in accordance with the internal laws of the Commonwealth of Massachusetts, U.S.A., without regard to conflict of laws principles. +*/ + +package org.broadinstitute.sting.gatk.walkers.haplotypecaller.graphs; + +import org.broadinstitute.sting.BaseTest; +import org.broadinstitute.sting.utils.collections.PrimitivePair; +import org.testng.Assert; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +public class GraphUtilsUnitTest extends BaseTest { + @DataProvider(name = "findLongestUniqueMatchData") + public Object[][] makefindLongestUniqueMatchData() { + List tests = new ArrayList(); + + { // test all edge conditions + final String ref = "ACGT"; + for ( int start = 0; start < ref.length(); start++ ) { + for ( int end = start + 1; end <= ref.length(); end++ ) { + final String kmer = ref.substring(start, end); + tests.add(new Object[]{ref, kmer, end - 1, end - start}); + tests.add(new Object[]{ref, "N" + kmer, end - 1, end - start}); + tests.add(new Object[]{ref, "NN" + kmer, end - 1, end - start}); + tests.add(new Object[]{ref, kmer + "N", -1, 0}); + tests.add(new Object[]{ref, kmer + "NN", -1, 0}); + } + } + } + + { // multiple matches + final String ref = "AACCGGTT"; + for ( final String alt : Arrays.asList("A", "C", "G", "T") ) + tests.add(new Object[]{ref, alt, -1, 0}); + tests.add(new Object[]{ref, "AA", 1, 2}); + tests.add(new Object[]{ref, "CC", 3, 2}); + tests.add(new Object[]{ref, "GG", 5, 2}); + tests.add(new Object[]{ref, "TT", 7, 2}); + } + + { // complex matches that have unique substrings of lots of parts of kmer in the ref + final String ref = "ACGTACGTACGT"; + tests.add(new Object[]{ref, "ACGT", -1, 0}); + tests.add(new Object[]{ref, "TACGT", -1, 0}); + tests.add(new Object[]{ref, "GTACGT", -1, 0}); + tests.add(new Object[]{ref, "CGTACGT", -1, 0}); + tests.add(new Object[]{ref, "ACGTACGT", -1, 0}); + tests.add(new Object[]{ref, "TACGTACGT", 11, 9}); + tests.add(new Object[]{ref, "NTACGTACGT", 11, 9}); + tests.add(new Object[]{ref, "GTACGTACGT", 11, 10}); + tests.add(new Object[]{ref, "NGTACGTACGT", 11, 10}); + tests.add(new Object[]{ref, "CGTACGTACGT", 11, 11}); + } + + return tests.toArray(new Object[][]{}); + } + + /** + * Example testng test using MyDataProvider + */ + @Test(dataProvider = "findLongestUniqueMatchData") + public void testfindLongestUniqueMatch(final String seq, final String kmer, final int start, final int length) { + // adaptor this code to do whatever testing you want given the arguments start and size + final PrimitivePair.Int actual = GraphUtils.findLongestUniqueSuffixMatch(seq.getBytes(), kmer.getBytes()); + if ( start == -1 ) + Assert.assertNull(actual); + else { + Assert.assertNotNull(actual); + Assert.assertEquals(actual.first, start); + Assert.assertEquals(actual.second, length); + } + } +} diff --git a/protected/java/test/org/broadinstitute/sting/gatk/walkers/haplotypecaller/graphs/KBestPathsUnitTest.java b/protected/java/test/org/broadinstitute/sting/gatk/walkers/haplotypecaller/graphs/KBestPathsUnitTest.java index d1bae74b2..d6709672a 100644 --- a/protected/java/test/org/broadinstitute/sting/gatk/walkers/haplotypecaller/graphs/KBestPathsUnitTest.java +++ b/protected/java/test/org/broadinstitute/sting/gatk/walkers/haplotypecaller/graphs/KBestPathsUnitTest.java @@ -114,7 +114,7 @@ public class KBestPathsUnitTest extends BaseTest { if ( addCycle ) graph.addEdge(middleBottom, middleBottom); // enumerate all possible paths - final List> paths = new KBestPaths(allowCycles).getKBestPaths(graph, starts, ends); + final List> paths = new KBestPaths(allowCycles).getKBestPaths(graph, starts, ends); final int expectedNumOfPaths = nStartNodes * nBranchesPerBubble * (addCycle && allowCycles ? 2 : 1) * nEndNodes; Assert.assertEquals(paths.size(), expectedNumOfPaths, "Didn't find the expected number of paths"); @@ -127,7 +127,7 @@ public class KBestPathsUnitTest extends BaseTest { // get the best path, and make sure it's the same as our optimal path overall final Path best = paths.get(0); - final List> justOne = new KBestPaths(allowCycles).getKBestPaths(graph, 1, starts, ends); + final List> justOne = new KBestPaths(allowCycles).getKBestPaths(graph, 1, starts, ends); Assert.assertEquals(justOne.size(), 1); Assert.assertTrue(justOne.get(0).pathsAreTheSame(best), "Best path from complete enumerate " + best + " not the same as from k = 1 search " + justOne.get(0)); } @@ -147,7 +147,7 @@ public class KBestPathsUnitTest extends BaseTest { graph.addEdges(v4, v2); // enumerate all possible paths - final List> paths = new KBestPaths(false).getKBestPaths(graph, v1, v5); + final List> paths = new KBestPaths(false).getKBestPaths(graph, v1, v5); Assert.assertEquals(paths.size(), 1, "Didn't find the expected number of paths"); } @@ -163,7 +163,7 @@ public class KBestPathsUnitTest extends BaseTest { graph.addEdges(v1, v2, v3, v3); // enumerate all possible paths - final List> paths = new KBestPaths(false).getKBestPaths(graph, v1, v3); + final List> paths = new KBestPaths(false).getKBestPaths(graph, v1, v3); Assert.assertEquals(paths.size(), 1, "Didn't find the expected number of paths"); } @@ -201,9 +201,9 @@ public class KBestPathsUnitTest extends BaseTest { graph.addEdge(v2Alt, v3, new BaseEdge(false, 5)); // Construct the test path - Path path = new Path(v, graph); - path = new Path(path, graph.getEdge(v, v2Alt)); - path = new Path(path, graph.getEdge(v2Alt, v3)); + Path path = new Path(v, graph); + path = new Path(path, graph.getEdge(v, v2Alt)); + path = new Path(path, graph.getEdge(v2Alt, v3)); // Construct the actual cigar string implied by the test path Cigar expectedCigar = new Cigar(); @@ -219,7 +219,8 @@ public class KBestPathsUnitTest extends BaseTest { } expectedCigar.add(new CigarElement(postRef.length(), CigarOperator.M)); - Assert.assertEquals(path.calculateCigar().toString(), AlignmentUtils.consolidateCigar(expectedCigar).toString(), "Cigar string mismatch"); + final String ref = preRef + v2Ref.getSequenceString() + postRef; + Assert.assertEquals(path.calculateCigar(ref.getBytes()).toString(), AlignmentUtils.consolidateCigar(expectedCigar).toString(), "Cigar string mismatch"); } @DataProvider(name = "GetBasesData") @@ -251,9 +252,9 @@ public class KBestPathsUnitTest extends BaseTest { } // enumerate all possible paths - final List> paths = new KBestPaths().getKBestPaths(graph); + final List> paths = new KBestPaths().getKBestPaths(graph); Assert.assertEquals(paths.size(), 1); - final Path path = paths.get(0); + final Path path = paths.get(0); Assert.assertEquals(new String(path.getBases()), Utils.join("", frags), "Path doesn't have the expected sequence"); } @@ -296,6 +297,8 @@ public class KBestPathsUnitTest extends BaseTest { SeqVertex v7 = new SeqVertex(postRef); SeqVertex postV = new SeqVertex(postAltOption); + final String ref = preRef + v2Ref.getSequenceString() + midRef1 + v4Ref.getSequenceString() + midRef2 + v6Ref.getSequenceString() + postRef; + graph.addVertex(preV); graph.addVertex(v); graph.addVertex(v2Ref); @@ -324,18 +327,18 @@ public class KBestPathsUnitTest extends BaseTest { graph.addEdge(v7, postV, new BaseEdge(false, 1)); // Construct the test path - Path path = new Path( (offRefBeginning ? preV : v), graph); + Path path = new Path( (offRefBeginning ? preV : v), graph); if( offRefBeginning ) { - path = new Path(path, graph.getEdge(preV, v)); + path = new Path(path, graph.getEdge(preV, v)); } - path = new Path(path, graph.getEdge(v, v2Alt)); - path = new Path(path, graph.getEdge(v2Alt, v3)); - path = new Path(path, graph.getEdge(v3, v4Ref)); - path = new Path(path, graph.getEdge(v4Ref, v5)); - path = new Path(path, graph.getEdge(v5, v6Alt)); - path = new Path(path, graph.getEdge(v6Alt, v7)); + path = new Path(path, graph.getEdge(v, v2Alt)); + path = new Path(path, graph.getEdge(v2Alt, v3)); + path = new Path(path, graph.getEdge(v3, v4Ref)); + path = new Path(path, graph.getEdge(v4Ref, v5)); + path = new Path(path, graph.getEdge(v5, v6Alt)); + path = new Path(path, graph.getEdge(v6Alt, v7)); if( offRefEnding ) { - path = new Path(path, graph.getEdge(v7,postV)); + path = new Path(path, graph.getEdge(v7,postV)); } // Construct the actual cigar string implied by the test path @@ -373,7 +376,9 @@ public class KBestPathsUnitTest extends BaseTest { expectedCigar.add(new CigarElement(postAltOption.length(), CigarOperator.I)); } - Assert.assertEquals(path.calculateCigar().toString(), AlignmentUtils.consolidateCigar(expectedCigar).toString(), "Cigar string mismatch"); + Assert.assertEquals(path.calculateCigar(ref.getBytes()).toString(), + AlignmentUtils.consolidateCigar(expectedCigar).toString(), + "Cigar string mismatch: ref = " + ref + " alt " + new String(path.getBases())); } @Test(enabled = !DEBUG) @@ -389,43 +394,46 @@ public class KBestPathsUnitTest extends BaseTest { graph.addEdges(new BaseEdge(true, 1), top, ref, bot); graph.addEdges(new BaseEdge(false, 1), top, alt, bot); - final KBestPaths pathFinder = new KBestPaths(); - final List> paths = pathFinder.getKBestPaths(graph, top, bot); + final KBestPaths pathFinder = new KBestPaths(); + final List> paths = pathFinder.getKBestPaths(graph, top, bot); Assert.assertEquals(paths.size(), 2); - final Path refPath = paths.get(0); - final Path altPath = paths.get(1); + final Path refPath = paths.get(0); + final Path altPath = paths.get(1); - Assert.assertEquals(refPath.calculateCigar().toString(), "10M"); - Assert.assertEquals(altPath.calculateCigar().toString(), "1M3I5M3D1M"); + final String refString = top.getSequenceString() + ref.getSequenceString() + bot.getSequenceString(); + Assert.assertEquals(refPath.calculateCigar(refString.getBytes()).toString(), "10M"); + Assert.assertEquals(altPath.calculateCigar(refString.getBytes()).toString(), "1M3I5M3D1M"); } @Test(enabled = !DEBUG) public void testHardSWPath() { // Construct the assembly graph SeqGraph graph = new SeqGraph(); - final SeqVertex top = new SeqVertex( "NNN"); - final SeqVertex bot = new SeqVertex( "NNN"); + final SeqVertex top = new SeqVertex( "NNN" ); + final SeqVertex bot = new SeqVertex( "NNN" ); final SeqVertex alt = new SeqVertex( "ACAGAGAGAGAGAGAGAGAGAGAGAGAGAGAGAGAGAGAGAGAGAGAGAGA" ); final SeqVertex ref = new SeqVertex( "TGTGTGTGTGTGTGACAGAGAGAGAGAGAGAGAGAGAGAGAGAGA" ); graph.addVertices(top, bot, alt, ref); graph.addEdges(new BaseEdge(true, 1), top, ref, bot); graph.addEdges(new BaseEdge(false, 1), top, alt, bot); - final KBestPaths pathFinder = new KBestPaths(); - final List> paths = pathFinder.getKBestPaths(graph, top, bot); + final KBestPaths pathFinder = new KBestPaths(); + final List> paths = pathFinder.getKBestPaths(graph, top, bot); Assert.assertEquals(paths.size(), 2); - final Path refPath = paths.get(0); - final Path altPath = paths.get(1); + final Path refPath = paths.get(0); + final Path altPath = paths.get(1); - logger.warn("RefPath : " + refPath + " cigar " + refPath.calculateCigar()); - logger.warn("AltPath : " + altPath + " cigar " + altPath.calculateCigar()); + final String refString = top.getSequenceString() + ref.getSequenceString() + bot.getSequenceString(); - Assert.assertEquals(refPath.calculateCigar().toString(), "51M"); - Assert.assertEquals(altPath.calculateCigar().toString(), "3M6I48M"); + logger.warn("RefPath : " + refPath + " cigar " + refPath.calculateCigar(refString.getBytes())); + logger.warn("AltPath : " + altPath + " cigar " + altPath.calculateCigar(refString.getBytes())); + + Assert.assertEquals(refPath.calculateCigar(refString.getBytes()).toString(), "51M"); + Assert.assertEquals(altPath.calculateCigar(refString.getBytes()).toString(), "3M6I48M"); } // ----------------------------------------------------------------- @@ -466,30 +474,87 @@ public class KBestPathsUnitTest extends BaseTest { // Construct the assembly graph SeqGraph graph = new SeqGraph(); - SeqVertex top = new SeqVertex(""); + final int padSize = 0; + SeqVertex top = new SeqVertex(Utils.dupString("N", padSize)); SeqVertex ref = new SeqVertex(prefix + refMid + end); SeqVertex alt = new SeqVertex(prefix + altMid + end); - SeqVertex bot = new SeqVertex(""); + SeqVertex bot = new SeqVertex(Utils.dupString("N", padSize)); graph.addVertices(top, ref, alt, bot); graph.addEdges(new BaseEdge(true, 1), top, ref, bot); graph.addEdges(new BaseEdge(false, 1), top, alt, bot); // Construct the test path - Path path = Path.makePath(Arrays.asList(top, alt, bot), graph); + Path path = Path.makePath(Arrays.asList(top, alt, bot), graph); Cigar expected = new Cigar(); + expected.add(new CigarElement(padSize, CigarOperator.M)); if ( ! prefix.equals("") ) expected.add(new CigarElement(prefix.length(), CigarOperator.M)); for ( final CigarElement elt : TextCigarCodec.getSingleton().decode(midCigar).getCigarElements() ) expected.add(elt); if ( ! end.equals("") ) expected.add(new CigarElement(end.length(), CigarOperator.M)); + expected.add(new CigarElement(padSize, CigarOperator.M)); expected = AlignmentUtils.consolidateCigar(expected); - final Cigar pathCigar = path.calculateCigar(); + final String refString = top.getSequenceString() + ref.getSequenceString() + bot.getSequenceString(); + final Cigar pathCigar = path.calculateCigar(refString.getBytes()); logger.warn("diffs: " + ref + " vs. " + alt + " cigar " + midCigar); logger.warn("Path " + path + " with cigar " + pathCigar); logger.warn("Expected cigar " + expected); - Assert.assertEquals(pathCigar, expected, "Cigar mismatch"); + Assert.assertEquals(pathCigar, expected, "Cigar mismatch: ref = " + refString + " vs alt = " + new String(path.getBases())); + } + + @Test(enabled = !DEBUG) + public void testLeftAlignCigarSequentially() { + String preRefString = "GATCGATCGATC"; + String postRefString = "TTT"; + String refString = "ATCGAGGAGAGCGCCCCG"; + String indelString1 = "X"; + String indelString2 = "YZ"; + int refIndel1 = 10; + int refIndel2 = 12; + + for ( final int indelSize1 : Arrays.asList(1, 2, 3, 4) ) { + for ( final int indelOp1 : Arrays.asList(1, -1) ) { + for ( final int indelSize2 : Arrays.asList(1, 2, 3, 4) ) { + for ( final int indelOp2 : Arrays.asList(1, -1) ) { + + Cigar expectedCigar = new Cigar(); + expectedCigar.add(new CigarElement(refString.length(), CigarOperator.M)); + expectedCigar.add(new CigarElement(indelSize1, (indelOp1 > 0 ? CigarOperator.I : CigarOperator.D))); + expectedCigar.add(new CigarElement((indelOp1 < 0 ? refIndel1 - indelSize1 : refIndel1), CigarOperator.M)); + expectedCigar.add(new CigarElement(refString.length(), CigarOperator.M)); + expectedCigar.add(new CigarElement(indelSize2 * 2, (indelOp2 > 0 ? CigarOperator.I : CigarOperator.D))); + expectedCigar.add(new CigarElement((indelOp2 < 0 ? (refIndel2 - indelSize2) * 2 : refIndel2 * 2), CigarOperator.M)); + expectedCigar.add(new CigarElement(refString.length(), CigarOperator.M)); + + Cigar givenCigar = new Cigar(); + givenCigar.add(new CigarElement(refString.length() + refIndel1/2, CigarOperator.M)); + givenCigar.add(new CigarElement(indelSize1, (indelOp1 > 0 ? CigarOperator.I : CigarOperator.D))); + givenCigar.add(new CigarElement((indelOp1 < 0 ? (refIndel1/2 - indelSize1) : refIndel1/2) + refString.length() + refIndel2/2 * 2, CigarOperator.M)); + givenCigar.add(new CigarElement(indelSize2 * 2, (indelOp2 > 0 ? CigarOperator.I : CigarOperator.D))); + givenCigar.add(new CigarElement((indelOp2 < 0 ? (refIndel2/2 - indelSize2) * 2 : refIndel2/2 * 2) + refString.length(), CigarOperator.M)); + + String theRef = preRefString + refString + Utils.dupString(indelString1, refIndel1) + refString + Utils.dupString(indelString2, refIndel2) + refString + postRefString; + String theRead = refString + Utils.dupString(indelString1, refIndel1 + indelOp1 * indelSize1) + refString + Utils.dupString(indelString2, refIndel2 + indelOp2 * indelSize2) + refString; + + Cigar calculatedCigar = Path.leftAlignCigarSequentially(AlignmentUtils.consolidateCigar(givenCigar), theRef.getBytes(), theRead.getBytes(), preRefString.length(), 0); + Assert.assertEquals(AlignmentUtils.consolidateCigar(calculatedCigar).toString(), AlignmentUtils.consolidateCigar(expectedCigar).toString(), "Cigar strings do not match!"); + } + } + } + } + } + + @Test(enabled = true) + public void testLeftAlignCigarSequentiallyAdjacentID() { + final String ref = "GTCTCTCTCTCTCTCTCTATATATATATATATATTT"; + final String hap = "GTCTCTCTCTCTCTCTCTCTCTATATATATATATTT"; + final Cigar originalCigar = TextCigarCodec.getSingleton().decode("18M4I12M4D2M"); + + final Cigar result = Path.leftAlignCigarSequentially(originalCigar, ref.getBytes(), hap.getBytes(), 0, 0); + logger.warn("Result is " + result); + Assert.assertEquals(originalCigar.getReferenceLength(), result.getReferenceLength(), "Reference lengths are different"); } } diff --git a/protected/java/test/org/broadinstitute/sting/gatk/walkers/haplotypecaller/graphs/LowWeightChainPrunerUnitTest.java b/protected/java/test/org/broadinstitute/sting/gatk/walkers/haplotypecaller/graphs/LowWeightChainPrunerUnitTest.java new file mode 100644 index 000000000..06d81499c --- /dev/null +++ b/protected/java/test/org/broadinstitute/sting/gatk/walkers/haplotypecaller/graphs/LowWeightChainPrunerUnitTest.java @@ -0,0 +1,163 @@ +/* +* By downloading the PROGRAM you agree to the following terms of use: +* +* BROAD INSTITUTE - SOFTWARE LICENSE AGREEMENT - FOR ACADEMIC NON-COMMERCIAL RESEARCH PURPOSES ONLY +* +* This Agreement is made between the Broad Institute, Inc. with a principal address at 7 Cambridge Center, Cambridge, MA 02142 (BROAD) and the LICENSEE and is effective at the date the downloading is completed (EFFECTIVE DATE). +* +* WHEREAS, LICENSEE desires to license the PROGRAM, as defined hereinafter, and BROAD wishes to have this PROGRAM utilized in the public interest, subject only to the royalty-free, nonexclusive, nontransferable license rights of the United States Government pursuant to 48 CFR 52.227-14; and +* WHEREAS, LICENSEE desires to license the PROGRAM and BROAD desires to grant a license on the following terms and conditions. +* NOW, THEREFORE, in consideration of the promises and covenants made herein, the parties hereto agree as follows: +* +* 1. DEFINITIONS +* 1.1 PROGRAM shall mean copyright in the object code and source code known as GATK2 and related documentation, if any, as they exist on the EFFECTIVE DATE and can be downloaded from http://www.broadinstitute/GATK on the EFFECTIVE DATE. +* +* 2. LICENSE +* 2.1 Grant. Subject to the terms of this Agreement, BROAD hereby grants to LICENSEE, solely for academic non-commercial research purposes, a non-exclusive, non-transferable license to: (a) download, execute and display the PROGRAM and (b) create bug fixes and modify the PROGRAM. +* The LICENSEE may apply the PROGRAM in a pipeline to data owned by users other than the LICENSEE and provide these users the results of the PROGRAM provided LICENSEE does so for academic non-commercial purposes only. For clarification purposes, academic sponsored research is not a commercial use under the terms of this Agreement. +* 2.2 No Sublicensing or Additional Rights. LICENSEE shall not sublicense or distribute the PROGRAM, in whole or in part, without prior written permission from BROAD. LICENSEE shall ensure that all of its users agree to the terms of this Agreement. LICENSEE further agrees that it shall not put the PROGRAM on a network, server, or other similar technology that may be accessed by anyone other than the LICENSEE and its employees and users who have agreed to the terms of this agreement. +* 2.3 License Limitations. Nothing in this Agreement shall be construed to confer any rights upon LICENSEE by implication, estoppel, or otherwise to any computer software, trademark, intellectual property, or patent rights of BROAD, or of any other entity, except as expressly granted herein. LICENSEE agrees that the PROGRAM, in whole or part, shall not be used for any commercial purpose, including without limitation, as the basis of a commercial software or hardware product or to provide services. LICENSEE further agrees that the PROGRAM shall not be copied or otherwise adapted in order to circumvent the need for obtaining a license for use of the PROGRAM. +* +* 3. OWNERSHIP OF INTELLECTUAL PROPERTY +* LICENSEE acknowledges that title to the PROGRAM shall remain with BROAD. The PROGRAM is marked with the following BROAD copyright notice and notice of attribution to contributors. LICENSEE shall retain such notice on all copies. LICENSEE agrees to include appropriate attribution if any results obtained from use of the PROGRAM are included in any publication. +* Copyright 2012 Broad Institute, Inc. +* Notice of attribution: The GATK2 program was made available through the generosity of Medical and Population Genetics program at the Broad Institute, Inc. +* LICENSEE shall not use any trademark or trade name of BROAD, or any variation, adaptation, or abbreviation, of such marks or trade names, or any names of officers, faculty, students, employees, or agents of BROAD except as states above for attribution purposes. +* +* 4. INDEMNIFICATION +* LICENSEE shall indemnify, defend, and hold harmless BROAD, and their respective officers, faculty, students, employees, associated investigators and agents, and their respective successors, heirs and assigns, (Indemnitees), against any liability, damage, loss, or expense (including reasonable attorneys fees and expenses) incurred by or imposed upon any of the Indemnitees in connection with any claims, suits, actions, demands or judgments arising out of any theory of liability (including, without limitation, actions in the form of tort, warranty, or strict liability and regardless of whether such action has any factual basis) pursuant to any right or license granted under this Agreement. +* +* 5. NO REPRESENTATIONS OR WARRANTIES +* THE PROGRAM IS DELIVERED AS IS. BROAD MAKES NO REPRESENTATIONS OR WARRANTIES OF ANY KIND CONCERNING THE PROGRAM OR THE COPYRIGHT, EXPRESS OR IMPLIED, INCLUDING, WITHOUT LIMITATION, WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NONINFRINGEMENT, OR THE ABSENCE OF LATENT OR OTHER DEFECTS, WHETHER OR NOT DISCOVERABLE. BROAD EXTENDS NO WARRANTIES OF ANY KIND AS TO PROGRAM CONFORMITY WITH WHATEVER USER MANUALS OR OTHER LITERATURE MAY BE ISSUED FROM TIME TO TIME. +* IN NO EVENT SHALL BROAD OR ITS RESPECTIVE DIRECTORS, OFFICERS, EMPLOYEES, AFFILIATED INVESTIGATORS AND AFFILIATES BE LIABLE FOR INCIDENTAL OR CONSEQUENTIAL DAMAGES OF ANY KIND, INCLUDING, WITHOUT LIMITATION, ECONOMIC DAMAGES OR INJURY TO PROPERTY AND LOST PROFITS, REGARDLESS OF WHETHER BROAD SHALL BE ADVISED, SHALL HAVE OTHER REASON TO KNOW, OR IN FACT SHALL KNOW OF THE POSSIBILITY OF THE FOREGOING. +* +* 6. ASSIGNMENT +* This Agreement is personal to LICENSEE and any rights or obligations assigned by LICENSEE without the prior written consent of BROAD shall be null and void. +* +* 7. MISCELLANEOUS +* 7.1 Export Control. LICENSEE gives assurance that it will comply with all United States export control laws and regulations controlling the export of the PROGRAM, including, without limitation, all Export Administration Regulations of the United States Department of Commerce. Among other things, these laws and regulations prohibit, or require a license for, the export of certain types of software to specified countries. +* 7.2 Termination. LICENSEE shall have the right to terminate this Agreement for any reason upon prior written notice to BROAD. If LICENSEE breaches any provision hereunder, and fails to cure such breach within thirty (30) days, BROAD may terminate this Agreement immediately. Upon termination, LICENSEE shall provide BROAD with written assurance that the original and all copies of the PROGRAM have been destroyed, except that, upon prior written authorization from BROAD, LICENSEE may retain a copy for archive purposes. +* 7.3 Survival. The following provisions shall survive the expiration or termination of this Agreement: Articles 1, 3, 4, 5 and Sections 2.2, 2.3, 7.3, and 7.4. +* 7.4 Notice. Any notices under this Agreement shall be in writing, shall specifically refer to this Agreement, and shall be sent by hand, recognized national overnight courier, confirmed facsimile transmission, confirmed electronic mail, or registered or certified mail, postage prepaid, return receipt requested. All notices under this Agreement shall be deemed effective upon receipt. +* 7.5 Amendment and Waiver; Entire Agreement. This Agreement may be amended, supplemented, or otherwise modified only by means of a written instrument signed by all parties. Any waiver of any rights or failure to act in a specific instance shall relate only to such instance and shall not be construed as an agreement to waive any rights or fail to act in any other instance, whether or not similar. This Agreement constitutes the entire agreement among the parties with respect to its subject matter and supersedes prior agreements or understandings between the parties relating to its subject matter. +* 7.6 Binding Effect; Headings. This Agreement shall be binding upon and inure to the benefit of the parties and their respective permitted successors and assigns. All headings are for convenience only and shall not affect the meaning of any provision of this Agreement. +* 7.7 Governing Law. This Agreement shall be construed, governed, interpreted and applied in accordance with the internal laws of the Commonwealth of Massachusetts, U.S.A., without regard to conflict of laws principles. +*/ + +package org.broadinstitute.sting.gatk.walkers.haplotypecaller.graphs; + +import org.broadinstitute.sting.BaseTest; +import org.testng.Assert; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + +import java.io.File; +import java.util.*; + +public class LowWeightChainPrunerUnitTest extends BaseTest { + @DataProvider(name = "pruneChainsData") + public Object[][] makePruneChainsData() { + List tests = new ArrayList<>(); + + final SeqVertex v1 = new SeqVertex("A"); + final SeqVertex v2 = new SeqVertex("C"); + final SeqVertex v3 = new SeqVertex("G"); + final SeqVertex v4 = new SeqVertex("T"); + final SeqVertex v5 = new SeqVertex("AA"); + final SeqVertex v6 = new SeqVertex("CC"); + + for ( final int edgeWeight : Arrays.asList(1, 2, 3) ) { + for ( final int pruneFactor : Arrays.asList(1, 2, 3, 4) ) { + for ( final boolean isRef : Arrays.asList(true, false)) { + { // just an isolated chain + final int nExpected = edgeWeight < pruneFactor && ! isRef ? 3 : 0; + SeqGraph graph = new SeqGraph(); + graph.addVertices(v1, v2, v3); + graph.addEdges(new BaseEdge(isRef, edgeWeight), v1, v2, v3); + tests.add(new Object[]{"combinatorial", graph, pruneFactor, nExpected > 0 ? Collections.emptySet() : graph.vertexSet()}); + } + } + } + } + + { // connects to ref chain + SeqGraph graph = new SeqGraph(); + graph.addVertices(v1, v2, v3); + graph.addVertices(v4, v5); + graph.addEdges(new BaseEdge(true, 1), v4, v5); + graph.addEdges(new BaseEdge(false, 1), v4, v1, v2, v3, v5); + tests.add(new Object[]{"bad internal branch", graph, 2, new HashSet<>(Arrays.asList(v4, v5))}); + } + + { // has bad cycle + SeqGraph graph = new SeqGraph(); + graph.addVertices(v1, v2, v3, v4); + graph.addEdges(new BaseEdge(false, 1), v4, v1, v2, v3, v1); + // note that we'll remove v4 because it's low weight + tests.add(new Object[]{"has bad cycle", graph, 2, Collections.emptySet()}); + } + + { // has good cycle + SeqGraph graph = new SeqGraph(); + graph.addVertices(v1, v2, v3, v4); + graph.addEdges(new BaseEdge(false, 3), v4, v1, v2, v3, v1); + // note that we'll remove v4 because it's low weight + tests.add(new Object[]{"has good cycle", graph, 2, graph.vertexSet()}); + } + + { // has branch + SeqGraph graph = new SeqGraph(); + graph.addVertices(v1, v2, v3, v4, v5, v6); + graph.addEdges(new BaseEdge(false, 1), v1, v2, v3, v4, v6); + graph.addEdges(new BaseEdge(false, 1), v1, v2, v3, v5, v6); + tests.add(new Object[]{"has two bad branches", graph, 2, Collections.emptySet()}); + } + + { // middle vertex above threshold => no one can be removed + SeqGraph graph = new SeqGraph(); + graph.addVertices(v1, v2, v3, v4, v5); + graph.addEdges(new BaseEdge(false, 1), v1, v2); + graph.addEdges(new BaseEdge(false, 3), v2, v3); + graph.addEdges(new BaseEdge(false, 1), v3, v4, v5); + tests.add(new Object[]{"middle vertex above factor", graph, 2, graph.vertexSet()}); + } + + { // the branching node has value > pruneFactor + SeqGraph graph = new SeqGraph(); + graph.addVertices(v1, v2, v3, v4, v5, v6); + graph.addEdges(new BaseEdge(false, 3), v1, v2); + graph.addEdges(new BaseEdge(false, 3), v2, v3); + graph.addEdges(new BaseEdge(false, 1), v3, v4, v6); + graph.addEdges(new BaseEdge(false, 3), v2, v5, v6); + tests.add(new Object[]{"branch node greater than pruneFactor", graph, 2, graph.vertexSet()}); + } + + { // A single isolated chain with weights all below pruning should be pruned + SeqGraph graph = new SeqGraph(); + graph.addVertices(v1, v2, v3, v4, v5); + graph.addEdges(new BaseEdge(false, 1), v1, v2, v3); + graph.addEdges(new BaseEdge(false, 5), v4, v5); + tests.add(new Object[]{"isolated chain", graph, 2, new LinkedHashSet<>(Arrays.asList(v4, v5))}); + } + + { // A chain with weights all below pruning should be pruned, even if it connects to another good chain + SeqGraph graph = new SeqGraph(); + graph.addVertices(v1, v2, v3, v4, v5, v6); + graph.addEdges(new BaseEdge(false, 1), v1, v2, v3, v5); + graph.addEdges(new BaseEdge(false, 5), v4, v5, v6); + tests.add(new Object[]{"bad chain branching into good one", graph, 2, new HashSet<>(Arrays.asList(v4, v5, v6))}); + } + + return tests.toArray(new Object[][]{}); + } + + @Test(dataProvider = "pruneChainsData", enabled = true) + public void testPruneChains(final String name, final SeqGraph graph, final int pruneFactor, final Set remainingVertices) { + final Set copy = new HashSet<>(remainingVertices); +// graph.printGraph(new File("in.dot"), 0); + final LowWeightChainPruner pruner = new LowWeightChainPruner<>(pruneFactor); + pruner.pruneLowWeightChains(graph); +// graph.printGraph(new File("out.dot"), 0); + Assert.assertEquals(graph.vertexSet(), copy); + } +} diff --git a/protected/java/test/org/broadinstitute/sting/gatk/walkers/haplotypecaller/graphs/MultiSampleEdgeUnitTest.java b/protected/java/test/org/broadinstitute/sting/gatk/walkers/haplotypecaller/graphs/MultiSampleEdgeUnitTest.java new file mode 100644 index 000000000..f11be6635 --- /dev/null +++ b/protected/java/test/org/broadinstitute/sting/gatk/walkers/haplotypecaller/graphs/MultiSampleEdgeUnitTest.java @@ -0,0 +1,103 @@ +/* +* By downloading the PROGRAM you agree to the following terms of use: +* +* BROAD INSTITUTE - SOFTWARE LICENSE AGREEMENT - FOR ACADEMIC NON-COMMERCIAL RESEARCH PURPOSES ONLY +* +* This Agreement is made between the Broad Institute, Inc. with a principal address at 7 Cambridge Center, Cambridge, MA 02142 (BROAD) and the LICENSEE and is effective at the date the downloading is completed (EFFECTIVE DATE). +* +* WHEREAS, LICENSEE desires to license the PROGRAM, as defined hereinafter, and BROAD wishes to have this PROGRAM utilized in the public interest, subject only to the royalty-free, nonexclusive, nontransferable license rights of the United States Government pursuant to 48 CFR 52.227-14; and +* WHEREAS, LICENSEE desires to license the PROGRAM and BROAD desires to grant a license on the following terms and conditions. +* NOW, THEREFORE, in consideration of the promises and covenants made herein, the parties hereto agree as follows: +* +* 1. DEFINITIONS +* 1.1 PROGRAM shall mean copyright in the object code and source code known as GATK2 and related documentation, if any, as they exist on the EFFECTIVE DATE and can be downloaded from http://www.broadinstitute/GATK on the EFFECTIVE DATE. +* +* 2. LICENSE +* 2.1 Grant. Subject to the terms of this Agreement, BROAD hereby grants to LICENSEE, solely for academic non-commercial research purposes, a non-exclusive, non-transferable license to: (a) download, execute and display the PROGRAM and (b) create bug fixes and modify the PROGRAM. +* The LICENSEE may apply the PROGRAM in a pipeline to data owned by users other than the LICENSEE and provide these users the results of the PROGRAM provided LICENSEE does so for academic non-commercial purposes only. For clarification purposes, academic sponsored research is not a commercial use under the terms of this Agreement. +* 2.2 No Sublicensing or Additional Rights. LICENSEE shall not sublicense or distribute the PROGRAM, in whole or in part, without prior written permission from BROAD. LICENSEE shall ensure that all of its users agree to the terms of this Agreement. LICENSEE further agrees that it shall not put the PROGRAM on a network, server, or other similar technology that may be accessed by anyone other than the LICENSEE and its employees and users who have agreed to the terms of this agreement. +* 2.3 License Limitations. Nothing in this Agreement shall be construed to confer any rights upon LICENSEE by implication, estoppel, or otherwise to any computer software, trademark, intellectual property, or patent rights of BROAD, or of any other entity, except as expressly granted herein. LICENSEE agrees that the PROGRAM, in whole or part, shall not be used for any commercial purpose, including without limitation, as the basis of a commercial software or hardware product or to provide services. LICENSEE further agrees that the PROGRAM shall not be copied or otherwise adapted in order to circumvent the need for obtaining a license for use of the PROGRAM. +* +* 3. OWNERSHIP OF INTELLECTUAL PROPERTY +* LICENSEE acknowledges that title to the PROGRAM shall remain with BROAD. The PROGRAM is marked with the following BROAD copyright notice and notice of attribution to contributors. LICENSEE shall retain such notice on all copies. LICENSEE agrees to include appropriate attribution if any results obtained from use of the PROGRAM are included in any publication. +* Copyright 2012 Broad Institute, Inc. +* Notice of attribution: The GATK2 program was made available through the generosity of Medical and Population Genetics program at the Broad Institute, Inc. +* LICENSEE shall not use any trademark or trade name of BROAD, or any variation, adaptation, or abbreviation, of such marks or trade names, or any names of officers, faculty, students, employees, or agents of BROAD except as states above for attribution purposes. +* +* 4. INDEMNIFICATION +* LICENSEE shall indemnify, defend, and hold harmless BROAD, and their respective officers, faculty, students, employees, associated investigators and agents, and their respective successors, heirs and assigns, (Indemnitees), against any liability, damage, loss, or expense (including reasonable attorneys fees and expenses) incurred by or imposed upon any of the Indemnitees in connection with any claims, suits, actions, demands or judgments arising out of any theory of liability (including, without limitation, actions in the form of tort, warranty, or strict liability and regardless of whether such action has any factual basis) pursuant to any right or license granted under this Agreement. +* +* 5. NO REPRESENTATIONS OR WARRANTIES +* THE PROGRAM IS DELIVERED AS IS. BROAD MAKES NO REPRESENTATIONS OR WARRANTIES OF ANY KIND CONCERNING THE PROGRAM OR THE COPYRIGHT, EXPRESS OR IMPLIED, INCLUDING, WITHOUT LIMITATION, WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NONINFRINGEMENT, OR THE ABSENCE OF LATENT OR OTHER DEFECTS, WHETHER OR NOT DISCOVERABLE. BROAD EXTENDS NO WARRANTIES OF ANY KIND AS TO PROGRAM CONFORMITY WITH WHATEVER USER MANUALS OR OTHER LITERATURE MAY BE ISSUED FROM TIME TO TIME. +* IN NO EVENT SHALL BROAD OR ITS RESPECTIVE DIRECTORS, OFFICERS, EMPLOYEES, AFFILIATED INVESTIGATORS AND AFFILIATES BE LIABLE FOR INCIDENTAL OR CONSEQUENTIAL DAMAGES OF ANY KIND, INCLUDING, WITHOUT LIMITATION, ECONOMIC DAMAGES OR INJURY TO PROPERTY AND LOST PROFITS, REGARDLESS OF WHETHER BROAD SHALL BE ADVISED, SHALL HAVE OTHER REASON TO KNOW, OR IN FACT SHALL KNOW OF THE POSSIBILITY OF THE FOREGOING. +* +* 6. ASSIGNMENT +* This Agreement is personal to LICENSEE and any rights or obligations assigned by LICENSEE without the prior written consent of BROAD shall be null and void. +* +* 7. MISCELLANEOUS +* 7.1 Export Control. LICENSEE gives assurance that it will comply with all United States export control laws and regulations controlling the export of the PROGRAM, including, without limitation, all Export Administration Regulations of the United States Department of Commerce. Among other things, these laws and regulations prohibit, or require a license for, the export of certain types of software to specified countries. +* 7.2 Termination. LICENSEE shall have the right to terminate this Agreement for any reason upon prior written notice to BROAD. If LICENSEE breaches any provision hereunder, and fails to cure such breach within thirty (30) days, BROAD may terminate this Agreement immediately. Upon termination, LICENSEE shall provide BROAD with written assurance that the original and all copies of the PROGRAM have been destroyed, except that, upon prior written authorization from BROAD, LICENSEE may retain a copy for archive purposes. +* 7.3 Survival. The following provisions shall survive the expiration or termination of this Agreement: Articles 1, 3, 4, 5 and Sections 2.2, 2.3, 7.3, and 7.4. +* 7.4 Notice. Any notices under this Agreement shall be in writing, shall specifically refer to this Agreement, and shall be sent by hand, recognized national overnight courier, confirmed facsimile transmission, confirmed electronic mail, or registered or certified mail, postage prepaid, return receipt requested. All notices under this Agreement shall be deemed effective upon receipt. +* 7.5 Amendment and Waiver; Entire Agreement. This Agreement may be amended, supplemented, or otherwise modified only by means of a written instrument signed by all parties. Any waiver of any rights or failure to act in a specific instance shall relate only to such instance and shall not be construed as an agreement to waive any rights or fail to act in any other instance, whether or not similar. This Agreement constitutes the entire agreement among the parties with respect to its subject matter and supersedes prior agreements or understandings between the parties relating to its subject matter. +* 7.6 Binding Effect; Headings. This Agreement shall be binding upon and inure to the benefit of the parties and their respective permitted successors and assigns. All headings are for convenience only and shall not affect the meaning of any provision of this Agreement. +* 7.7 Governing Law. This Agreement shall be construed, governed, interpreted and applied in accordance with the internal laws of the Commonwealth of Massachusetts, U.S.A., without regard to conflict of laws principles. +*/ + +package org.broadinstitute.sting.gatk.walkers.haplotypecaller.graphs; + +import org.apache.commons.lang.ArrayUtils; +import org.broadinstitute.sting.BaseTest; +import org.broadinstitute.sting.utils.MathUtils; +import org.broadinstitute.sting.utils.Utils; +import org.testng.Assert; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +public class MultiSampleEdgeUnitTest extends BaseTest { + @DataProvider(name = "MultiplicityData") + public Object[][] makeMultiplicityData() { + List tests = new ArrayList(); + + final List countsPerSample = Arrays.asList(0, 1, 2, 3, 4, 5); + for ( final int nSamples : Arrays.asList(1, 2, 3, 4, 5)) { + for ( final List perm : Utils.makePermutations(countsPerSample, nSamples, false) ) { + tests.add(new Object[]{perm}); + } + } + + return tests.toArray(new Object[][]{}); + } + + /** + * Example testng test using MyDataProvider + */ + @Test(dataProvider = "MultiplicityData") + public void testMultiplicity(final List countsPerSample) { + final MultiSampleEdge edge = new MultiSampleEdge(false, 0); + Assert.assertEquals(edge.getMultiplicity(), 0); + Assert.assertEquals(edge.getPruningMultiplicity(), 0); + + int total = 0; + for ( int i = 0; i < countsPerSample.size(); i++ ) { + int countForSample = 0; + for ( int count = 0; count < countsPerSample.get(i); count++ ) { + edge.incMultiplicity(1); + total++; + countForSample++; + Assert.assertEquals(edge.getMultiplicity(), total); + Assert.assertEquals(edge.getCurrentSingleSampleMultiplicity(), countForSample); + } + edge.flushSingleSampleMultiplicity(); + } + + final int max = MathUtils.arrayMax(ArrayUtils.toPrimitive(countsPerSample.toArray(new Integer[countsPerSample.size()]))); + Assert.assertEquals(edge.getMultiplicity(), total); + Assert.assertEquals(edge.getPruningMultiplicity(), max); + Assert.assertEquals(edge.getMaxSingleSampleMultiplicity(), max); + } +} diff --git a/protected/java/test/org/broadinstitute/sting/gatk/walkers/haplotypecaller/graphs/PathUnitTest.java b/protected/java/test/org/broadinstitute/sting/gatk/walkers/haplotypecaller/graphs/PathUnitTest.java new file mode 100644 index 000000000..ee07bea33 --- /dev/null +++ b/protected/java/test/org/broadinstitute/sting/gatk/walkers/haplotypecaller/graphs/PathUnitTest.java @@ -0,0 +1,80 @@ +/* +* By downloading the PROGRAM you agree to the following terms of use: +* +* BROAD INSTITUTE - SOFTWARE LICENSE AGREEMENT - FOR ACADEMIC NON-COMMERCIAL RESEARCH PURPOSES ONLY +* +* This Agreement is made between the Broad Institute, Inc. with a principal address at 7 Cambridge Center, Cambridge, MA 02142 (BROAD) and the LICENSEE and is effective at the date the downloading is completed (EFFECTIVE DATE). +* +* WHEREAS, LICENSEE desires to license the PROGRAM, as defined hereinafter, and BROAD wishes to have this PROGRAM utilized in the public interest, subject only to the royalty-free, nonexclusive, nontransferable license rights of the United States Government pursuant to 48 CFR 52.227-14; and +* WHEREAS, LICENSEE desires to license the PROGRAM and BROAD desires to grant a license on the following terms and conditions. +* NOW, THEREFORE, in consideration of the promises and covenants made herein, the parties hereto agree as follows: +* +* 1. DEFINITIONS +* 1.1 PROGRAM shall mean copyright in the object code and source code known as GATK2 and related documentation, if any, as they exist on the EFFECTIVE DATE and can be downloaded from http://www.broadinstitute/GATK on the EFFECTIVE DATE. +* +* 2. LICENSE +* 2.1 Grant. Subject to the terms of this Agreement, BROAD hereby grants to LICENSEE, solely for academic non-commercial research purposes, a non-exclusive, non-transferable license to: (a) download, execute and display the PROGRAM and (b) create bug fixes and modify the PROGRAM. +* The LICENSEE may apply the PROGRAM in a pipeline to data owned by users other than the LICENSEE and provide these users the results of the PROGRAM provided LICENSEE does so for academic non-commercial purposes only. For clarification purposes, academic sponsored research is not a commercial use under the terms of this Agreement. +* 2.2 No Sublicensing or Additional Rights. LICENSEE shall not sublicense or distribute the PROGRAM, in whole or in part, without prior written permission from BROAD. LICENSEE shall ensure that all of its users agree to the terms of this Agreement. LICENSEE further agrees that it shall not put the PROGRAM on a network, server, or other similar technology that may be accessed by anyone other than the LICENSEE and its employees and users who have agreed to the terms of this agreement. +* 2.3 License Limitations. Nothing in this Agreement shall be construed to confer any rights upon LICENSEE by implication, estoppel, or otherwise to any computer software, trademark, intellectual property, or patent rights of BROAD, or of any other entity, except as expressly granted herein. LICENSEE agrees that the PROGRAM, in whole or part, shall not be used for any commercial purpose, including without limitation, as the basis of a commercial software or hardware product or to provide services. LICENSEE further agrees that the PROGRAM shall not be copied or otherwise adapted in order to circumvent the need for obtaining a license for use of the PROGRAM. +* +* 3. OWNERSHIP OF INTELLECTUAL PROPERTY +* LICENSEE acknowledges that title to the PROGRAM shall remain with BROAD. The PROGRAM is marked with the following BROAD copyright notice and notice of attribution to contributors. LICENSEE shall retain such notice on all copies. LICENSEE agrees to include appropriate attribution if any results obtained from use of the PROGRAM are included in any publication. +* Copyright 2012 Broad Institute, Inc. +* Notice of attribution: The GATK2 program was made available through the generosity of Medical and Population Genetics program at the Broad Institute, Inc. +* LICENSEE shall not use any trademark or trade name of BROAD, or any variation, adaptation, or abbreviation, of such marks or trade names, or any names of officers, faculty, students, employees, or agents of BROAD except as states above for attribution purposes. +* +* 4. INDEMNIFICATION +* LICENSEE shall indemnify, defend, and hold harmless BROAD, and their respective officers, faculty, students, employees, associated investigators and agents, and their respective successors, heirs and assigns, (Indemnitees), against any liability, damage, loss, or expense (including reasonable attorneys fees and expenses) incurred by or imposed upon any of the Indemnitees in connection with any claims, suits, actions, demands or judgments arising out of any theory of liability (including, without limitation, actions in the form of tort, warranty, or strict liability and regardless of whether such action has any factual basis) pursuant to any right or license granted under this Agreement. +* +* 5. NO REPRESENTATIONS OR WARRANTIES +* THE PROGRAM IS DELIVERED AS IS. BROAD MAKES NO REPRESENTATIONS OR WARRANTIES OF ANY KIND CONCERNING THE PROGRAM OR THE COPYRIGHT, EXPRESS OR IMPLIED, INCLUDING, WITHOUT LIMITATION, WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NONINFRINGEMENT, OR THE ABSENCE OF LATENT OR OTHER DEFECTS, WHETHER OR NOT DISCOVERABLE. BROAD EXTENDS NO WARRANTIES OF ANY KIND AS TO PROGRAM CONFORMITY WITH WHATEVER USER MANUALS OR OTHER LITERATURE MAY BE ISSUED FROM TIME TO TIME. +* IN NO EVENT SHALL BROAD OR ITS RESPECTIVE DIRECTORS, OFFICERS, EMPLOYEES, AFFILIATED INVESTIGATORS AND AFFILIATES BE LIABLE FOR INCIDENTAL OR CONSEQUENTIAL DAMAGES OF ANY KIND, INCLUDING, WITHOUT LIMITATION, ECONOMIC DAMAGES OR INJURY TO PROPERTY AND LOST PROFITS, REGARDLESS OF WHETHER BROAD SHALL BE ADVISED, SHALL HAVE OTHER REASON TO KNOW, OR IN FACT SHALL KNOW OF THE POSSIBILITY OF THE FOREGOING. +* +* 6. ASSIGNMENT +* This Agreement is personal to LICENSEE and any rights or obligations assigned by LICENSEE without the prior written consent of BROAD shall be null and void. +* +* 7. MISCELLANEOUS +* 7.1 Export Control. LICENSEE gives assurance that it will comply with all United States export control laws and regulations controlling the export of the PROGRAM, including, without limitation, all Export Administration Regulations of the United States Department of Commerce. Among other things, these laws and regulations prohibit, or require a license for, the export of certain types of software to specified countries. +* 7.2 Termination. LICENSEE shall have the right to terminate this Agreement for any reason upon prior written notice to BROAD. If LICENSEE breaches any provision hereunder, and fails to cure such breach within thirty (30) days, BROAD may terminate this Agreement immediately. Upon termination, LICENSEE shall provide BROAD with written assurance that the original and all copies of the PROGRAM have been destroyed, except that, upon prior written authorization from BROAD, LICENSEE may retain a copy for archive purposes. +* 7.3 Survival. The following provisions shall survive the expiration or termination of this Agreement: Articles 1, 3, 4, 5 and Sections 2.2, 2.3, 7.3, and 7.4. +* 7.4 Notice. Any notices under this Agreement shall be in writing, shall specifically refer to this Agreement, and shall be sent by hand, recognized national overnight courier, confirmed facsimile transmission, confirmed electronic mail, or registered or certified mail, postage prepaid, return receipt requested. All notices under this Agreement shall be deemed effective upon receipt. +* 7.5 Amendment and Waiver; Entire Agreement. This Agreement may be amended, supplemented, or otherwise modified only by means of a written instrument signed by all parties. Any waiver of any rights or failure to act in a specific instance shall relate only to such instance and shall not be construed as an agreement to waive any rights or fail to act in any other instance, whether or not similar. This Agreement constitutes the entire agreement among the parties with respect to its subject matter and supersedes prior agreements or understandings between the parties relating to its subject matter. +* 7.6 Binding Effect; Headings. This Agreement shall be binding upon and inure to the benefit of the parties and their respective permitted successors and assigns. All headings are for convenience only and shall not affect the meaning of any provision of this Agreement. +* 7.7 Governing Law. This Agreement shall be construed, governed, interpreted and applied in accordance with the internal laws of the Commonwealth of Massachusetts, U.S.A., without regard to conflict of laws principles. +*/ + +package org.broadinstitute.sting.gatk.walkers.haplotypecaller.graphs; + +import net.sf.samtools.Cigar; +import org.broadinstitute.sting.BaseTest; +import org.testng.Assert; +import org.testng.annotations.Test; + +public class PathUnitTest extends BaseTest { + @Test(enabled = true) + public void testAlignReallyLongDeletion() { + final String ref = "ATGGTGGCTCATACCTGTAATCCCAGCACTTTGGGAGGCCGAGGCGGGAACATCACCTGAGGCCAGGAGTTCAAAACCAGCCTGGCTAACATAGCAAAACCCCATCTCTAATGAAAATACAAAAATTAGCTGGGTGTGGTGGTGTCCGCCTGTAGTCCCAGCTACTCAGGAGACTAAGGCATGAGAATCACTTGAACCCAGGATGCAGAGGCTGTAGTGAGCCGAGATTGCACCACGGCTGCACTCCAGCCTGGGCAACAGAGCGAGACTCTGTCTCAAATAAAATAGCGTAACGTAACATAACATAACATAACATAACATAACATAACATAACATAACATAACATAACATAACACAACAACAAAATAAAATAACATAAATCATGTTGTTAGGAAAAAAATCAGTTATGCAGCTACATGCTATTTACAAGAGATATACCTTAAAATATAAGACACAGAGGCCGGGCGCGGTAGCTCATGCCTGTAATCCCAGCACTTTGGGAGGCTGAGGCAAGCGGATCATGAGGTCAGGAGATCGAGACCATCC"; + final String hap = "ATGGTGGCTCATACCTGTAATCCCAGCACTTTGGGAGGCTGAGGCAAGCGGATCATGAGGTCAGGAGATCGAGACCATCCT"; + + final SeqGraph graph = new SeqGraph(); + final SeqVertex v = new SeqVertex(hap); + graph.addVertex(v); + final Path path = new Path(v, graph); + final Cigar cigar = path.calculateCigar(ref.getBytes()); + Assert.assertNull(cigar, "Should have failed gracefully"); + } + + @Test(enabled = true) + public void testAlignReallyLongDeletion2() { + final String ref = "CGGCTAATTTTTGTATTTTTAGTAGAGACAGGGTTTCACCATGTTGGCCAGGCTGGTCTTGAACTCCTGACCTCAGGTGATCCACTCGCCTCGGTCTCCCAAAGTGTTGGGATTACAGGCATGAACCACTGCACCTGGCCTAGTGTTTGGGAAAACTATACTAGGAAAAGAATAGTTGCTTTAAGTCATTCTTTGATTATTCTGAGAATTGGCATATAGCTGCCATTATAACCTACTTTTGCTAAATATAATAATAATAATCATTATTTTTATTTTTTGAGACAGGGTCTTGTTTTGTCACCCCGGCTGGAGTGAAGTGGCGCAATCTCGGCTCACTGCAACCTCCACCTCCGGGTGCAAGCAATTCTCCTGCCTCAGCCTCTTGAGTAGCTAGGATTACAGGCACAAGCCATCATGCCCAGCTAATTTTTGTATTTTTAGTAGAGACAGGGTTTCACCATGTTGGTCAGGCTGGTCTTGAACTCCTGACCTCAGGT"; + final String hap = "CGGCTAATTTTTGTATTTTTAGTAGAGACAGGGTTTCACCATGTTGGTCAGGCTGGTCTTGAACTCCTGACCTCAGGT"; + + final SeqGraph graph = new SeqGraph(); + final SeqVertex v = new SeqVertex(hap); + graph.addVertex(v); + final Path path = new Path(v, graph); + final Cigar cigar = path.calculateCigar(ref.getBytes()); + Assert.assertEquals(cigar.toString(), "48M419D30M"); + } +} diff --git a/protected/java/test/org/broadinstitute/sting/gatk/walkers/haplotypecaller/graphs/SeqGraphUnitTest.java b/protected/java/test/org/broadinstitute/sting/gatk/walkers/haplotypecaller/graphs/SeqGraphUnitTest.java index bd2e3cc2c..c72f426be 100644 --- a/protected/java/test/org/broadinstitute/sting/gatk/walkers/haplotypecaller/graphs/SeqGraphUnitTest.java +++ b/protected/java/test/org/broadinstitute/sting/gatk/walkers/haplotypecaller/graphs/SeqGraphUnitTest.java @@ -280,16 +280,15 @@ public class SeqGraphUnitTest extends BaseTest { all.addEdges(pre2, top, middle2, bottom, tail2); final SeqGraph expected = new SeqGraph(); + SeqVertex newPre1 = new SeqVertex(Utils.dupString("A", SeqGraph.MIN_COMMON_SEQUENCE_TO_MERGE_SOURCE_SINK_VERTICES) + "C"); + SeqVertex newPre2 = new SeqVertex(Utils.dupString("A", SeqGraph.MIN_COMMON_SEQUENCE_TO_MERGE_SOURCE_SINK_VERTICES) + "G"); + final SeqVertex newTop = new SeqVertex("TA"); final SeqVertex newMiddle1 = new SeqVertex("G"); final SeqVertex newMiddle2 = new SeqVertex("T"); final SeqVertex newBottom = new SeqVertex("C" + bottom.getSequenceString()); - final SeqVertex newTop = new SeqVertex(Utils.dupString("A", SeqGraph.MIN_COMMON_SEQUENCE_TO_MERGE_SOURCE_SINK_VERTICES)); - final SeqVertex newTopDown1 = new SeqVertex("G"); - final SeqVertex newTopDown2 = new SeqVertex("C"); - final SeqVertex newTopBottomMerged = new SeqVertex("TA"); - expected.addVertices(newTop, newTopDown1, newTopDown2, newTopBottomMerged, newMiddle1, newMiddle2, newBottom, tail1, tail2); - expected.addEdges(newTop, newTopDown1, newTopBottomMerged, newMiddle1, newBottom, tail1); - expected.addEdges(newTop, newTopDown2, newTopBottomMerged, newMiddle2, newBottom, tail2); + expected.addVertices(newPre1, newPre2, newTop, newMiddle1, newMiddle2, newBottom, tail1, tail2); + expected.addEdges(newPre1, newTop, newMiddle1, newBottom, tail1); + expected.addEdges(newPre2, newTop, newMiddle2, newBottom, tail2); tests.add(new Object[]{all.clone(), expected.clone()}); } diff --git a/protected/java/test/org/broadinstitute/sting/gatk/walkers/haplotypecaller/graphs/SharedVertexSequenceSplitterUnitTest.java b/protected/java/test/org/broadinstitute/sting/gatk/walkers/haplotypecaller/graphs/SharedVertexSequenceSplitterUnitTest.java index 2df783b19..5bc13f884 100644 --- a/protected/java/test/org/broadinstitute/sting/gatk/walkers/haplotypecaller/graphs/SharedVertexSequenceSplitterUnitTest.java +++ b/protected/java/test/org/broadinstitute/sting/gatk/walkers/haplotypecaller/graphs/SharedVertexSequenceSplitterUnitTest.java @@ -227,8 +227,8 @@ public class SharedVertexSequenceSplitterUnitTest extends BaseTest { } final Set haplotypes = new HashSet(); - final List> originalPaths = new KBestPaths().getKBestPaths((SeqGraph)graph.clone()); - for ( final Path path : originalPaths ) + final List> originalPaths = new KBestPaths().getKBestPaths((SeqGraph)graph.clone()); + for ( final Path path : originalPaths ) haplotypes.add(new String(path.getBases())); final SharedVertexSequenceSplitter splitter = new SharedVertexSequenceSplitter(graph, v); @@ -238,8 +238,8 @@ public class SharedVertexSequenceSplitterUnitTest extends BaseTest { splitter.updateGraph(top, bot); if ( PRINT_GRAPHS ) graph.printGraph(new File(Utils.join("_", strings) + ".updated.dot"), 0); - final List> splitPaths = new KBestPaths().getKBestPaths(graph); - for ( final Path path : splitPaths ) { + final List> splitPaths = new KBestPaths().getKBestPaths(graph); + for ( final Path path : splitPaths ) { final String h = new String(path.getBases()); Assert.assertTrue(haplotypes.contains(h), "Failed to find haplotype " + h); } diff --git a/protected/java/test/org/broadinstitute/sting/gatk/walkers/haplotypecaller/readthreading/ReadThreadingAssemblerUnitTest.java b/protected/java/test/org/broadinstitute/sting/gatk/walkers/haplotypecaller/readthreading/ReadThreadingAssemblerUnitTest.java new file mode 100644 index 000000000..8efb3d486 --- /dev/null +++ b/protected/java/test/org/broadinstitute/sting/gatk/walkers/haplotypecaller/readthreading/ReadThreadingAssemblerUnitTest.java @@ -0,0 +1,213 @@ +/* +* By downloading the PROGRAM you agree to the following terms of use: +* +* BROAD INSTITUTE - SOFTWARE LICENSE AGREEMENT - FOR ACADEMIC NON-COMMERCIAL RESEARCH PURPOSES ONLY +* +* This Agreement is made between the Broad Institute, Inc. with a principal address at 7 Cambridge Center, Cambridge, MA 02142 (BROAD) and the LICENSEE and is effective at the date the downloading is completed (EFFECTIVE DATE). +* +* WHEREAS, LICENSEE desires to license the PROGRAM, as defined hereinafter, and BROAD wishes to have this PROGRAM utilized in the public interest, subject only to the royalty-free, nonexclusive, nontransferable license rights of the United States Government pursuant to 48 CFR 52.227-14; and +* WHEREAS, LICENSEE desires to license the PROGRAM and BROAD desires to grant a license on the following terms and conditions. +* NOW, THEREFORE, in consideration of the promises and covenants made herein, the parties hereto agree as follows: +* +* 1. DEFINITIONS +* 1.1 PROGRAM shall mean copyright in the object code and source code known as GATK2 and related documentation, if any, as they exist on the EFFECTIVE DATE and can be downloaded from http://www.broadinstitute/GATK on the EFFECTIVE DATE. +* +* 2. LICENSE +* 2.1 Grant. Subject to the terms of this Agreement, BROAD hereby grants to LICENSEE, solely for academic non-commercial research purposes, a non-exclusive, non-transferable license to: (a) download, execute and display the PROGRAM and (b) create bug fixes and modify the PROGRAM. +* The LICENSEE may apply the PROGRAM in a pipeline to data owned by users other than the LICENSEE and provide these users the results of the PROGRAM provided LICENSEE does so for academic non-commercial purposes only. For clarification purposes, academic sponsored research is not a commercial use under the terms of this Agreement. +* 2.2 No Sublicensing or Additional Rights. LICENSEE shall not sublicense or distribute the PROGRAM, in whole or in part, without prior written permission from BROAD. LICENSEE shall ensure that all of its users agree to the terms of this Agreement. LICENSEE further agrees that it shall not put the PROGRAM on a network, server, or other similar technology that may be accessed by anyone other than the LICENSEE and its employees and users who have agreed to the terms of this agreement. +* 2.3 License Limitations. Nothing in this Agreement shall be construed to confer any rights upon LICENSEE by implication, estoppel, or otherwise to any computer software, trademark, intellectual property, or patent rights of BROAD, or of any other entity, except as expressly granted herein. LICENSEE agrees that the PROGRAM, in whole or part, shall not be used for any commercial purpose, including without limitation, as the basis of a commercial software or hardware product or to provide services. LICENSEE further agrees that the PROGRAM shall not be copied or otherwise adapted in order to circumvent the need for obtaining a license for use of the PROGRAM. +* +* 3. OWNERSHIP OF INTELLECTUAL PROPERTY +* LICENSEE acknowledges that title to the PROGRAM shall remain with BROAD. The PROGRAM is marked with the following BROAD copyright notice and notice of attribution to contributors. LICENSEE shall retain such notice on all copies. LICENSEE agrees to include appropriate attribution if any results obtained from use of the PROGRAM are included in any publication. +* Copyright 2012 Broad Institute, Inc. +* Notice of attribution: The GATK2 program was made available through the generosity of Medical and Population Genetics program at the Broad Institute, Inc. +* LICENSEE shall not use any trademark or trade name of BROAD, or any variation, adaptation, or abbreviation, of such marks or trade names, or any names of officers, faculty, students, employees, or agents of BROAD except as states above for attribution purposes. +* +* 4. INDEMNIFICATION +* LICENSEE shall indemnify, defend, and hold harmless BROAD, and their respective officers, faculty, students, employees, associated investigators and agents, and their respective successors, heirs and assigns, (Indemnitees), against any liability, damage, loss, or expense (including reasonable attorneys fees and expenses) incurred by or imposed upon any of the Indemnitees in connection with any claims, suits, actions, demands or judgments arising out of any theory of liability (including, without limitation, actions in the form of tort, warranty, or strict liability and regardless of whether such action has any factual basis) pursuant to any right or license granted under this Agreement. +* +* 5. NO REPRESENTATIONS OR WARRANTIES +* THE PROGRAM IS DELIVERED AS IS. BROAD MAKES NO REPRESENTATIONS OR WARRANTIES OF ANY KIND CONCERNING THE PROGRAM OR THE COPYRIGHT, EXPRESS OR IMPLIED, INCLUDING, WITHOUT LIMITATION, WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NONINFRINGEMENT, OR THE ABSENCE OF LATENT OR OTHER DEFECTS, WHETHER OR NOT DISCOVERABLE. BROAD EXTENDS NO WARRANTIES OF ANY KIND AS TO PROGRAM CONFORMITY WITH WHATEVER USER MANUALS OR OTHER LITERATURE MAY BE ISSUED FROM TIME TO TIME. +* IN NO EVENT SHALL BROAD OR ITS RESPECTIVE DIRECTORS, OFFICERS, EMPLOYEES, AFFILIATED INVESTIGATORS AND AFFILIATES BE LIABLE FOR INCIDENTAL OR CONSEQUENTIAL DAMAGES OF ANY KIND, INCLUDING, WITHOUT LIMITATION, ECONOMIC DAMAGES OR INJURY TO PROPERTY AND LOST PROFITS, REGARDLESS OF WHETHER BROAD SHALL BE ADVISED, SHALL HAVE OTHER REASON TO KNOW, OR IN FACT SHALL KNOW OF THE POSSIBILITY OF THE FOREGOING. +* +* 6. ASSIGNMENT +* This Agreement is personal to LICENSEE and any rights or obligations assigned by LICENSEE without the prior written consent of BROAD shall be null and void. +* +* 7. MISCELLANEOUS +* 7.1 Export Control. LICENSEE gives assurance that it will comply with all United States export control laws and regulations controlling the export of the PROGRAM, including, without limitation, all Export Administration Regulations of the United States Department of Commerce. Among other things, these laws and regulations prohibit, or require a license for, the export of certain types of software to specified countries. +* 7.2 Termination. LICENSEE shall have the right to terminate this Agreement for any reason upon prior written notice to BROAD. If LICENSEE breaches any provision hereunder, and fails to cure such breach within thirty (30) days, BROAD may terminate this Agreement immediately. Upon termination, LICENSEE shall provide BROAD with written assurance that the original and all copies of the PROGRAM have been destroyed, except that, upon prior written authorization from BROAD, LICENSEE may retain a copy for archive purposes. +* 7.3 Survival. The following provisions shall survive the expiration or termination of this Agreement: Articles 1, 3, 4, 5 and Sections 2.2, 2.3, 7.3, and 7.4. +* 7.4 Notice. Any notices under this Agreement shall be in writing, shall specifically refer to this Agreement, and shall be sent by hand, recognized national overnight courier, confirmed facsimile transmission, confirmed electronic mail, or registered or certified mail, postage prepaid, return receipt requested. All notices under this Agreement shall be deemed effective upon receipt. +* 7.5 Amendment and Waiver; Entire Agreement. This Agreement may be amended, supplemented, or otherwise modified only by means of a written instrument signed by all parties. Any waiver of any rights or failure to act in a specific instance shall relate only to such instance and shall not be construed as an agreement to waive any rights or fail to act in any other instance, whether or not similar. This Agreement constitutes the entire agreement among the parties with respect to its subject matter and supersedes prior agreements or understandings between the parties relating to its subject matter. +* 7.6 Binding Effect; Headings. This Agreement shall be binding upon and inure to the benefit of the parties and their respective permitted successors and assigns. All headings are for convenience only and shall not affect the meaning of any provision of this Agreement. +* 7.7 Governing Law. This Agreement shall be construed, governed, interpreted and applied in accordance with the internal laws of the Commonwealth of Massachusetts, U.S.A., without regard to conflict of laws principles. +*/ + +package org.broadinstitute.sting.gatk.walkers.haplotypecaller.readthreading; + +import org.broadinstitute.sting.BaseTest; +import org.broadinstitute.sting.gatk.walkers.haplotypecaller.graphs.*; +import org.broadinstitute.sting.utils.Utils; +import org.broadinstitute.sting.utils.haplotype.Haplotype; +import org.broadinstitute.sting.utils.sam.ArtificialSAMUtils; +import org.broadinstitute.sting.utils.sam.GATKSAMRecord; +import org.testng.Assert; +import org.testng.annotations.Test; + +import java.io.File; +import java.util.*; + +public class ReadThreadingAssemblerUnitTest extends BaseTest { + private final static boolean DEBUG = false; + + private static class TestAssembler { + final ReadThreadingAssembler assembler; + + Haplotype refHaplotype; + final List reads = new LinkedList(); + + private TestAssembler(final int kmerSize) { + this.assembler = new ReadThreadingAssembler(100000, Arrays.asList(kmerSize)); + assembler.setJustReturnRawGraph(true); + assembler.setPruneFactor(0); + } + + public void addSequence(final byte[] bases, final boolean isRef) { + if ( isRef ) { + refHaplotype = new Haplotype(bases, true); + } else { + final GATKSAMRecord read = ArtificialSAMUtils.createArtificialRead(bases, Utils.dupBytes((byte)30,bases.length), bases.length + "M"); + reads.add(read); + } + } + + public SeqGraph assemble() { + assembler.removePathsNotConnectedToRef = false; // need to pass some of the tests + assembler.setDebugGraphTransformations(true); + final SeqGraph graph = assembler.assemble(reads, refHaplotype).get(0); + if ( DEBUG ) graph.printGraph(new File("test.dot"), 0); + return graph; + } + } + + private void assertLinearGraph(final TestAssembler assembler, final String seq) { + final SeqGraph graph = assembler.assemble(); + graph.simplifyGraph(); + Assert.assertEquals(graph.vertexSet().size(), 1); + Assert.assertEquals(graph.vertexSet().iterator().next().getSequenceString(), seq); + } + + private void assertSingleBubble(final TestAssembler assembler, final String one, final String two) { + final SeqGraph graph = assembler.assemble(); + graph.simplifyGraph(); + List> paths = new KBestPaths().getKBestPaths(graph); + Assert.assertEquals(paths.size(), 2); + final Set expected = new HashSet(Arrays.asList(one, two)); + for ( final Path path : paths ) { + final String seq = new String(path.getBases()); + Assert.assertTrue(expected.contains(seq)); + expected.remove(seq); + } + } + + @Test(enabled = ! DEBUG) + public void testRefCreation() { + final String ref = "ACGTAACCGGTT"; + final TestAssembler assembler = new TestAssembler(3); + assembler.addSequence(ref.getBytes(), true); + assertLinearGraph(assembler, ref); + } + + @Test(enabled = ! DEBUG) + public void testRefNonUniqueCreation() { + final String ref = "GAAAAT"; + final TestAssembler assembler = new TestAssembler(3); + assembler.addSequence(ref.getBytes(), true); + assertLinearGraph(assembler, ref); + } + + @Test(enabled = ! DEBUG) + public void testRefAltCreation() { + final TestAssembler assembler = new TestAssembler(3); + final String ref = "ACAACTGA"; + final String alt = "ACAGCTGA"; + assembler.addSequence(ref.getBytes(), true); + assembler.addSequence(alt.getBytes(), false); + assertSingleBubble(assembler, ref, alt); + } + + @Test(enabled = ! DEBUG) + public void testPartialReadsCreation() { + final TestAssembler assembler = new TestAssembler(3); + final String ref = "ACAACTGA"; + final String alt1 = "ACAGCT"; + final String alt2 = "GCTGA"; + assembler.addSequence(ref.getBytes(), true); + assembler.addSequence(alt1.getBytes(), false); + assembler.addSequence(alt2.getBytes(), false); + assertSingleBubble(assembler, ref, "ACAGCTGA"); + } + + @Test(enabled = ! DEBUG) + public void testStartInMiddle() { + final TestAssembler assembler = new TestAssembler(3); + final String ref = "CAAAATG"; + final String read = "AAATG"; + assembler.addSequence(ref.getBytes(), true); + assembler.addSequence(read.getBytes(), false); + assertLinearGraph(assembler, ref); + } + + @Test(enabled = ! DEBUG) + public void testStartInMiddleWithBubble() { + final TestAssembler assembler = new TestAssembler(3); + final String ref = "CAAAATGGGG"; + final String read = "AAATCGGG"; + assembler.addSequence(ref.getBytes(), true); + assembler.addSequence(read.getBytes(), false); + assertSingleBubble(assembler, ref, "CAAAATCGGG"); + } + + @Test(enabled = ! DEBUG) + public void testNoGoodStarts() { + final TestAssembler assembler = new TestAssembler(3); + final String ref = "CAAAATGGGG"; + final String read = "AAATCGGG"; + assembler.addSequence(ref.getBytes(), true); + assembler.addSequence(read.getBytes(), false); + assertSingleBubble(assembler, ref, "CAAAATCGGG"); + } + + + @Test(enabled = !DEBUG) + public void testCreateWithBasesBeforeRefSource() { + final TestAssembler assembler = new TestAssembler(3); + final String ref = "ACTG"; + final String read = "CTGGGACT"; + assembler.addSequence(ReadThreadingGraphUnitTest.getBytes(ref), true); + assembler.addSequence(ReadThreadingGraphUnitTest.getBytes(read), false); + assertLinearGraph(assembler, "ACTGGGACT"); + } + + @Test(enabled = !DEBUG) + public void testSingleIndelAsDoubleIndel3Reads() { + final TestAssembler assembler = new TestAssembler(25); + // The single indel spans two repetitive structures + final String ref = "GTTTTTCCTAGGCAAATGGTTTCTATAAAATTATGTGTGTGTGTCTCTCTCTGTGTGTGTGTGTGTGTGTGTGTGTATACCTAATCTCACACTCTTTTTTCTGG"; + final String read1 = "GTTTTTCCTAGGCAAATGGTTTCTATAAAATTATGTGTGTGTGTCTCT----------GTGTGTGTGTGTGTGTGTATACCTAATCTCACACTCTTTTTTCTGG"; + final String read2 = "GTTTTTCCTAGGCAAATGGTTTCTATAAAATTATGTGTGTGTGTCTCT----------GTGTGTGTGTGTGTGTGTATACCTAATCTCACACTCTTTTTTCTGG"; + assembler.addSequence(ReadThreadingGraphUnitTest.getBytes(ref), true); + assembler.addSequence(ReadThreadingGraphUnitTest.getBytes(read1), false); + assembler.addSequence(ReadThreadingGraphUnitTest.getBytes(read2), false); + + final SeqGraph graph = assembler.assemble(); + final KBestPaths pathFinder = new KBestPaths(); + final List> paths = pathFinder.getKBestPaths(graph); + Assert.assertEquals(paths.size(), 2); + final byte[] refPath = paths.get(0).getBases().length == ref.length() ? paths.get(0).getBases() : paths.get(1).getBases(); + final byte[] altPath = paths.get(0).getBases().length == ref.length() ? paths.get(1).getBases() : paths.get(0).getBases(); + Assert.assertEquals(refPath, ReadThreadingGraphUnitTest.getBytes(ref)); + Assert.assertEquals(altPath, ReadThreadingGraphUnitTest.getBytes(read1)); + } +} diff --git a/protected/java/test/org/broadinstitute/sting/gatk/walkers/haplotypecaller/readthreading/ReadThreadingGraphUnitTest.java b/protected/java/test/org/broadinstitute/sting/gatk/walkers/haplotypecaller/readthreading/ReadThreadingGraphUnitTest.java new file mode 100644 index 000000000..10c1cc00d --- /dev/null +++ b/protected/java/test/org/broadinstitute/sting/gatk/walkers/haplotypecaller/readthreading/ReadThreadingGraphUnitTest.java @@ -0,0 +1,191 @@ +/* +* By downloading the PROGRAM you agree to the following terms of use: +* +* BROAD INSTITUTE - SOFTWARE LICENSE AGREEMENT - FOR ACADEMIC NON-COMMERCIAL RESEARCH PURPOSES ONLY +* +* This Agreement is made between the Broad Institute, Inc. with a principal address at 7 Cambridge Center, Cambridge, MA 02142 (BROAD) and the LICENSEE and is effective at the date the downloading is completed (EFFECTIVE DATE). +* +* WHEREAS, LICENSEE desires to license the PROGRAM, as defined hereinafter, and BROAD wishes to have this PROGRAM utilized in the public interest, subject only to the royalty-free, nonexclusive, nontransferable license rights of the United States Government pursuant to 48 CFR 52.227-14; and +* WHEREAS, LICENSEE desires to license the PROGRAM and BROAD desires to grant a license on the following terms and conditions. +* NOW, THEREFORE, in consideration of the promises and covenants made herein, the parties hereto agree as follows: +* +* 1. DEFINITIONS +* 1.1 PROGRAM shall mean copyright in the object code and source code known as GATK2 and related documentation, if any, as they exist on the EFFECTIVE DATE and can be downloaded from http://www.broadinstitute/GATK on the EFFECTIVE DATE. +* +* 2. LICENSE +* 2.1 Grant. Subject to the terms of this Agreement, BROAD hereby grants to LICENSEE, solely for academic non-commercial research purposes, a non-exclusive, non-transferable license to: (a) download, execute and display the PROGRAM and (b) create bug fixes and modify the PROGRAM. +* The LICENSEE may apply the PROGRAM in a pipeline to data owned by users other than the LICENSEE and provide these users the results of the PROGRAM provided LICENSEE does so for academic non-commercial purposes only. For clarification purposes, academic sponsored research is not a commercial use under the terms of this Agreement. +* 2.2 No Sublicensing or Additional Rights. LICENSEE shall not sublicense or distribute the PROGRAM, in whole or in part, without prior written permission from BROAD. LICENSEE shall ensure that all of its users agree to the terms of this Agreement. LICENSEE further agrees that it shall not put the PROGRAM on a network, server, or other similar technology that may be accessed by anyone other than the LICENSEE and its employees and users who have agreed to the terms of this agreement. +* 2.3 License Limitations. Nothing in this Agreement shall be construed to confer any rights upon LICENSEE by implication, estoppel, or otherwise to any computer software, trademark, intellectual property, or patent rights of BROAD, or of any other entity, except as expressly granted herein. LICENSEE agrees that the PROGRAM, in whole or part, shall not be used for any commercial purpose, including without limitation, as the basis of a commercial software or hardware product or to provide services. LICENSEE further agrees that the PROGRAM shall not be copied or otherwise adapted in order to circumvent the need for obtaining a license for use of the PROGRAM. +* +* 3. OWNERSHIP OF INTELLECTUAL PROPERTY +* LICENSEE acknowledges that title to the PROGRAM shall remain with BROAD. The PROGRAM is marked with the following BROAD copyright notice and notice of attribution to contributors. LICENSEE shall retain such notice on all copies. LICENSEE agrees to include appropriate attribution if any results obtained from use of the PROGRAM are included in any publication. +* Copyright 2012 Broad Institute, Inc. +* Notice of attribution: The GATK2 program was made available through the generosity of Medical and Population Genetics program at the Broad Institute, Inc. +* LICENSEE shall not use any trademark or trade name of BROAD, or any variation, adaptation, or abbreviation, of such marks or trade names, or any names of officers, faculty, students, employees, or agents of BROAD except as states above for attribution purposes. +* +* 4. INDEMNIFICATION +* LICENSEE shall indemnify, defend, and hold harmless BROAD, and their respective officers, faculty, students, employees, associated investigators and agents, and their respective successors, heirs and assigns, (Indemnitees), against any liability, damage, loss, or expense (including reasonable attorneys fees and expenses) incurred by or imposed upon any of the Indemnitees in connection with any claims, suits, actions, demands or judgments arising out of any theory of liability (including, without limitation, actions in the form of tort, warranty, or strict liability and regardless of whether such action has any factual basis) pursuant to any right or license granted under this Agreement. +* +* 5. NO REPRESENTATIONS OR WARRANTIES +* THE PROGRAM IS DELIVERED AS IS. BROAD MAKES NO REPRESENTATIONS OR WARRANTIES OF ANY KIND CONCERNING THE PROGRAM OR THE COPYRIGHT, EXPRESS OR IMPLIED, INCLUDING, WITHOUT LIMITATION, WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NONINFRINGEMENT, OR THE ABSENCE OF LATENT OR OTHER DEFECTS, WHETHER OR NOT DISCOVERABLE. BROAD EXTENDS NO WARRANTIES OF ANY KIND AS TO PROGRAM CONFORMITY WITH WHATEVER USER MANUALS OR OTHER LITERATURE MAY BE ISSUED FROM TIME TO TIME. +* IN NO EVENT SHALL BROAD OR ITS RESPECTIVE DIRECTORS, OFFICERS, EMPLOYEES, AFFILIATED INVESTIGATORS AND AFFILIATES BE LIABLE FOR INCIDENTAL OR CONSEQUENTIAL DAMAGES OF ANY KIND, INCLUDING, WITHOUT LIMITATION, ECONOMIC DAMAGES OR INJURY TO PROPERTY AND LOST PROFITS, REGARDLESS OF WHETHER BROAD SHALL BE ADVISED, SHALL HAVE OTHER REASON TO KNOW, OR IN FACT SHALL KNOW OF THE POSSIBILITY OF THE FOREGOING. +* +* 6. ASSIGNMENT +* This Agreement is personal to LICENSEE and any rights or obligations assigned by LICENSEE without the prior written consent of BROAD shall be null and void. +* +* 7. MISCELLANEOUS +* 7.1 Export Control. LICENSEE gives assurance that it will comply with all United States export control laws and regulations controlling the export of the PROGRAM, including, without limitation, all Export Administration Regulations of the United States Department of Commerce. Among other things, these laws and regulations prohibit, or require a license for, the export of certain types of software to specified countries. +* 7.2 Termination. LICENSEE shall have the right to terminate this Agreement for any reason upon prior written notice to BROAD. If LICENSEE breaches any provision hereunder, and fails to cure such breach within thirty (30) days, BROAD may terminate this Agreement immediately. Upon termination, LICENSEE shall provide BROAD with written assurance that the original and all copies of the PROGRAM have been destroyed, except that, upon prior written authorization from BROAD, LICENSEE may retain a copy for archive purposes. +* 7.3 Survival. The following provisions shall survive the expiration or termination of this Agreement: Articles 1, 3, 4, 5 and Sections 2.2, 2.3, 7.3, and 7.4. +* 7.4 Notice. Any notices under this Agreement shall be in writing, shall specifically refer to this Agreement, and shall be sent by hand, recognized national overnight courier, confirmed facsimile transmission, confirmed electronic mail, or registered or certified mail, postage prepaid, return receipt requested. All notices under this Agreement shall be deemed effective upon receipt. +* 7.5 Amendment and Waiver; Entire Agreement. This Agreement may be amended, supplemented, or otherwise modified only by means of a written instrument signed by all parties. Any waiver of any rights or failure to act in a specific instance shall relate only to such instance and shall not be construed as an agreement to waive any rights or fail to act in any other instance, whether or not similar. This Agreement constitutes the entire agreement among the parties with respect to its subject matter and supersedes prior agreements or understandings between the parties relating to its subject matter. +* 7.6 Binding Effect; Headings. This Agreement shall be binding upon and inure to the benefit of the parties and their respective permitted successors and assigns. All headings are for convenience only and shall not affect the meaning of any provision of this Agreement. +* 7.7 Governing Law. This Agreement shall be construed, governed, interpreted and applied in accordance with the internal laws of the Commonwealth of Massachusetts, U.S.A., without regard to conflict of laws principles. +*/ + +package org.broadinstitute.sting.gatk.walkers.haplotypecaller.readthreading; + +import org.broadinstitute.sting.BaseTest; +import org.broadinstitute.sting.gatk.walkers.haplotypecaller.Kmer; +import org.broadinstitute.sting.gatk.walkers.haplotypecaller.graphs.MultiSampleEdge; +import org.testng.Assert; +import org.testng.annotations.Test; + +import java.io.File; +import java.util.*; + +public class ReadThreadingGraphUnitTest extends BaseTest { + private final static boolean DEBUG = false; + + public static byte[] getBytes(final String alignment) { + return alignment.replace("-","").getBytes(); + } + + private void assertNonUniques(final ReadThreadingGraph assembler, String ... nonUniques) { + final Set actual = new HashSet<>(); + assembler.buildGraphIfNecessary(); + for ( final Kmer kmer : assembler.getNonUniqueKmers() ) actual.add(kmer.baseString()); + final Set expected = new HashSet<>(Arrays.asList(nonUniques)); + Assert.assertEquals(actual, expected); + } + + @Test(enabled = ! DEBUG) + public void testNonUniqueMiddle() { + final ReadThreadingGraph assembler = new ReadThreadingGraph(3); + final String ref = "GACACACAGTCA"; + final String read1 = "GACAC---GTCA"; + final String read2 = "CAC---GTCA"; + assembler.addSequence(getBytes(ref), true); + assembler.addSequence(getBytes(read1), false); + assembler.addSequence(getBytes(read2), false); + assertNonUniques(assembler, "ACA", "CAC"); + } + + @Test(enabled = ! DEBUG) + public void testReadsCreateNonUnique() { + final ReadThreadingGraph assembler = new ReadThreadingGraph(3); + final String ref = "GCAC--GTCA"; // CAC is unique + final String read1 = "GCACACGTCA"; // makes CAC non unique because it has a duplication + final String read2 = "CACGTCA"; // shouldn't be allowed to match CAC as start + assembler.addSequence(getBytes(ref), true); + assembler.addSequence(getBytes(read1), false); + assembler.addSequence(getBytes(read2), false); +// assembler.convertToSequenceGraph().printGraph(new File("test.dot"), 0); + + assertNonUniques(assembler, "CAC"); + //assertSingleBubble(assembler, ref, "CAAAATCGGG"); + } + + @Test(enabled = ! DEBUG) + public void testCountingOfStartEdges() { + final ReadThreadingGraph assembler = new ReadThreadingGraph(3); + final String ref = "NNNGTCAAA"; // ref has some bases before start + final String read1 = "GTCAAA"; // starts at first non N base + + assembler.addSequence(getBytes(ref), true); + assembler.addSequence(getBytes(read1), false); + assembler.buildGraphIfNecessary(); +// assembler.printGraph(new File("test.dot"), 0); + + for ( final MultiSampleEdge edge : assembler.edgeSet() ) { + final MultiDeBruijnVertex source = assembler.getEdgeSource(edge); + final MultiDeBruijnVertex target = assembler.getEdgeTarget(edge); + final boolean headerVertex = source.getSuffix() == 'N' || target.getSuffix() == 'N'; + if ( headerVertex ) { + Assert.assertEquals(edge.getMultiplicity(), 1, "Bases in the unique reference header should have multiplicity of 1"); + } else { + Assert.assertEquals(edge.getMultiplicity(), 2, "Should have multiplicity of 2 for any edge outside the ref header but got " + edge + " " + source + " -> " + target); + } + } + } + + @Test(enabled = !DEBUG) + public void testCountingOfStartEdgesWithMultiplePrefixes() { + final ReadThreadingGraph assembler = new ReadThreadingGraph(3); + assembler.increaseCountsThroughBranches = true; + final String ref = "NNNGTCAXX"; // ref has some bases before start + final String alt1 = "NNNCTCAXX"; // alt1 has SNP right after N + final String read = "TCAXX"; // starts right after SNP, but merges right before branch + + assembler.addSequence(getBytes(ref), true); + assembler.addSequence(getBytes(alt1), false); + assembler.addSequence(getBytes(read), false); + assembler.buildGraphIfNecessary(); + assembler.printGraph(new File("test.dot"), 0); + + final List oneCountVertices = Arrays.asList("NNN", "NNG", "NNC", "NGT", "NCT"); + final List threeCountVertices = Arrays.asList("CAX", "AXX"); + + for ( final MultiSampleEdge edge : assembler.edgeSet() ) { + final MultiDeBruijnVertex source = assembler.getEdgeSource(edge); + final MultiDeBruijnVertex target = assembler.getEdgeTarget(edge); + final int expected = oneCountVertices.contains(target.getSequenceString()) ? 1 : (threeCountVertices.contains(target.getSequenceString()) ? 3 : 2); + Assert.assertEquals(edge.getMultiplicity(), expected, "Bases at edge " + edge + " from " + source + " to " + target + " has bad multiplicity"); + } + } + + // TODO -- update to use determineKmerSizeAndNonUniques directly +// @DataProvider(name = "KmerSizeData") +// public Object[][] makeKmerSizeDataProvider() { +// List tests = new ArrayList(); +// +// // this functionality can be adapted to provide input data for whatever you might want in your data +// tests.add(new Object[]{3, 3, 3, Arrays.asList("ACG"), Arrays.asList()}); +// tests.add(new Object[]{3, 4, 3, Arrays.asList("CAGACG"), Arrays.asList()}); +// +// tests.add(new Object[]{3, 3, 3, Arrays.asList("AAAAC"), Arrays.asList("AAA")}); +// tests.add(new Object[]{3, 4, 4, Arrays.asList("AAAAC"), Arrays.asList()}); +// tests.add(new Object[]{3, 5, 4, Arrays.asList("AAAAC"), Arrays.asList()}); +// tests.add(new Object[]{3, 4, 3, Arrays.asList("CAAA"), Arrays.asList()}); +// tests.add(new Object[]{3, 4, 4, Arrays.asList("CAAAA"), Arrays.asList()}); +// tests.add(new Object[]{3, 5, 4, Arrays.asList("CAAAA"), Arrays.asList()}); +// tests.add(new Object[]{3, 5, 5, Arrays.asList("ACGAAAAACG"), Arrays.asList()}); +// +// for ( int maxSize = 3; maxSize < 20; maxSize++ ) { +// for ( int dupSize = 3; dupSize < 20; dupSize++ ) { +// final int expectedSize = Math.min(maxSize, dupSize); +// final String dup = Utils.dupString("C", dupSize); +// final List nonUnique = dupSize > maxSize ? Arrays.asList(Utils.dupString("C", maxSize)) : Collections.emptyList(); +// tests.add(new Object[]{3, maxSize, expectedSize, Arrays.asList("ACGT", "A" + dup + "GT"), nonUnique}); +// tests.add(new Object[]{3, maxSize, expectedSize, Arrays.asList("A" + dup + "GT", "ACGT"), nonUnique}); +// } +// } +// +// return tests.toArray(new Object[][]{}); +// } +// +// /** +// * Example testng test using MyDataProvider +// */ +// @Test(dataProvider = "KmerSizeData") +// public void testDynamicKmerSizing(final int min, final int max, final int expectKmer, final List seqs, final List expectedNonUniques) { +// final ReadThreadingGraph assembler = new ReadThreadingGraph(min, max); +// for ( String seq : seqs ) assembler.addSequence(seq.getBytes(), false); +// assembler.buildGraphIfNecessary(); +// Assert.assertEquals(assembler.getKmerSize(), expectKmer); +// assertNonUniques(assembler, expectedNonUniques.toArray(new String[]{})); +// } + + +} diff --git a/protected/java/test/org/broadinstitute/sting/gatk/walkers/haplotypecaller/readthreading/SequenceForKmersUnitTest.java b/protected/java/test/org/broadinstitute/sting/gatk/walkers/haplotypecaller/readthreading/SequenceForKmersUnitTest.java new file mode 100644 index 000000000..7c3160c30 --- /dev/null +++ b/protected/java/test/org/broadinstitute/sting/gatk/walkers/haplotypecaller/readthreading/SequenceForKmersUnitTest.java @@ -0,0 +1,80 @@ +/* +* By downloading the PROGRAM you agree to the following terms of use: +* +* BROAD INSTITUTE - SOFTWARE LICENSE AGREEMENT - FOR ACADEMIC NON-COMMERCIAL RESEARCH PURPOSES ONLY +* +* This Agreement is made between the Broad Institute, Inc. with a principal address at 7 Cambridge Center, Cambridge, MA 02142 (BROAD) and the LICENSEE and is effective at the date the downloading is completed (EFFECTIVE DATE). +* +* WHEREAS, LICENSEE desires to license the PROGRAM, as defined hereinafter, and BROAD wishes to have this PROGRAM utilized in the public interest, subject only to the royalty-free, nonexclusive, nontransferable license rights of the United States Government pursuant to 48 CFR 52.227-14; and +* WHEREAS, LICENSEE desires to license the PROGRAM and BROAD desires to grant a license on the following terms and conditions. +* NOW, THEREFORE, in consideration of the promises and covenants made herein, the parties hereto agree as follows: +* +* 1. DEFINITIONS +* 1.1 PROGRAM shall mean copyright in the object code and source code known as GATK2 and related documentation, if any, as they exist on the EFFECTIVE DATE and can be downloaded from http://www.broadinstitute/GATK on the EFFECTIVE DATE. +* +* 2. LICENSE +* 2.1 Grant. Subject to the terms of this Agreement, BROAD hereby grants to LICENSEE, solely for academic non-commercial research purposes, a non-exclusive, non-transferable license to: (a) download, execute and display the PROGRAM and (b) create bug fixes and modify the PROGRAM. +* The LICENSEE may apply the PROGRAM in a pipeline to data owned by users other than the LICENSEE and provide these users the results of the PROGRAM provided LICENSEE does so for academic non-commercial purposes only. For clarification purposes, academic sponsored research is not a commercial use under the terms of this Agreement. +* 2.2 No Sublicensing or Additional Rights. LICENSEE shall not sublicense or distribute the PROGRAM, in whole or in part, without prior written permission from BROAD. LICENSEE shall ensure that all of its users agree to the terms of this Agreement. LICENSEE further agrees that it shall not put the PROGRAM on a network, server, or other similar technology that may be accessed by anyone other than the LICENSEE and its employees and users who have agreed to the terms of this agreement. +* 2.3 License Limitations. Nothing in this Agreement shall be construed to confer any rights upon LICENSEE by implication, estoppel, or otherwise to any computer software, trademark, intellectual property, or patent rights of BROAD, or of any other entity, except as expressly granted herein. LICENSEE agrees that the PROGRAM, in whole or part, shall not be used for any commercial purpose, including without limitation, as the basis of a commercial software or hardware product or to provide services. LICENSEE further agrees that the PROGRAM shall not be copied or otherwise adapted in order to circumvent the need for obtaining a license for use of the PROGRAM. +* +* 3. OWNERSHIP OF INTELLECTUAL PROPERTY +* LICENSEE acknowledges that title to the PROGRAM shall remain with BROAD. The PROGRAM is marked with the following BROAD copyright notice and notice of attribution to contributors. LICENSEE shall retain such notice on all copies. LICENSEE agrees to include appropriate attribution if any results obtained from use of the PROGRAM are included in any publication. +* Copyright 2012 Broad Institute, Inc. +* Notice of attribution: The GATK2 program was made available through the generosity of Medical and Population Genetics program at the Broad Institute, Inc. +* LICENSEE shall not use any trademark or trade name of BROAD, or any variation, adaptation, or abbreviation, of such marks or trade names, or any names of officers, faculty, students, employees, or agents of BROAD except as states above for attribution purposes. +* +* 4. INDEMNIFICATION +* LICENSEE shall indemnify, defend, and hold harmless BROAD, and their respective officers, faculty, students, employees, associated investigators and agents, and their respective successors, heirs and assigns, (Indemnitees), against any liability, damage, loss, or expense (including reasonable attorneys fees and expenses) incurred by or imposed upon any of the Indemnitees in connection with any claims, suits, actions, demands or judgments arising out of any theory of liability (including, without limitation, actions in the form of tort, warranty, or strict liability and regardless of whether such action has any factual basis) pursuant to any right or license granted under this Agreement. +* +* 5. NO REPRESENTATIONS OR WARRANTIES +* THE PROGRAM IS DELIVERED AS IS. BROAD MAKES NO REPRESENTATIONS OR WARRANTIES OF ANY KIND CONCERNING THE PROGRAM OR THE COPYRIGHT, EXPRESS OR IMPLIED, INCLUDING, WITHOUT LIMITATION, WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NONINFRINGEMENT, OR THE ABSENCE OF LATENT OR OTHER DEFECTS, WHETHER OR NOT DISCOVERABLE. BROAD EXTENDS NO WARRANTIES OF ANY KIND AS TO PROGRAM CONFORMITY WITH WHATEVER USER MANUALS OR OTHER LITERATURE MAY BE ISSUED FROM TIME TO TIME. +* IN NO EVENT SHALL BROAD OR ITS RESPECTIVE DIRECTORS, OFFICERS, EMPLOYEES, AFFILIATED INVESTIGATORS AND AFFILIATES BE LIABLE FOR INCIDENTAL OR CONSEQUENTIAL DAMAGES OF ANY KIND, INCLUDING, WITHOUT LIMITATION, ECONOMIC DAMAGES OR INJURY TO PROPERTY AND LOST PROFITS, REGARDLESS OF WHETHER BROAD SHALL BE ADVISED, SHALL HAVE OTHER REASON TO KNOW, OR IN FACT SHALL KNOW OF THE POSSIBILITY OF THE FOREGOING. +* +* 6. ASSIGNMENT +* This Agreement is personal to LICENSEE and any rights or obligations assigned by LICENSEE without the prior written consent of BROAD shall be null and void. +* +* 7. MISCELLANEOUS +* 7.1 Export Control. LICENSEE gives assurance that it will comply with all United States export control laws and regulations controlling the export of the PROGRAM, including, without limitation, all Export Administration Regulations of the United States Department of Commerce. Among other things, these laws and regulations prohibit, or require a license for, the export of certain types of software to specified countries. +* 7.2 Termination. LICENSEE shall have the right to terminate this Agreement for any reason upon prior written notice to BROAD. If LICENSEE breaches any provision hereunder, and fails to cure such breach within thirty (30) days, BROAD may terminate this Agreement immediately. Upon termination, LICENSEE shall provide BROAD with written assurance that the original and all copies of the PROGRAM have been destroyed, except that, upon prior written authorization from BROAD, LICENSEE may retain a copy for archive purposes. +* 7.3 Survival. The following provisions shall survive the expiration or termination of this Agreement: Articles 1, 3, 4, 5 and Sections 2.2, 2.3, 7.3, and 7.4. +* 7.4 Notice. Any notices under this Agreement shall be in writing, shall specifically refer to this Agreement, and shall be sent by hand, recognized national overnight courier, confirmed facsimile transmission, confirmed electronic mail, or registered or certified mail, postage prepaid, return receipt requested. All notices under this Agreement shall be deemed effective upon receipt. +* 7.5 Amendment and Waiver; Entire Agreement. This Agreement may be amended, supplemented, or otherwise modified only by means of a written instrument signed by all parties. Any waiver of any rights or failure to act in a specific instance shall relate only to such instance and shall not be construed as an agreement to waive any rights or fail to act in any other instance, whether or not similar. This Agreement constitutes the entire agreement among the parties with respect to its subject matter and supersedes prior agreements or understandings between the parties relating to its subject matter. +* 7.6 Binding Effect; Headings. This Agreement shall be binding upon and inure to the benefit of the parties and their respective permitted successors and assigns. All headings are for convenience only and shall not affect the meaning of any provision of this Agreement. +* 7.7 Governing Law. This Agreement shall be construed, governed, interpreted and applied in accordance with the internal laws of the Commonwealth of Massachusetts, U.S.A., without regard to conflict of laws principles. +*/ + +package org.broadinstitute.sting.gatk.walkers.haplotypecaller.readthreading; + +import org.broadinstitute.sting.BaseTest; +import org.broadinstitute.sting.utils.Utils; +import org.testng.Assert; +import org.testng.annotations.Test; + +public class SequenceForKmersUnitTest extends BaseTest { + @Test + public void testNoCount() { + final byte[] seq = "ACGT".getBytes(); + final SequenceForKmers sk = new SequenceForKmers("foo", seq, 0, seq.length, null, true); + Assert.assertEquals(sk.name, "foo"); + Assert.assertEquals(sk.sequence, seq); + Assert.assertEquals(sk.start, 0); + Assert.assertEquals(sk.stop, seq.length); + Assert.assertEquals(sk.isRef, true); + for ( int i = 0; i < seq.length; i++ ) + Assert.assertEquals(sk.getCount(i), 1); + } + + @Test + public void testWithCounts() { + final int len = 256; + final int[] counts = new int[len]; + for ( int i = 0; i < len; i++ ) counts[i] = i; + final byte[] seq = Utils.dupBytes((byte)'A', len); + + final SequenceForKmers sk = new SequenceForKmers("foo", seq, 0, seq.length, counts, true); + + for ( int i = 0; i < seq.length; i++ ) + Assert.assertEquals(sk.getCount(i), i); + } +} diff --git a/public/java/src/org/broadinstitute/sting/gatk/traversals/TraverseActiveRegions.java b/public/java/src/org/broadinstitute/sting/gatk/traversals/TraverseActiveRegions.java index 1daaaf1da..f9a4fcdbb 100644 --- a/public/java/src/org/broadinstitute/sting/gatk/traversals/TraverseActiveRegions.java +++ b/public/java/src/org/broadinstitute/sting/gatk/traversals/TraverseActiveRegions.java @@ -438,7 +438,7 @@ public class TraverseActiveRegions extends TraversalEngine= 0 + */ + public static int longestCommonPrefix(final byte[] seq1, final byte[] seq2, final int maxLength) { + if ( seq1 == null ) throw new IllegalArgumentException("seq1 is null"); + if ( seq2 == null ) throw new IllegalArgumentException("seq2 is null"); + if ( maxLength < 0 ) throw new IllegalArgumentException("maxLength < 0 " + maxLength); + + final int end = Math.min(seq1.length, Math.min(seq2.length, maxLength)); + for ( int i = 0; i < end; i++ ) { + if ( seq1[i] != seq2[i] ) + return i; + } + return end; + } + + /** + * Get the length of the longest common suffix of seq1 and seq2 + * @param seq1 non-null byte array + * @param seq2 non-null byte array + * @param maxLength the maximum allowed length to return + * @return the length of the longest common suffix of seq1 and seq2, >= 0 + */ + public static int longestCommonSuffix(final byte[] seq1, final byte[] seq2, final int maxLength) { + if ( seq1 == null ) throw new IllegalArgumentException("seq1 is null"); + if ( seq2 == null ) throw new IllegalArgumentException("seq2 is null"); + if ( maxLength < 0 ) throw new IllegalArgumentException("maxLength < 0 " + maxLength); + + final int end = Math.min(seq1.length, Math.min(seq2.length, maxLength)); + for ( int i = 0; i < end; i++ ) { + if ( seq1[seq1.length - i - 1] != seq2[seq2.length - i - 1] ) + return i; + } + return end; + } } diff --git a/public/java/src/org/broadinstitute/sting/utils/smithwaterman/SWPairwiseAlignment.java b/public/java/src/org/broadinstitute/sting/utils/smithwaterman/SWPairwiseAlignment.java index 890faa82a..78f81ec5e 100644 --- a/public/java/src/org/broadinstitute/sting/utils/smithwaterman/SWPairwiseAlignment.java +++ b/public/java/src/org/broadinstitute/sting/utils/smithwaterman/SWPairwiseAlignment.java @@ -45,7 +45,7 @@ import java.util.*; * Date: Mar 23, 2009 * Time: 1:54:54 PM */ -public final class SWPairwiseAlignment { +public final class SWPairwiseAlignment implements SmithWaterman { private int alignment_offset; // offset of s2 w/respect to s1 private Cigar alignmentCigar; @@ -57,7 +57,7 @@ public final class SWPairwiseAlignment { private static final int CLIP = 3; protected static boolean cutoff = false; - private static boolean DO_SOFTCLIP = true; + private boolean doSoftClipping = true; /** * The SW scoring matrix, stored for debugging purposes if keepScoringMatrix is true @@ -90,10 +90,23 @@ public final class SWPairwiseAlignment { * @param parameters the SW parameters to use */ public SWPairwiseAlignment(byte[] seq1, byte[] seq2, Parameters parameters) { - this.parameters = parameters; + this(parameters); align(seq1,seq2); } + /** + * Create a new SW pairwise aligner, without actually doing any alignment yet + * + * @param parameters the SW parameters to use + */ + protected SWPairwiseAlignment(Parameters parameters) { + this.parameters = parameters; + } + + protected void setDoSoftClipping(final boolean doSoftClipping) { + this.doSoftClipping = doSoftClipping; + } + /** * Create a new SW pairwise aligner * @@ -111,8 +124,10 @@ public final class SWPairwiseAlignment { this(seq1,seq2,SWParameterSet.ORIGINAL_DEFAULT); } + @Override public Cigar getCigar() { return alignmentCigar ; } + @Override public int getAlignmentStart2wrt1() { return alignment_offset; } public void align(final byte[] a, final byte[] b) { @@ -265,7 +280,7 @@ public final class SWPairwiseAlignment { List lce = new ArrayList(5); - if ( segment_length > 0 && DO_SOFTCLIP ) { + if ( segment_length > 0 && doSoftClipping ) { lce.add(makeElement(CLIP, segment_length)); segment_length = 0; } @@ -316,7 +331,7 @@ public final class SWPairwiseAlignment { // last 3 bases of the read overlap with/align to the ref), the cigar will be still 5M if // DO_SOFTCLIP is false or 2S3M if DO_SOFTCLIP is true. // The consumers need to check for the alignment offset and deal with it properly. - if (DO_SOFTCLIP ) { + if (doSoftClipping ) { lce.add(makeElement(state, segment_length)); if ( p2> 0 ) lce.add(makeElement(CLIP, p2)); alignment_offset = p1 ; @@ -360,7 +375,7 @@ public final class SWPairwiseAlignment { Cigar cigar = getCigar(); - if ( ! DO_SOFTCLIP ) { + if ( ! doSoftClipping ) { // we need to go through all the hassle below only if we do not do softclipping; // otherwise offset is never negative diff --git a/public/java/src/org/broadinstitute/sting/utils/smithwaterman/SmithWaterman.java b/public/java/src/org/broadinstitute/sting/utils/smithwaterman/SmithWaterman.java new file mode 100644 index 000000000..44fd889c5 --- /dev/null +++ b/public/java/src/org/broadinstitute/sting/utils/smithwaterman/SmithWaterman.java @@ -0,0 +1,56 @@ +/* +* Copyright (c) 2012 The Broad Institute +* +* Permission is hereby granted, free of charge, to any person +* obtaining a copy of this software and associated documentation +* files (the "Software"), to deal in the Software without +* restriction, including without limitation the rights to use, +* copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the +* Software is furnished to do so, subject to the following +* conditions: +* +* The above copyright notice and this permission notice shall be +* included in all copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR +* THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +package org.broadinstitute.sting.utils.smithwaterman; + +import net.sf.samtools.Cigar; + +/** + * Generic interface for SmithWaterman calculations + * + * This interface allows clients to use a generic SmithWaterman variable, without propogating the specific + * implementation of SmithWaterman throughout their code: + * + * SmithWaterman sw = new SpecificSmithWatermanImplementation(ref, read, params) + * sw.getCigar() + * sw.getAlignmentStart2wrt1() + * + * User: depristo + * Date: 4/26/13 + * Time: 8:24 AM + */ +public interface SmithWaterman { + /** + * Get the cigar string for the alignment of this SmithWaterman class + * @return a non-null cigar + */ + public Cigar getCigar(); + + /** + * Get the starting position of the read sequence in the reference sequence + * @return a positive integer >= 0 + */ + public int getAlignmentStart2wrt1(); +} diff --git a/public/java/test/org/broadinstitute/sting/utils/UtilsUnitTest.java b/public/java/test/org/broadinstitute/sting/utils/UtilsUnitTest.java index 154b000ce..3c68b8753 100644 --- a/public/java/test/org/broadinstitute/sting/utils/UtilsUnitTest.java +++ b/public/java/test/org/broadinstitute/sting/utils/UtilsUnitTest.java @@ -29,6 +29,7 @@ import org.apache.commons.io.FileUtils; import org.broadinstitute.sting.utils.io.IOUtils; import org.testng.Assert; import org.broadinstitute.sting.BaseTest; +import org.testng.annotations.DataProvider; import org.testng.annotations.Test; import java.io.File; @@ -189,4 +190,30 @@ public class UtilsUnitTest extends BaseTest { final String sourceString = FileUtils.readFileToString(source); Assert.assertEquals(Utils.calcMD5(sourceString), sourceMD5); } + + @Test + public void testLongestCommonOps() { + for ( int prefixLen = 0; prefixLen < 20; prefixLen++ ) { + for ( int extraSeq1Len = 0; extraSeq1Len < 10; extraSeq1Len++ ) { + for ( int extraSeq2Len = 0; extraSeq2Len < 10; extraSeq2Len++ ) { + for ( int max = 0; max < 50; max++ ) { + final String prefix = Utils.dupString("A", prefixLen); + final int expected = Math.min(prefixLen, max); + + { + final String seq1 = prefix + Utils.dupString("C", extraSeq1Len); + final String seq2 = prefix + Utils.dupString("G", extraSeq1Len); + Assert.assertEquals(Utils.longestCommonPrefix(seq1.getBytes(), seq2.getBytes(), max), expected, "LongestCommonPrefix failed: seq1 " + seq1 + " seq2 " + seq2 + " max " + max); + } + + { + final String seq1 = Utils.dupString("C", extraSeq1Len) + prefix; + final String seq2 = Utils.dupString("G", extraSeq1Len) + prefix; + Assert.assertEquals(Utils.longestCommonSuffix(seq1.getBytes(), seq2.getBytes(), max), expected, "longestCommonSuffix failed: seq1 " + seq1 + " seq2 " + seq2 + " max " + max); + } + } + } + } + } + } } diff --git a/public/java/test/org/broadinstitute/sting/utils/clipping/ReadClipperUnitTest.java b/public/java/test/org/broadinstitute/sting/utils/clipping/ReadClipperUnitTest.java index ae7c1e01c..6ec4336b0 100644 --- a/public/java/test/org/broadinstitute/sting/utils/clipping/ReadClipperUnitTest.java +++ b/public/java/test/org/broadinstitute/sting/utils/clipping/ReadClipperUnitTest.java @@ -33,8 +33,10 @@ import org.broadinstitute.sting.utils.Utils; import org.broadinstitute.sting.utils.sam.GATKSAMRecord; import org.testng.Assert; import org.testng.annotations.BeforeClass; +import org.testng.annotations.DataProvider; import org.testng.annotations.Test; +import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; @@ -86,6 +88,30 @@ public class ReadClipperUnitTest extends BaseTest { } } + @DataProvider(name = "ClippedReadLengthData") + public Object[][] makeClippedReadLengthData() { + List tests = new ArrayList(); + + // this functionality can be adapted to provide input data for whatever you might want in your data + final int originalReadLength = 50; + for ( int nToClip = 1; nToClip < originalReadLength - 1; nToClip++ ) { + tests.add(new Object[]{originalReadLength, nToClip}); + } + + return tests.toArray(new Object[][]{}); + } + + @Test(dataProvider = "ClippedReadLengthData", enabled = true) + public void testHardClipReadLengthIsRight(final int originalReadLength, final int nToClip) { + GATKSAMRecord read = ReadClipperTestUtils.makeReadFromCigar(originalReadLength + "M"); + read.getReadLength(); // provoke the caching of the read length + final int expectedReadLength = originalReadLength - nToClip; + GATKSAMRecord clipped = ReadClipper.hardClipByReadCoordinates(read, 0, nToClip - 1); + Assert.assertEquals(clipped.getReadLength(), expectedReadLength, + String.format("Clipped read length %d with cigar %s not equal to the expected read length %d after clipping %d bases from the left from a %d bp read with cigar %s", + clipped.getReadLength(), clipped.getCigar(), expectedReadLength, nToClip, read.getReadLength(), read.getCigar())); + } + @Test(enabled = true) public void testHardClipByReferenceCoordinates() { for (Cigar cigar : cigarList) { From 8b9c6aae3efd4676bd06f90577c6c81cd06bfcfd Mon Sep 17 00:00:00 2001 From: Eric Banks Date: Wed, 8 May 2013 23:23:51 -0400 Subject: [PATCH 06/10] Fix bug in Reduce Reads that arises in multi-sample mode. * bitset could legitimately be in an unfinished state but we were trying to access it without finalizing. * added --cancer_mode argument per Mark's suggestion to force the user to explicitly enable multi-sample mode. * tests were easiest to implement as integration tests (this was a really complicated case). --- .../compression/reducereads/ReduceReads.java | 13 ++++++++++ .../reducereads/SlidingWindow.java | 4 ++++ .../ReduceReadsIntegrationTest.java | 24 +++++++++++++++++-- 3 files changed, 39 insertions(+), 2 deletions(-) diff --git a/protected/java/src/org/broadinstitute/sting/gatk/walkers/compression/reducereads/ReduceReads.java b/protected/java/src/org/broadinstitute/sting/gatk/walkers/compression/reducereads/ReduceReads.java index 71910e566..eb55701ae 100644 --- a/protected/java/src/org/broadinstitute/sting/gatk/walkers/compression/reducereads/ReduceReads.java +++ b/protected/java/src/org/broadinstitute/sting/gatk/walkers/compression/reducereads/ReduceReads.java @@ -64,6 +64,7 @@ import org.broadinstitute.sting.gatk.io.StingSAMFileWriter; import org.broadinstitute.sting.gatk.refdata.RefMetaDataTracker; import org.broadinstitute.sting.gatk.walkers.*; import org.broadinstitute.sting.utils.GenomeLoc; +import org.broadinstitute.sting.utils.SampleUtils; import org.broadinstitute.sting.utils.Utils; import org.broadinstitute.sting.utils.clipping.ReadClipper; import org.broadinstitute.sting.utils.exceptions.ReviewedStingException; @@ -236,6 +237,15 @@ public class ReduceReads extends ReadWalker, Redu @Argument(fullName = "downsample_coverage", shortName = "ds", doc = "", required = false) public int downsampleCoverage = 250; + /** + * Generally, this tool is not meant to be run for more than 1 sample at a time. The one valid exception + * brought to our attention by colleagues is the specific case of tumor/normal pairs in cancer analysis. + * To prevent users from unintentionally running the tool in a less than ideal manner, we require them + * to explicitly enable multi-sample analysis with this argument. + */ + @Argument(fullName = "cancer_mode", shortName = "cancer_mode", doc = "enable multi-samples reduction for cancer analysis", required = false) + public boolean ALLOW_MULTIPLE_SAMPLES = false; + @Hidden @Argument(fullName = "nwayout", shortName = "nw", doc = "", required = false) public boolean nwayout = false; @@ -294,6 +304,9 @@ public class ReduceReads extends ReadWalker, Redu if ( minAltProportionToTriggerVariant < 0.0 || minAltProportionToTriggerVariant > 1.0 ) throw new UserException.BadArgumentValue("--minimum_alt_proportion_to_trigger_variant", "must be a value between 0 and 1 (inclusive)"); + if ( SampleUtils.getSAMFileSamples(getToolkit().getSAMFileHeader()).size() > 1 && !ALLOW_MULTIPLE_SAMPLES ) + throw new UserException.BadInput("Reduce Reads is not meant to be run for more than 1 sample at a time except for the specific case of tumor/normal pairs in cancer analysis"); + if ( known.isEmpty() ) knownSnpPositions = null; else diff --git a/protected/java/src/org/broadinstitute/sting/gatk/walkers/compression/reducereads/SlidingWindow.java b/protected/java/src/org/broadinstitute/sting/gatk/walkers/compression/reducereads/SlidingWindow.java index d3ca037be..8843d6270 100644 --- a/protected/java/src/org/broadinstitute/sting/gatk/walkers/compression/reducereads/SlidingWindow.java +++ b/protected/java/src/org/broadinstitute/sting/gatk/walkers/compression/reducereads/SlidingWindow.java @@ -877,6 +877,10 @@ public class SlidingWindow { final int start = region.getStart() - windowHeaderStart; int stop = region.getStop() - windowHeaderStart; + // make sure the bitset is complete given the region (it might not be in multi-sample mode) + if ( region.getStop() > markedSites.getStartLocation() + markedSites.getVariantSiteBitSet().length ) + markSites(region.getStop()); + CloseVariantRegionResult closeVariantRegionResult = closeVariantRegion(start, stop, knownSnpPositions); allReads.addAll(closeVariantRegionResult.reads); diff --git a/protected/java/test/org/broadinstitute/sting/gatk/walkers/compression/reducereads/ReduceReadsIntegrationTest.java b/protected/java/test/org/broadinstitute/sting/gatk/walkers/compression/reducereads/ReduceReadsIntegrationTest.java index 15b54dbd1..405e616f1 100644 --- a/protected/java/test/org/broadinstitute/sting/gatk/walkers/compression/reducereads/ReduceReadsIntegrationTest.java +++ b/protected/java/test/org/broadinstitute/sting/gatk/walkers/compression/reducereads/ReduceReadsIntegrationTest.java @@ -53,6 +53,7 @@ import org.testng.annotations.Test; import java.io.File; import java.util.Arrays; +import java.util.Collections; import java.util.List; public class ReduceReadsIntegrationTest extends WalkerTest { @@ -221,13 +222,13 @@ public class ReduceReadsIntegrationTest extends WalkerTest { @Test(enabled = true) public void testCoReduction() { - String base = String.format("-T ReduceReads %s -npt -R %s -I %s -I %s", COREDUCTION_L, REF, COREDUCTION_BAM_A, COREDUCTION_BAM_B) + " -o %s "; + String base = String.format("-T ReduceReads %s --cancer_mode -npt -R %s -I %s -I %s", COREDUCTION_L, REF, COREDUCTION_BAM_A, COREDUCTION_BAM_B) + " -o %s "; executeTest("testCoReduction", new WalkerTestSpec(base, Arrays.asList("bam"), Arrays.asList("5f4d2c1d9c010dfd6865aeba7d0336fe")), COREDUCTION_QUALS_TEST_MD5); } @Test(enabled = true) public void testCoReductionWithKnowns() { - String base = String.format("-T ReduceReads %s -npt -R %s -I %s -I %s -known %s", COREDUCTION_L, REF, COREDUCTION_BAM_A, COREDUCTION_BAM_B, DBSNP) + " -o %s "; + String base = String.format("-T ReduceReads %s --cancer_mode -npt -R %s -I %s -I %s -known %s", COREDUCTION_L, REF, COREDUCTION_BAM_A, COREDUCTION_BAM_B, DBSNP) + " -o %s "; executeTest("testCoReductionWithKnowns", new WalkerTestSpec(base, Arrays.asList("bam"), Arrays.asList("ca48dd972bf57595c691972c0f887cb4")), COREDUCTION_QUALS_TEST_MD5); } @@ -281,5 +282,24 @@ public class ReduceReadsIntegrationTest extends WalkerTest { " -o %s --downsample_coverage 250 -dcov 50 "; executeTest("testPairedReadsInVariantRegion", new WalkerTestSpec(base, Arrays.asList("bam"), Arrays.asList("7e7b358443827ca239db3b98f299aec6")), "2af063d1bd3c322b03405dbb3ecf59a9"); } + + /** + * Confirm that this bam does not fail when multi-sample mode is enabled. The provided example is tricky and used to cause + * us to exception out in the code. + */ + @Test(enabled = true) + public void testMultiSampleDoesNotFailWithFlag() { + String cmd = "-T ReduceReads --cancer_mode -npt -R " + b37KGReference + " -I " + privateTestDir + "rr_multisample.bam -o /dev/null"; + executeTestWithoutAdditionalRRTests("testMultiSampleDoesNotFailWithFlag", new WalkerTestSpec(cmd, 0, Collections.emptyList())); + } + + /** + * Confirm that this bam fails when multi-sample mode is not enabled + */ + @Test(enabled = true) + public void testMultiSampleFailsWithoutFlag() { + String cmd = "-T ReduceReads -npt -R " + b37KGReference + " -I " + privateTestDir + "rr_multisample.bam -o /dev/null"; + executeTestWithoutAdditionalRRTests("testMultiSampleDoesNotFailWithFlag", new WalkerTestSpec(cmd, 0, UserException.BadInput.class)); + } } From 639030bd6d6e89ebf796e4e05853f7265cf96789 Mon Sep 17 00:00:00 2001 From: David Roazen Date: Fri, 10 May 2013 17:26:07 -0400 Subject: [PATCH 07/10] Enable convenient display of diff engine output in Bamboo, plus misc. minor test-related improvements -Diff engine output is now included in the actual exception message thrown as a result of an MD5 mismatch, which allows it to be conveniently viewed on the main page of a build in Bamboo. Minor Additional Improvements: -WalkerTestSpec now auto-detects test class name via new JVMUtils.getCallingClass() method, and the test class name is now included as a regular part of integration test output for each test. -Fix race condition in MD5DB.ensureMd5DbDirectory() -integrationtests dir is now cleaned by "ant clean" GSA-915 #resolve --- build.xml | 3 + .../sting/utils/classloader/JVMUtils.java | 58 +++++++ .../test/org/broadinstitute/sting/MD5DB.java | 151 ++++++++++-------- .../org/broadinstitute/sting/MD5Mismatch.java | 19 ++- .../org/broadinstitute/sting/WalkerTest.java | 64 +++++--- .../utils/classloader/JVMUtilsUnitTest.java | 75 +++++++++ .../sting/queue/pipeline/PipelineTest.scala | 2 +- 7 files changed, 270 insertions(+), 102 deletions(-) create mode 100644 public/java/test/org/broadinstitute/sting/utils/classloader/JVMUtilsUnitTest.java diff --git a/build.xml b/build.xml index 56bf4f0cd..2e9df4d5e 100644 --- a/build.xml +++ b/build.xml @@ -1031,6 +1031,7 @@ + @@ -1043,6 +1044,7 @@ + @@ -1078,6 +1080,7 @@ + diff --git a/public/java/src/org/broadinstitute/sting/utils/classloader/JVMUtils.java b/public/java/src/org/broadinstitute/sting/utils/classloader/JVMUtils.java index 2f5115dfa..8f4958f6c 100644 --- a/public/java/src/org/broadinstitute/sting/utils/classloader/JVMUtils.java +++ b/public/java/src/org/broadinstitute/sting/utils/classloader/JVMUtils.java @@ -248,4 +248,62 @@ public class JVMUtils { interfaces.add(interfaceClass.getSimpleName()); return Utils.join(", ", interfaces); } + + /** + * Returns the Class that invoked the specified "callee" class by examining the runtime stack. + * The calling class is defined as the first class below the callee class on the stack. + * + * For example, given callee == MyClass and the following runtime stack: + * + * JVMUtils.getCallingClass(MyClass) <-- top + * MyClass.foo() + * MyClass.bar() + * OtherClass.foo() + * OtherClass.bar() + * etc. + * + * this method would return OtherClass, since its methods invoked the methods in MyClass. + * + * Considers only the occurrence of the callee class on the stack that is closest to the top + * (even if there are multiple, non-contiguous occurrences). + * + * @param callee Class object for the class whose calling class we want to locate + * @return Class object for the class that invoked the callee class, or null if + * no calling class was found + * @throws IllegalArgumentException if the callee class is not found on the runtime stack + * @throws IllegalStateException if we get an error while trying to load the Class object for the calling + * class reported on the runtime stack + */ + public static Class getCallingClass( final Class callee ) { + final StackTraceElement[] stackTrace = new Throwable().getStackTrace(); + final String calleeClassName = callee.getName(); + + // Start examining the stack at the second-from-the-top position, to remove + // this method call (ie., the call to getCallingClass() itself) from consideration. + int stackTraceIndex = 1; + + // Find the first occurrence of the callee on the runtime stack. Need to use String comparison + // unfortunately, due to limitations of the StackTraceElement class. + while ( stackTraceIndex < stackTrace.length && ! stackTrace[stackTraceIndex].getClassName().equals(calleeClassName) ) { + stackTraceIndex++; + } + + // Make sure we actually found the callee class on the stack + if ( stackTraceIndex == stackTrace.length ) { + throw new IllegalArgumentException(String.format("Specified callee %s is not present on the call stack", callee.getSimpleName())); + } + + // Now find the caller class, which will be the class below the callee on the stack + while ( stackTraceIndex < stackTrace.length && stackTrace[stackTraceIndex].getClassName().equals(calleeClassName) ) { + stackTraceIndex++; + } + + try { + return stackTraceIndex < stackTrace.length ? Class.forName(stackTrace[stackTraceIndex].getClassName()) : null; + } + catch ( ClassNotFoundException e ) { + throw new IllegalStateException(String.format("Could not find caller class %s from the runtime stack in the classpath", + stackTrace[stackTraceIndex].getClassName())); + } + } } diff --git a/public/java/test/org/broadinstitute/sting/MD5DB.java b/public/java/test/org/broadinstitute/sting/MD5DB.java index 2b0d52a11..7bd6f7bc4 100644 --- a/public/java/test/org/broadinstitute/sting/MD5DB.java +++ b/public/java/test/org/broadinstitute/sting/MD5DB.java @@ -97,7 +97,12 @@ public class MD5DB { if ( ! dir.exists() ) { System.out.printf("##### Creating MD5 db %s%n", LOCAL_MD5_DB_DIR); if ( ! dir.mkdir() ) { - throw new ReviewedStingException("Infrastructure failure: failed to create md5 directory " + LOCAL_MD5_DB_DIR); + // Need to check AGAIN whether the dir exists, because we might be doing multi-process parallelism + // within the same working directory, and another GATK instance may have come along and created the + // directory between the calls to exists() and mkdir() above. + if ( ! dir.exists() ) { + throw new ReviewedStingException("Infrastructure failure: failed to create md5 directory " + LOCAL_MD5_DB_DIR); + } } } } @@ -203,98 +208,106 @@ public class MD5DB { } public static class MD5Match { - final String actualMD5, expectedMD5; - final String failMessage; - boolean failed; + public final String actualMD5, expectedMD5; + public final String failMessage; + public final String diffEngineOutput; + public final boolean failed; - public MD5Match(final String actualMD5, final String expectedMD5, final String failMessage, final boolean failed) { + public MD5Match(final String actualMD5, final String expectedMD5, final String failMessage, final String diffEngineOutput, final boolean failed) { this.actualMD5 = actualMD5; this.expectedMD5 = expectedMD5; this.failMessage = failMessage; + this.diffEngineOutput = diffEngineOutput; this.failed = failed; } } /** - * Tests a file MD5 against an expected value, returning the MD5. NOTE: This function WILL throw an exception if the MD5s are different. - * @param name Name of the test. + * Tests a file MD5 against an expected value, returning an MD5Match object containing a description of the + * match or mismatch. In case of a mismatch, outputs a description of the mismatch to various log files/streams. + * + * NOTE: This function WILL NOT throw an exception if the MD5s are different. + * + * @param testName Name of the test. + * @param testClassName Name of the class that contains the test. * @param resultsFile File to MD5. * @param expectedMD5 Expected MD5 value. * @param parameterize If true or if expectedMD5 is an empty string, will print out the calculated MD5 instead of error text. - * @return The calculated MD5. + * @return an MD5Match object containing a description of the match/mismatch. Will have its "failed" field set + * to true if there was a mismatch (unless we're using the "parameterize" argument) */ - public MD5Match assertMatchingMD5(final String name, final File resultsFile, final String expectedMD5, final boolean parameterize) { - final String actualMD5 = testFileMD5(name, resultsFile, expectedMD5, parameterize); - String failMessage = null; + public MD5Match testFileMD5(final String testName, final String testClassName, final File resultsFile, final String expectedMD5, final boolean parameterize) { + final String actualMD5 = calculateFileMD5(resultsFile); + String diffEngineOutput = ""; + String failMessage = ""; boolean failed = false; + // copy md5 to integrationtests + updateMD5Db(actualMD5, resultsFile); + if (parameterize || expectedMD5.equals("")) { - // Don't assert - } else if ( actualMD5.equals(expectedMD5) ) { - //BaseTest.log(String.format(" => %s PASSED (expected=%s)", name, expectedMD5)); - } else { + BaseTest.log(String.format("PARAMETERIZATION: file %s has md5 = %s", resultsFile, actualMD5)); + } else if ( ! expectedMD5.equals(actualMD5) ) { failed = true; - failMessage = String.format("%s has mismatching MD5s: expected=%s observed=%s", name, expectedMD5, actualMD5); + failMessage = String.format("%s:%s has mismatching MD5s: expected=%s observed=%s", testClassName, testName, expectedMD5, actualMD5); + diffEngineOutput = logMD5MismatchAndGetDiffEngineOutput(testName, testClassName, expectedMD5, actualMD5); } - return new MD5Match(actualMD5, expectedMD5, failMessage, failed); + return new MD5Match(actualMD5, expectedMD5, failMessage, diffEngineOutput, failed); } - /** - * Tests a file MD5 against an expected value, returning the MD5. NOTE: This function WILL NOT throw an exception if the MD5s are different. - * @param name Name of the test. - * @param resultsFile File to MD5. - * @param expectedMD5 Expected MD5 value. - * @param parameterize If true or if expectedMD5 is an empty string, will print out the calculated MD5 instead of error text. - * @return The calculated MD5. + * Calculates the MD5 for the specified file and returns it as a String + * + * @param file file whose MD5 to calculate + * @return file's MD5 in String form + * @throws RuntimeException if the file could not be read */ - public String testFileMD5(final String name, final File resultsFile, final String expectedMD5, final boolean parameterize) { + public String calculateFileMD5( final File file ) { try { - final String filemd5sum = Utils.calcMD5(getBytesFromFile(resultsFile)); - - // - // copy md5 to integrationtests - // - updateMD5Db(filemd5sum, resultsFile); - - if (parameterize || expectedMD5.equals("")) { - BaseTest.log(String.format("PARAMETERIZATION: file %s has md5 = %s", resultsFile, filemd5sum)); - } else { - //System.out.println(String.format("Checking MD5 for %s [calculated=%s, expected=%s]", resultsFile, filemd5sum, expectedMD5)); - //System.out.flush(); - - if ( ! expectedMD5.equals(filemd5sum) ) { - // we are going to fail for real in assertEquals (so we are counted by the testing framework). - // prepare ourselves for the comparison - System.out.printf("##### Test %s is going to fail #####%n", name); - String pathToExpectedMD5File = getMD5FilePath(expectedMD5, "[No DB file found]"); - String pathToFileMD5File = getMD5FilePath(filemd5sum, "[No DB file found]"); - BaseTest.log(String.format("expected %s", expectedMD5)); - BaseTest.log(String.format("calculated %s", filemd5sum)); - BaseTest.log(String.format("diff %s %s", pathToExpectedMD5File, pathToFileMD5File)); - - md5MismatchStream.printf("%s\t%s\t%s%n", expectedMD5, filemd5sum, name); - md5MismatchStream.flush(); - - // inline differences - final ByteArrayOutputStream baos = new ByteArrayOutputStream(); - final PrintStream ps = new PrintStream(baos); - DiffEngine.SummaryReportParams params = new DiffEngine.SummaryReportParams(ps, 20, 10, 0, MAX_RAW_DIFFS_TO_SUMMARIZE, false); - boolean success = DiffEngine.simpleDiffFiles(new File(pathToExpectedMD5File), new File(pathToFileMD5File), MAX_RECORDS_TO_READ, params); - if ( success ) { - final String content = baos.toString(); - BaseTest.log(content); - System.out.printf("Note that the above list is not comprehensive. At most 20 lines of output, and 10 specific differences will be listed. Please use -T DiffObjects -R public/testdata/exampleFASTA.fasta -m %s -t %s to explore the differences more freely%n", - pathToExpectedMD5File, pathToFileMD5File); - } - ps.close(); - } - } - - return filemd5sum; - } catch (Exception e) { - throw new RuntimeException("Failed to read bytes from calls file: " + resultsFile, e); + return Utils.calcMD5(getBytesFromFile(file)); + } + catch ( Exception e ) { + throw new RuntimeException("Failed to read bytes from file: " + file + " for MD5 calculation", e); } } + + /** + * Logs a description (including diff engine output) of the MD5 mismatch between the expectedMD5 + * and actualMD5 to a combination of BaseTest.log(), the md5MismatchStream, and stdout, then returns + * the diff engine output. + * + * @param testName name of the test that generated the mismatch + * @param testClassName name of the class containing the test that generated the mismatch + * @param expectedMD5 the MD5 we were expecting from this test + * @param actualMD5 the MD5 we actually calculated from the test output + * @return the diff engine output produced while logging the description of the mismatch + */ + private String logMD5MismatchAndGetDiffEngineOutput(final String testName, final String testClassName, final String expectedMD5, final String actualMD5) { + System.out.printf("##### Test %s:%s is going to fail #####%n", testClassName, testName); + String pathToExpectedMD5File = getMD5FilePath(expectedMD5, "[No DB file found]"); + String pathToFileMD5File = getMD5FilePath(actualMD5, "[No DB file found]"); + BaseTest.log(String.format("expected %s", expectedMD5)); + BaseTest.log(String.format("calculated %s", actualMD5)); + BaseTest.log(String.format("diff %s %s", pathToExpectedMD5File, pathToFileMD5File)); + + md5MismatchStream.printf("%s\t%s\t%s%n", expectedMD5, actualMD5, testName); + md5MismatchStream.flush(); + + // inline differences + String diffEngineOutput = ""; + final ByteArrayOutputStream baos = new ByteArrayOutputStream(); + final PrintStream ps = new PrintStream(baos); + DiffEngine.SummaryReportParams params = new DiffEngine.SummaryReportParams(ps, 20, 10, 0, MAX_RAW_DIFFS_TO_SUMMARIZE, false); + boolean success = DiffEngine.simpleDiffFiles(new File(pathToExpectedMD5File), new File(pathToFileMD5File), MAX_RECORDS_TO_READ, params); + if ( success ) { + diffEngineOutput = baos.toString(); + BaseTest.log(diffEngineOutput); + System.out.printf("Note that the above list is not comprehensive. At most 20 lines of output, and 10 specific differences will be listed. Please use -T DiffObjects -R public/testdata/exampleFASTA.fasta -m %s -t %s to explore the differences more freely%n", + pathToExpectedMD5File, pathToFileMD5File); + } + ps.close(); + + return diffEngineOutput; + } } diff --git a/public/java/test/org/broadinstitute/sting/MD5Mismatch.java b/public/java/test/org/broadinstitute/sting/MD5Mismatch.java index e459a24ce..56acedaf0 100644 --- a/public/java/test/org/broadinstitute/sting/MD5Mismatch.java +++ b/public/java/test/org/broadinstitute/sting/MD5Mismatch.java @@ -35,29 +35,32 @@ import java.util.List; * @since Date created */ public class MD5Mismatch extends Exception { - final List actuals, expecteds; + final List actuals, expecteds, diffEngineOutputs; - public MD5Mismatch(final String actual, final String expected) { - this(Collections.singletonList(actual), Collections.singletonList(expected)); + public MD5Mismatch(final String actual, final String expected, final String diffEngineOutput) { + this(Collections.singletonList(actual), Collections.singletonList(expected), Collections.singletonList(diffEngineOutput)); } - public MD5Mismatch(final List actuals, final List expecteds) { - super(formatMessage(actuals, expecteds)); + public MD5Mismatch(final List actuals, final List expecteds, final List diffEngineOutputs) { + super(formatMessage(actuals, expecteds, diffEngineOutputs)); this.actuals = actuals; this.expecteds = expecteds; + this.diffEngineOutputs = diffEngineOutputs; } @Override public String toString() { - return formatMessage(actuals, expecteds); + return formatMessage(actuals, expecteds, diffEngineOutputs); } - private final static String formatMessage(final List actuals, final List expecteds) { + private static String formatMessage(final List actuals, final List expecteds, final List diffEngineOutputs) { final StringBuilder b = new StringBuilder("MD5 mismatch: "); for ( int i = 0; i < actuals.size(); i++ ) { - if ( i > 1 ) b.append("\t\t\n"); + if ( i >= 1 ) b.append("\t\t\n\n"); b.append("actual ").append(actuals.get(i)); b.append(" expected ").append(expecteds.get(i)); + b.append("\nDiff Engine Output:\n"); + b.append(diffEngineOutputs.get(i)); } return b.toString(); } diff --git a/public/java/test/org/broadinstitute/sting/WalkerTest.java b/public/java/test/org/broadinstitute/sting/WalkerTest.java index dd5a2b0a7..40f1f7bcd 100644 --- a/public/java/test/org/broadinstitute/sting/WalkerTest.java +++ b/public/java/test/org/broadinstitute/sting/WalkerTest.java @@ -34,6 +34,7 @@ import org.broadinstitute.sting.gatk.CommandLineGATK; import org.broadinstitute.sting.gatk.GenomeAnalysisEngine; import org.broadinstitute.sting.gatk.phonehome.GATKRunReport; import org.broadinstitute.sting.utils.Utils; +import org.broadinstitute.sting.utils.classloader.JVMUtils; import org.broadinstitute.variant.bcf2.BCF2Utils; import org.broadinstitute.sting.utils.collections.Pair; import org.broadinstitute.variant.vcf.VCFCodec; @@ -73,10 +74,6 @@ public class WalkerTest extends BaseTest { return md5DB; } - public MD5DB.MD5Match assertMatchingMD5(final String name, final File resultsFile, final String expectedMD5) { - return getMd5DB().assertMatchingMD5(name, resultsFile, expectedMD5, parameterize()); - } - public void validateOutputBCFIfPossible(final String name, final File resultFile) { final File bcfFile = BCF2Utils.shadowBCF(resultFile); if ( bcfFile != null && bcfFile.exists() ) { @@ -114,15 +111,15 @@ public class WalkerTest extends BaseTest { } } - public List assertMatchingMD5s(final String name, List resultFiles, List expectedMD5s) { + public List assertMatchingMD5s(final String testName, final String testClassName, List resultFiles, List expectedMD5s) { List md5s = new ArrayList(); List fails = new ArrayList(); for (int i = 0; i < resultFiles.size(); i++) { - MD5DB.MD5Match result = assertMatchingMD5(name, resultFiles.get(i), expectedMD5s.get(i)); - validateOutputBCFIfPossible(name, resultFiles.get(i)); + MD5DB.MD5Match result = getMd5DB().testFileMD5(testName, testClassName, resultFiles.get(i), expectedMD5s.get(i), parameterize()); + validateOutputBCFIfPossible(testName, resultFiles.get(i)); if ( ! result.failed ) { - validateOutputIndex(name, resultFiles.get(i)); + validateOutputIndex(testName, resultFiles.get(i)); md5s.add(result.expectedMD5); } else { fails.add(result); @@ -132,14 +129,17 @@ public class WalkerTest extends BaseTest { if ( ! fails.isEmpty() ) { List actuals = new ArrayList(); List expecteds = new ArrayList(); + List diffEngineOutputs = new ArrayList(); + for ( final MD5DB.MD5Match fail : fails ) { actuals.add(fail.actualMD5); expecteds.add(fail.expectedMD5); + diffEngineOutputs.add(fail.diffEngineOutput); logger.warn("Fail: " + fail.failMessage); } - final MD5Mismatch failure = new MD5Mismatch(actuals, expecteds); - Assert.fail(failure.toString(), failure); + final MD5Mismatch failure = new MD5Mismatch(actuals, expecteds, diffEngineOutputs); + Assert.fail(failure.toString()); } return md5s; @@ -170,6 +170,9 @@ public class WalkerTest extends BaseTest { boolean includeImplicitArgs = true; boolean includeShadowBCF = true; + // Name of the test class that created this test case + private Class testClass; + // the default output path for the integration test private File outputFileLocation = null; @@ -183,6 +186,7 @@ public class WalkerTest extends BaseTest { this.args = args; this.nOutputFiles = md5s.size(); this.md5s = md5s; + this.testClass = getCallingTestClass(); } public WalkerTestSpec(String args, List exts, List md5s) { @@ -194,12 +198,22 @@ public class WalkerTest extends BaseTest { this.nOutputFiles = md5s.size(); this.md5s = md5s; this.exts = exts; + this.testClass = getCallingTestClass(); } public WalkerTestSpec(String args, int nOutputFiles, Class expectedException) { this.args = args; this.nOutputFiles = nOutputFiles; this.expectedException = expectedException; + this.testClass = getCallingTestClass(); + } + + private Class getCallingTestClass() { + return JVMUtils.getCallingClass(getClass()); + } + + public String getTestClassName() { + return testClass.getSimpleName(); } public String getArgsWithImplicitArgs() { @@ -306,7 +320,7 @@ public class WalkerTest extends BaseTest { if ( spec.expectsException() ) { // this branch handles the case were we are testing that a walker will fail as expected - return executeTest(name, spec.getOutputFileLocation(), null, tmpFiles, args, spec.getExpectedException()); + return executeTest(name, spec.getTestClassName(), spec.getOutputFileLocation(), null, tmpFiles, args, spec.getExpectedException()); } else { List md5s = new LinkedList(); md5s.addAll(spec.md5s); @@ -316,7 +330,7 @@ public class WalkerTest extends BaseTest { md5s.add(md5); tmpFiles.add(spec.auxillaryFiles.get(md5)); } - return executeTest(name, spec.getOutputFileLocation(), md5s, tmpFiles, args, null); + return executeTest(name, spec.getTestClassName(), spec.getOutputFileLocation(), md5s, tmpFiles, args, null); } } @@ -337,35 +351,37 @@ public class WalkerTest extends BaseTest { /** * execute the test, given the following: - * @param name the name of the test + * @param testName the name of the test + * @param testClassName the name of the class that contains the test * @param md5s the list of md5s * @param tmpFiles the temp file corresponding to the md5 list * @param args the argument list * @param expectedException the expected exception or null * @return a pair of file and string lists */ - private Pair, List> executeTest(String name, File outputFileLocation, List md5s, List tmpFiles, String args, Class expectedException) { - if ( md5s != null ) qcMD5s(name, md5s); + private Pair, List> executeTest(String testName, String testClassName, File outputFileLocation, List md5s, List tmpFiles, String args, Class expectedException) { + if ( md5s != null ) qcMD5s(testName, md5s); if (outputFileLocation != null) args += " -o " + outputFileLocation.getAbsolutePath(); - executeTest(name, args, expectedException); + executeTest(testName, testClassName, args, expectedException); if ( expectedException != null ) { return null; } else { // we need to check MD5s - return new Pair, List>(tmpFiles, assertMatchingMD5s(name, tmpFiles, md5s)); + return new Pair, List>(tmpFiles, assertMatchingMD5s(testName, testClassName, tmpFiles, md5s)); } } /** * execute the test, given the following: - * @param name the name of the test - * @param args the argument list + * @param testName the name of the test + * @param testClassName the name of the class that contains the test + * @param args the argument list * @param expectedException the expected exception or null */ - private void executeTest(String name, String args, Class expectedException) { + private void executeTest(String testName, String testClassName, String args, Class expectedException) { CommandLineGATK instance = new CommandLineGATK(); String[] command = Utils.escapeExpressions(args); @@ -374,7 +390,7 @@ public class WalkerTest extends BaseTest { try { final String now = new SimpleDateFormat("HH:mm:ss").format(new Date()); final String cmdline = Utils.join(" ",command); - System.out.println(String.format("[%s] Executing test %s with GATK arguments: %s", now, name, cmdline)); + System.out.println(String.format("[%s] Executing test %s:%s with GATK arguments: %s", now, testClassName, testName, cmdline)); // also write the command line to the HTML log for convenient follow-up // do the replaceAll so paths become relative to the current BaseTest.log(cmdline.replaceAll(publicTestDirRoot, "").replaceAll(privateTestDirRoot, "")); @@ -388,8 +404,8 @@ public class WalkerTest extends BaseTest { // it's the type we expected //System.out.println(String.format(" => %s PASSED", name)); } else { - final String message = String.format("Test %s expected exception %s but instead got %s with error message %s", - name, expectedException, e.getClass(), e.getMessage()); + final String message = String.format("Test %s:%s expected exception %s but instead got %s with error message %s", + testClassName, testName, expectedException, e.getClass(), e.getMessage()); if ( e.getCause() != null ) { final ByteArrayOutputStream baos = new ByteArrayOutputStream(); final PrintStream ps = new PrintStream(baos); @@ -409,7 +425,7 @@ public class WalkerTest extends BaseTest { if ( expectedException != null ) { if ( ! gotAnException ) // we expected an exception but didn't see it - Assert.fail(String.format("Test %s expected exception %s but none was thrown", name, expectedException.toString())); + Assert.fail(String.format("Test %s:%s expected exception %s but none was thrown", testClassName, testName, expectedException.toString())); } else { if ( CommandLineExecutable.result != 0) { throw new RuntimeException("Error running the GATK with arguments: " + args); diff --git a/public/java/test/org/broadinstitute/sting/utils/classloader/JVMUtilsUnitTest.java b/public/java/test/org/broadinstitute/sting/utils/classloader/JVMUtilsUnitTest.java new file mode 100644 index 000000000..6ffd47f37 --- /dev/null +++ b/public/java/test/org/broadinstitute/sting/utils/classloader/JVMUtilsUnitTest.java @@ -0,0 +1,75 @@ +/* +* Copyright (c) 2012 The Broad Institute +* +* Permission is hereby granted, free of charge, to any person +* obtaining a copy of this software and associated documentation +* files (the "Software"), to deal in the Software without +* restriction, including without limitation the rights to use, +* copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the +* Software is furnished to do so, subject to the following +* conditions: +* +* The above copyright notice and this permission notice shall be +* included in all copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR +* THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +package org.broadinstitute.sting.utils.classloader; + +import org.testng.Assert; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + +public class JVMUtilsUnitTest { + + // Test classes used by the tests for JVMUtils.getCallingClass(): + private static class DummyTestClass1 { + public static Class getCaller( final Class callee ) { + return DummyTestClass2.getCaller(callee); + } + } + + private static class DummyTestClass2 { + public static Class getCaller( final Class callee ) { + return DummyTestClass3.getCaller(callee); + } + } + + private static class DummyTestClass3 { + public static Class getCaller( final Class callee ) { + return JVMUtils.getCallingClass(callee); + } + } + + @DataProvider( name = "TestGetCallingClassDataProvider" ) + public Object[][] getTestCallingClassTestData() { + return new Object[][] { + { DummyTestClass1.class, JVMUtilsUnitTest.class }, + { DummyTestClass2.class, DummyTestClass1.class }, + { DummyTestClass3.class, DummyTestClass2.class } + }; + } + + @Test( dataProvider = "TestGetCallingClassDataProvider" ) + public void testGetCallingClass( final Class callee, final Class expectedCaller ) { + final Class reportedCaller = DummyTestClass1.getCaller(callee); + + Assert.assertEquals(reportedCaller, expectedCaller, + String.format("Wrong calling class returned from DummyTestClass1.getCaller(%s)", callee.getSimpleName())); + } + + @Test( expectedExceptions = IllegalArgumentException.class ) + public void testGetCallingClassCalleeNotFound() { + // Trying to get the calling class of a class not on the runtime stack should produce an exception. + JVMUtils.getCallingClass(DummyTestClass1.class); + } +} diff --git a/public/scala/test/org/broadinstitute/sting/queue/pipeline/PipelineTest.scala b/public/scala/test/org/broadinstitute/sting/queue/pipeline/PipelineTest.scala index 03b38ffe9..6741e4107 100644 --- a/public/scala/test/org/broadinstitute/sting/queue/pipeline/PipelineTest.scala +++ b/public/scala/test/org/broadinstitute/sting/queue/pipeline/PipelineTest.scala @@ -113,7 +113,7 @@ object PipelineTest extends BaseTest with Logging { private def assertMatchingMD5s(name: String, fileMD5s: Traversable[(File, String)], parameterize: Boolean) { var failed = 0 for ((file, expectedMD5) <- fileMD5s) { - val calculatedMD5 = md5DB.testFileMD5(name, file, expectedMD5, parameterize) + val calculatedMD5 = md5DB.testFileMD5(name, "", file, expectedMD5, parameterize).actualMD5 if (!parameterize && expectedMD5 != "" && expectedMD5 != calculatedMD5) failed += 1 } From 2f5ef6db442859d9ac09ef6de1f9b7819f27098e Mon Sep 17 00:00:00 2001 From: Eric Banks Date: Wed, 1 May 2013 11:13:58 -0400 Subject: [PATCH 08/10] New faster Smith-Waterman implementation that is edge greedy and assumes that ref and haplotype have same global start/end points. * This version inherits from the original SW implementation so it can use the same matrix creation method. * A bunch of refactoring was done to the original version to clean it up a bit and to have it do the right thing for indels at the edges of the alignments. * Enum added for the overhang strategy to use; added implementation for the INDEL version of this strategy. * Lots of systematic testing added for this implementation. * NOT HOOKED UP TO HAPLOTYPE CALLER YET. Committing so that people can play around with this for now. --- ...EdgeGreedySWPairwiseAlignmentUnitTest.java | 259 ++++++++++++++++++ .../org/broadinstitute/sting/utils/Utils.java | 16 ++ .../GlobalEdgeGreedySWPairwiseAlignment.java | 217 +++++++++++++++ .../smithwaterman/SWPairwiseAlignment.java | 255 ++++++++++++----- .../utils/smithwaterman/SmithWaterman.java | 1 + .../sting/utils/UtilsUnitTest.java | 20 ++ .../smithwaterman/SmithWatermanBenchmark.java | 88 ++++++ 7 files changed, 784 insertions(+), 72 deletions(-) create mode 100644 protected/java/test/org/broadinstitute/sting/utils/smithwaterman/GlobalEdgeGreedySWPairwiseAlignmentUnitTest.java create mode 100644 public/java/src/org/broadinstitute/sting/utils/smithwaterman/GlobalEdgeGreedySWPairwiseAlignment.java create mode 100644 public/java/test/org/broadinstitute/sting/utils/smithwaterman/SmithWatermanBenchmark.java diff --git a/protected/java/test/org/broadinstitute/sting/utils/smithwaterman/GlobalEdgeGreedySWPairwiseAlignmentUnitTest.java b/protected/java/test/org/broadinstitute/sting/utils/smithwaterman/GlobalEdgeGreedySWPairwiseAlignmentUnitTest.java new file mode 100644 index 000000000..711a60436 --- /dev/null +++ b/protected/java/test/org/broadinstitute/sting/utils/smithwaterman/GlobalEdgeGreedySWPairwiseAlignmentUnitTest.java @@ -0,0 +1,259 @@ +/* +* By downloading the PROGRAM you agree to the following terms of use: +* +* BROAD INSTITUTE - SOFTWARE LICENSE AGREEMENT - FOR ACADEMIC NON-COMMERCIAL RESEARCH PURPOSES ONLY +* +* This Agreement is made between the Broad Institute, Inc. with a principal address at 7 Cambridge Center, Cambridge, MA 02142 (BROAD) and the LICENSEE and is effective at the date the downloading is completed (EFFECTIVE DATE). +* +* WHEREAS, LICENSEE desires to license the PROGRAM, as defined hereinafter, and BROAD wishes to have this PROGRAM utilized in the public interest, subject only to the royalty-free, nonexclusive, nontransferable license rights of the United States Government pursuant to 48 CFR 52.227-14; and +* WHEREAS, LICENSEE desires to license the PROGRAM and BROAD desires to grant a license on the following terms and conditions. +* NOW, THEREFORE, in consideration of the promises and covenants made herein, the parties hereto agree as follows: +* +* 1. DEFINITIONS +* 1.1 PROGRAM shall mean copyright in the object code and source code known as GATK2 and related documentation, if any, as they exist on the EFFECTIVE DATE and can be downloaded from http://www.broadinstitute/GATK on the EFFECTIVE DATE. +* +* 2. LICENSE +* 2.1 Grant. Subject to the terms of this Agreement, BROAD hereby grants to LICENSEE, solely for academic non-commercial research purposes, a non-exclusive, non-transferable license to: (a) download, execute and display the PROGRAM and (b) create bug fixes and modify the PROGRAM. +* The LICENSEE may apply the PROGRAM in a pipeline to data owned by users other than the LICENSEE and provide these users the results of the PROGRAM provided LICENSEE does so for academic non-commercial purposes only. For clarification purposes, academic sponsored research is not a commercial use under the terms of this Agreement. +* 2.2 No Sublicensing or Additional Rights. LICENSEE shall not sublicense or distribute the PROGRAM, in whole or in part, without prior written permission from BROAD. LICENSEE shall ensure that all of its users agree to the terms of this Agreement. LICENSEE further agrees that it shall not put the PROGRAM on a network, server, or other similar technology that may be accessed by anyone other than the LICENSEE and its employees and users who have agreed to the terms of this agreement. +* 2.3 License Limitations. Nothing in this Agreement shall be construed to confer any rights upon LICENSEE by implication, estoppel, or otherwise to any computer software, trademark, intellectual property, or patent rights of BROAD, or of any other entity, except as expressly granted herein. LICENSEE agrees that the PROGRAM, in whole or part, shall not be used for any commercial purpose, including without limitation, as the basis of a commercial software or hardware product or to provide services. LICENSEE further agrees that the PROGRAM shall not be copied or otherwise adapted in order to circumvent the need for obtaining a license for use of the PROGRAM. +* +* 3. OWNERSHIP OF INTELLECTUAL PROPERTY +* LICENSEE acknowledges that title to the PROGRAM shall remain with BROAD. The PROGRAM is marked with the following BROAD copyright notice and notice of attribution to contributors. LICENSEE shall retain such notice on all copies. LICENSEE agrees to include appropriate attribution if any results obtained from use of the PROGRAM are included in any publication. +* Copyright 2012 Broad Institute, Inc. +* Notice of attribution: The GATK2 program was made available through the generosity of Medical and Population Genetics program at the Broad Institute, Inc. +* LICENSEE shall not use any trademark or trade name of BROAD, or any variation, adaptation, or abbreviation, of such marks or trade names, or any names of officers, faculty, students, employees, or agents of BROAD except as states above for attribution purposes. +* +* 4. INDEMNIFICATION +* LICENSEE shall indemnify, defend, and hold harmless BROAD, and their respective officers, faculty, students, employees, associated investigators and agents, and their respective successors, heirs and assigns, (Indemnitees), against any liability, damage, loss, or expense (including reasonable attorneys fees and expenses) incurred by or imposed upon any of the Indemnitees in connection with any claims, suits, actions, demands or judgments arising out of any theory of liability (including, without limitation, actions in the form of tort, warranty, or strict liability and regardless of whether such action has any factual basis) pursuant to any right or license granted under this Agreement. +* +* 5. NO REPRESENTATIONS OR WARRANTIES +* THE PROGRAM IS DELIVERED AS IS. BROAD MAKES NO REPRESENTATIONS OR WARRANTIES OF ANY KIND CONCERNING THE PROGRAM OR THE COPYRIGHT, EXPRESS OR IMPLIED, INCLUDING, WITHOUT LIMITATION, WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NONINFRINGEMENT, OR THE ABSENCE OF LATENT OR OTHER DEFECTS, WHETHER OR NOT DISCOVERABLE. BROAD EXTENDS NO WARRANTIES OF ANY KIND AS TO PROGRAM CONFORMITY WITH WHATEVER USER MANUALS OR OTHER LITERATURE MAY BE ISSUED FROM TIME TO TIME. +* IN NO EVENT SHALL BROAD OR ITS RESPECTIVE DIRECTORS, OFFICERS, EMPLOYEES, AFFILIATED INVESTIGATORS AND AFFILIATES BE LIABLE FOR INCIDENTAL OR CONSEQUENTIAL DAMAGES OF ANY KIND, INCLUDING, WITHOUT LIMITATION, ECONOMIC DAMAGES OR INJURY TO PROPERTY AND LOST PROFITS, REGARDLESS OF WHETHER BROAD SHALL BE ADVISED, SHALL HAVE OTHER REASON TO KNOW, OR IN FACT SHALL KNOW OF THE POSSIBILITY OF THE FOREGOING. +* +* 6. ASSIGNMENT +* This Agreement is personal to LICENSEE and any rights or obligations assigned by LICENSEE without the prior written consent of BROAD shall be null and void. +* +* 7. MISCELLANEOUS +* 7.1 Export Control. LICENSEE gives assurance that it will comply with all United States export control laws and regulations controlling the export of the PROGRAM, including, without limitation, all Export Administration Regulations of the United States Department of Commerce. Among other things, these laws and regulations prohibit, or require a license for, the export of certain types of software to specified countries. +* 7.2 Termination. LICENSEE shall have the right to terminate this Agreement for any reason upon prior written notice to BROAD. If LICENSEE breaches any provision hereunder, and fails to cure such breach within thirty (30) days, BROAD may terminate this Agreement immediately. Upon termination, LICENSEE shall provide BROAD with written assurance that the original and all copies of the PROGRAM have been destroyed, except that, upon prior written authorization from BROAD, LICENSEE may retain a copy for archive purposes. +* 7.3 Survival. The following provisions shall survive the expiration or termination of this Agreement: Articles 1, 3, 4, 5 and Sections 2.2, 2.3, 7.3, and 7.4. +* 7.4 Notice. Any notices under this Agreement shall be in writing, shall specifically refer to this Agreement, and shall be sent by hand, recognized national overnight courier, confirmed facsimile transmission, confirmed electronic mail, or registered or certified mail, postage prepaid, return receipt requested. All notices under this Agreement shall be deemed effective upon receipt. +* 7.5 Amendment and Waiver; Entire Agreement. This Agreement may be amended, supplemented, or otherwise modified only by means of a written instrument signed by all parties. Any waiver of any rights or failure to act in a specific instance shall relate only to such instance and shall not be construed as an agreement to waive any rights or fail to act in any other instance, whether or not similar. This Agreement constitutes the entire agreement among the parties with respect to its subject matter and supersedes prior agreements or understandings between the parties relating to its subject matter. +* 7.6 Binding Effect; Headings. This Agreement shall be binding upon and inure to the benefit of the parties and their respective permitted successors and assigns. All headings are for convenience only and shall not affect the meaning of any provision of this Agreement. +* 7.7 Governing Law. This Agreement shall be construed, governed, interpreted and applied in accordance with the internal laws of the Commonwealth of Massachusetts, U.S.A., without regard to conflict of laws principles. +*/ + +package org.broadinstitute.sting.utils.smithwaterman; + +import net.sf.samtools.TextCigarCodec; +import org.broadinstitute.sting.BaseTest; +import org.broadinstitute.sting.utils.Utils; +import org.broadinstitute.sting.utils.sam.AlignmentUtils; +import org.testng.Assert; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +public class GlobalEdgeGreedySWPairwiseAlignmentUnitTest extends BaseTest { + + private final static boolean DEBUG = false; + + @Test(enabled = !DEBUG) + public void testReadAlignedToRefComplexAlignment() { + final String reference = "AAAGGACTGACTG"; + final String read = "ACTGACTGACTG"; + final GlobalEdgeGreedySWPairwiseAlignment sw = new GlobalEdgeGreedySWPairwiseAlignment(reference.getBytes(), read.getBytes()); + Assert.assertEquals(sw.getCigar().toString(), "1M1D11M"); + } + + @Test(enabled = !DEBUG) + public void testIndelsAtStartAndEnd() { + final String match = "CCCCC"; + final String reference = "AAA" + match; + final String read = match + "GGG"; + final int expectedStart = 0; + final String expectedCigar = "3D5M3I"; + final GlobalEdgeGreedySWPairwiseAlignment sw = new GlobalEdgeGreedySWPairwiseAlignment(reference.getBytes(), read.getBytes()); + Assert.assertEquals(sw.getAlignmentStart2wrt1(), expectedStart); + Assert.assertEquals(sw.getCigar().toString(), expectedCigar); + } + + @Test(enabled = !DEBUG) + public void testDegenerateAlignmentWithIndelsAtBothEnds() { + logger.warn("testDegenerateAlignmentWithIndelsAtBothEnds"); + final String ref = "TGTGTGTGTGTGTGACAGAGAGAGAGAGAGAGAGAGAGAGAGAGA"; + final String alt = "ACAGAGAGAGAGAGAGAGAGAGAGAGAGAGAGAGAGAGAGAGAGAGAGAGA"; + final int expectedStart = 0; + final String expectedCigar = "6I45M"; + final GlobalEdgeGreedySWPairwiseAlignment sw = new GlobalEdgeGreedySWPairwiseAlignment(ref.getBytes(), alt.getBytes(), SWParameterSet.STANDARD_NGS); + Assert.assertEquals(sw.getAlignmentStart2wrt1(), expectedStart); + Assert.assertEquals(sw.getCigar().toString(), expectedCigar); + } + + @Test(enabled = !DEBUG) + public void testAlignReallyLongDeletion() { + final String ref = "CGGCTAATTTTTGTATTTTTAGTAGAGACAGGGTTTCACCATGTTGGCCAGGCTGGTCTTGAACTCCTGACCTCAGGTGATCCACTCGCCTCGGTCTCCCAAAGTGTTGGGATTACAGGCATGAACCACTGCACCTGGCCTAGTGTTTGGGAAAACTATACTAGGAAAAGAATAGTTGCTTTAAGTCATTCTTTGATTATTCTGAGAATTGGCATATAGCTGCCATTATAACCTACTTTTGCTAAATATAATAATAATAATCATTATTTTTATTTTTTGAGACAGGGTCTTGTTTTGTCACCCCGGCTGGAGTGAAGTGGCGCAATCTCGGCTCACTGCAACCTCCACCTCCGGGTGCAAGCAATTCTCCTGCCTCAGCCTCTTGAGTAGCTAGGATTACAGGCACAAGCCATCATGCCCAGCTAATTTTTGTATTTTTAGTAGAGACAGGGTTTCACCATGTTGGTCAGGCTGGTCTTGAACTCCTGACCTCAGGT"; + final String alt = "CGGCTAATTTTTGTATTTTTAGTAGAGACAGGGTTTCACCATGTTGGTCAGGCTGGTCTTGAACTCCTGACCTCAGGT"; + + final GlobalEdgeGreedySWPairwiseAlignment sw = new GlobalEdgeGreedySWPairwiseAlignment(ref.getBytes(), alt.getBytes(), SWParameterSet.STANDARD_NGS); + Assert.assertEquals(sw.getAlignmentStart2wrt1(), 0); + Assert.assertEquals(sw.getCigar().toString(), "47M419D31M"); + } + + public static final Parameters params = new Parameters(20.0, -10.0, -26.0, -1.1); + @DataProvider(name = "SWData") + public Object[][] makeSWData() { + List tests = new ArrayList(); + + // simple cases + tests.add(new Object[]{"A", "C", "1M"}); + tests.add(new Object[]{"AAA", "AAA", "3M"}); + tests.add(new Object[]{"AAA", "AGA", "3M"}); + tests.add(new Object[]{"AAA", "GAA", "3M"}); + tests.add(new Object[]{"AAA", "AAG", "3M"}); + + // small single indels + tests.add(new Object[]{"ACACACAC", "ACACAC", "6M2D"}); + tests.add(new Object[]{"ACACAC", "ACACACAC", "6M2I"}); + tests.add(new Object[]{"XXACACACXX", "XXACACACACXX", "8M2I2M"}); + tests.add(new Object[]{"XXACACACXX", "XXACACXX", "6M2D2M"}); + tests.add(new Object[]{"ACGT", "AACGT", "1I4M"}); + tests.add(new Object[]{"ACGT", "ACCGT", "2M1I2M"}); + tests.add(new Object[]{"ACGT", "ACGGT", "3M1I1M"}); + tests.add(new Object[]{"ACGT", "ACGTT", "4M1I"}); + tests.add(new Object[]{"ACGT", "CGT", "1D3M"}); + tests.add(new Object[]{"ACGT", "AGT", "1M1D2M"}); + tests.add(new Object[]{"ACGT", "ACT", "2M1D1M"}); + tests.add(new Object[]{"ACGT", "ACG", "3M1D"}); + + // mismatches through out the sequences + final String ref = "ACGTAACCGGTT"; + for ( int diff = 0; diff < ref.length(); diff++ ) { + final byte[] altBases = ref.getBytes(); + altBases[diff] = 'N'; + tests.add(new Object[]{ref, new String(altBases), ref.length() + "M"}); + } + for ( int diff1 = 0; diff1 < ref.length(); diff1++ ) { + for ( int diff2 = 0; diff2 < ref.length(); diff2++ ) { + final byte[] altBases = ref.getBytes(); + altBases[diff1] = 'N'; + altBases[diff2] = 'N'; + tests.add(new Object[]{ref, new String(altBases), ref.length() + "M"}); + } + } + + // prefixes and suffixes matching + final String totalPrefix = "ACG"; + final String totalSuffix = "GCT"; + for ( int prefixSize = 0; prefixSize < totalPrefix.length(); prefixSize++) { + for ( int suffixSize = 0; suffixSize < totalPrefix.length(); suffixSize++) { + if ( prefixSize + suffixSize == 0 ) + continue; + for ( int indelSize = 1; indelSize < 50; indelSize++ ) { + final String prefix = totalPrefix.substring(0, prefixSize); + final String suffix = totalSuffix.substring(0, suffixSize); + final String insert = Utils.dupString("N", indelSize); + tests.add(new Object[]{prefix + suffix, prefix + insert + suffix, prefix.length() + "M" + indelSize + "I" + suffix.length() + "M"}); + tests.add(new Object[]{prefix + insert + suffix, prefix + suffix, prefix.length() + "M" + indelSize + "D" + suffix.length() + "M"}); + } + } + } + + // larger indels with prefixes/suffixes + tests.add(new Object[]{"ACTGTTTTGAACATCAGTTATTTTAAACTTTTAAGTTGTTAGCACAGCAAAAGCAACAAAATTCTAAGTGCAGTAATCACTTTACTGCGTGGTCATATGAAATCAAGGCAATGTTATGAGTATTACTGGAAAGCTGGACAGAGTAACGGGAAAAGTGACTAAAACTATGC", "CCTGTTTTGAACATCAGTTATTTTAAACTTTTAAGTTGTTAGCACAGCAAAAGCAACAAAATTCTAAGTGCAGTAATCACTTTACTGCGTGGTCATATGAAATCAAGGCAATGTTATGAGTATTACTGGAAAGCTGGACAGAGTAACGGGAAAAGTGACT", "160M10D"}); + tests.add(new Object[]{"LLLLLTATTAAGTAGTGCTCTATGTTGTCAACTAATTTATTTCCCATTTCAAACATTAGTTGACATGTTTTCATTTCTCTTTTGGAAGGAAACAACTAAATATGTTATCAATCCATCATTTACTTGTACAATAAATAAAGTTCTAAATCACTGCACAGTGTAAAATGGCAAATAGACTTCCCCATAACACAAAGCCATCCTGAAAAGTTTTGTTCATTTTAGAAGRRRRR", "LLLLLARRRRR", "5M219D6M"}); + tests.add(new Object[]{"LLLLLTATTTTTTRRRRR", "LLLLLARRRRR", "5M7D6M"}); + + // systematic testing + for ( final int forwardMatches : Arrays.asList(0, 1, 5, 10)) { + for ( final int forwardMismatches : Arrays.asList(0, 1, 2)) { + for ( final int middleMatches : Arrays.asList(0, 1, 5, 10)) { + for ( final int delSize : Arrays.asList(0, 1, 2, 3 )) { + for ( final int insSize : Arrays.asList(0, 1, 2, 3 )) { + for ( final int reverseMismatches : Arrays.asList(0, 1, 2)) { + for ( final int reverseMatches : Arrays.asList(0, 1, 5, 10)) { + // if there is an insertion and deletion, they should cancel each other out (at least partially) + final int overlap = Math.min(delSize, insSize); + final int myDelSize = delSize - overlap; + final int myInsSize = insSize - overlap; + + // this case is too difficult to create a CIGAR for because SW will (legitimately) prefer to switch the indel and mismatches + final int totalMismatches = forwardMismatches + reverseMismatches; + if ( (myDelSize > 0 || myInsSize > 0 ) && (totalMismatches >= myDelSize || totalMismatches >= myInsSize) ) + continue; + + final StringBuilder refBuilder = new StringBuilder(); + final StringBuilder altBuilder = new StringBuilder(); + final StringBuilder cigarBuilder = new StringBuilder(); + + refBuilder.append(Utils.dupString('A', forwardMatches + forwardMismatches + middleMatches)); + altBuilder.append(Utils.dupString('A', forwardMatches)); + altBuilder.append(Utils.dupString('C', forwardMismatches)); + altBuilder.append(Utils.dupString('A', middleMatches)); + cigarBuilder.append(forwardMatches + forwardMismatches + middleMatches); + cigarBuilder.append("M"); + + if ( myDelSize > 0 ) { + refBuilder.append(Utils.dupString('G', myDelSize)); + cigarBuilder.append(myDelSize); + cigarBuilder.append("D"); + } + if ( myInsSize > 0 ) { + altBuilder.append(Utils.dupString('T', myInsSize)); + cigarBuilder.append(myInsSize); + cigarBuilder.append("I"); + } + if ( overlap > 0 ) { + refBuilder.append(Utils.dupString('G', overlap)); + altBuilder.append(Utils.dupString('T', overlap)); + cigarBuilder.append(overlap); + cigarBuilder.append("M"); + } + if ( delSize > 0 || insSize > 0 ) { + refBuilder.append(Utils.dupString('A', middleMatches)); + altBuilder.append(Utils.dupString('A', middleMatches)); + cigarBuilder.append(middleMatches); + cigarBuilder.append("M"); + } + + refBuilder.append(Utils.dupString('A', reverseMismatches + reverseMatches)); + altBuilder.append(Utils.dupString('C', reverseMismatches)); + altBuilder.append(Utils.dupString('A', reverseMatches)); + cigarBuilder.append(reverseMismatches + reverseMatches); + cigarBuilder.append("M"); + + if ( refBuilder.length() > 0 && altBuilder.length() > 0 ) + tests.add(new Object[]{refBuilder.toString(), altBuilder.toString(), cigarBuilder.toString()}); + } + } + } + } + } + } + } + + return tests.toArray(new Object[][]{}); + } + + @Test(dataProvider = "SWData", enabled = !DEBUG) + public void testSW(final String seq1, final String seq2, final String expectedCigar) { + final GlobalEdgeGreedySWPairwiseAlignment alignment = new GlobalEdgeGreedySWPairwiseAlignment(seq1.getBytes(), seq2.getBytes(), new Parameters(5.0, -5.0, -25.0, -1.0)); + Assert.assertEquals(alignment.getCigar(), AlignmentUtils.consolidateCigar(TextCigarCodec.getSingleton().decode(expectedCigar))); + } + + /** + * For debugging purposes only + */ + @Test(enabled = DEBUG) + public void testDebugging() { + final String ref = "A"; + final String alt = "C"; + + final GlobalEdgeGreedySWPairwiseAlignment sw = new GlobalEdgeGreedySWPairwiseAlignment(ref.getBytes(), alt.getBytes(), new Parameters(5.0, -5.0, -25.0, -1.0)); + Assert.assertEquals(sw.getCigar().toString(), "1M"); + } +} diff --git a/public/java/src/org/broadinstitute/sting/utils/Utils.java b/public/java/src/org/broadinstitute/sting/utils/Utils.java index 5b2bba73c..73a538ee5 100644 --- a/public/java/src/org/broadinstitute/sting/utils/Utils.java +++ b/public/java/src/org/broadinstitute/sting/utils/Utils.java @@ -789,4 +789,20 @@ public class Utils { } return end; } + + /** + * Trim any number of bases from the front and/or back of an array + * + * @param seq the sequence to trim + * @param trimFromFront how much to trim from the front + * @param trimFromBack how much to trim from the back + * @return a non-null array; can be the original array (i.e. not a copy) + */ + public static byte[] trimArray(final byte[] seq, final int trimFromFront, final int trimFromBack) { + if ( trimFromFront + trimFromBack > seq.length ) + throw new IllegalArgumentException("trimming total is larger than the original array"); + + // don't perform array copies if we need to copy everything anyways + return ( trimFromFront == 0 && trimFromBack == 0 ) ? seq : Arrays.copyOfRange(seq, trimFromFront, seq.length - trimFromBack); + } } diff --git a/public/java/src/org/broadinstitute/sting/utils/smithwaterman/GlobalEdgeGreedySWPairwiseAlignment.java b/public/java/src/org/broadinstitute/sting/utils/smithwaterman/GlobalEdgeGreedySWPairwiseAlignment.java new file mode 100644 index 000000000..27ead2e48 --- /dev/null +++ b/public/java/src/org/broadinstitute/sting/utils/smithwaterman/GlobalEdgeGreedySWPairwiseAlignment.java @@ -0,0 +1,217 @@ +/* +* Copyright (c) 2012 The Broad Institute +* +* Permission is hereby granted, free of charge, to any person +* obtaining a copy of this software and associated documentation +* files (the "Software"), to deal in the Software without +* restriction, including without limitation the rights to use, +* copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the +* Software is furnished to do so, subject to the following +* conditions: +* +* The above copyright notice and this permission notice shall be +* included in all copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR +* THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +package org.broadinstitute.sting.utils.smithwaterman; + +import net.sf.samtools.Cigar; +import net.sf.samtools.CigarElement; +import org.broadinstitute.sting.utils.Utils; +import org.broadinstitute.sting.utils.sam.AlignmentUtils; + +import java.util.*; + +/** + * Pairwise discrete Smith-Waterman alignment with an edge greedy implementation + * + * ************************************************************************ + * **** IMPORTANT NOTE: **** + * **** This class assumes that all bytes come from UPPERCASED chars! **** + * ************************************************************************ + * + * User: ebanks + */ +public final class GlobalEdgeGreedySWPairwiseAlignment extends SWPairwiseAlignment { + + private final static boolean DEBUG_MODE = false; + + /** + * Create a new greedy SW pairwise aligner + * + * @param reference the reference sequence we want to align + * @param alternate the alternate sequence we want to align + * @param parameters the SW parameters to use + */ + public GlobalEdgeGreedySWPairwiseAlignment(final byte[] reference, final byte[] alternate, final Parameters parameters) { + super(reference, alternate, parameters); + } + + /** + * Create a new SW pairwise aligner + * + * After creating the object the two sequences are aligned with an internal call to align(seq1, seq2) + * + * @param reference the reference sequence we want to align + * @param alternate the alternate sequence we want to align + * @param namedParameters the named parameter set to get our parameters from + */ + public GlobalEdgeGreedySWPairwiseAlignment(final byte[] reference, final byte[] alternate, final SWParameterSet namedParameters) { + this(reference, alternate, namedParameters.parameters); + } + + /** + * @see #GlobalEdgeGreedySWPairwiseAlignment(byte[], byte[], SWParameterSet) with original default parameters + */ + public GlobalEdgeGreedySWPairwiseAlignment(byte[] reference, byte[] alternate) { + this(reference, alternate, SWParameterSet.ORIGINAL_DEFAULT); + } + + /** + * Aligns the alternate sequence to the reference sequence + * + * @param reference ref sequence + * @param alternate alt sequence + */ + @Override + protected void align(final byte[] reference, final byte[] alternate) { + if ( reference == null || reference.length == 0 ) + throw new IllegalArgumentException("Non-null, non-empty reference sequences are required for the Smith-Waterman calculation"); + if ( alternate == null || alternate.length == 0 ) + throw new IllegalArgumentException("Non-null, non-empty alternate sequences are required for the Smith-Waterman calculation"); + + final int forwardEdgeMatch = Utils.longestCommonPrefix(reference, alternate, Integer.MAX_VALUE); + + // edge case: one sequence is a strict prefix of the other + if ( forwardEdgeMatch == reference.length || forwardEdgeMatch == alternate.length ) { + alignmentResult = new SWPairwiseAlignmentResult(makeCigarForStrictPrefixAndSuffix(reference, alternate, forwardEdgeMatch, 0), 0); + return; + } + + int reverseEdgeMatch = Utils.longestCommonSuffix(reference, alternate, Integer.MAX_VALUE); + + // edge case: one sequence is a strict suffix of the other + if ( reverseEdgeMatch == reference.length || reverseEdgeMatch == alternate.length ) { + alignmentResult = new SWPairwiseAlignmentResult(makeCigarForStrictPrefixAndSuffix(reference, alternate, 0, reverseEdgeMatch), 0); + return; + } + + final int sizeOfRefToAlign = reference.length - forwardEdgeMatch - reverseEdgeMatch; + final int sizeOfAltToAlign = alternate.length - forwardEdgeMatch - reverseEdgeMatch; + + // edge case: one sequence is a strict subset of the other accounting for both prefix and suffix + final int minSizeToAlign = Math.min(sizeOfRefToAlign, sizeOfAltToAlign); + if ( minSizeToAlign < 0 ) + reverseEdgeMatch += minSizeToAlign; + if ( sizeOfRefToAlign <= 0 || sizeOfAltToAlign <= 0 ) { + alignmentResult = new SWPairwiseAlignmentResult(makeCigarForStrictPrefixAndSuffix(reference, alternate, forwardEdgeMatch, reverseEdgeMatch), 0); + return; + } + + final byte[] refToAlign = Utils.trimArray(reference, forwardEdgeMatch, reverseEdgeMatch); + final byte[] altToAlign = Utils.trimArray(alternate, forwardEdgeMatch, reverseEdgeMatch); + + final double[] sw = new double[(sizeOfRefToAlign+1)*(sizeOfAltToAlign+1)]; + if ( keepScoringMatrix ) SW = sw; + final int[] btrack = new int[(sizeOfRefToAlign+1)*(sizeOfAltToAlign+1)]; + + calculateMatrix(refToAlign, altToAlign, sw, btrack, OVERHANG_STRATEGY.INDEL); + + if ( DEBUG_MODE ) { + System.out.println(new String(refToAlign) + " vs. " + new String(altToAlign)); + debugMatrix(sw, sizeOfRefToAlign+1, sizeOfAltToAlign+1); + System.out.println("----"); + debugMatrix(btrack, sizeOfRefToAlign + 1, sizeOfAltToAlign + 1); + System.out.println(); + } + + alignmentResult = calculateCigar(forwardEdgeMatch, reverseEdgeMatch, sizeOfRefToAlign, sizeOfAltToAlign, sw, btrack); + } + + private void debugMatrix(final double[] matrix, final int dim1, final int dim2) { + for ( int i = 0; i < dim1; i++ ) { + for ( int j = 0; j < dim2; j++ ) + System.out.print(String.format("%.1f ", matrix[i * dim2 + j])); + System.out.println(); + } + } + + private void debugMatrix(final int[] matrix, final int dim1, final int dim2) { + for ( int i = 0; i < dim1; i++ ) { + for ( int j = 0; j < dim2; j++ ) + System.out.print(matrix[i*dim2 + j] + " "); + System.out.println(); + } + } + + /** + * Creates a CIGAR for the case where the prefix/suffix match combination encompasses an entire sequence + * + * @param reference the reference sequence + * @param alternate the alternate sequence + * @param matchingPrefix the prefix match size + * @param matchingSuffix the suffix match size + * @return non-null CIGAR + */ + private Cigar makeCigarForStrictPrefixAndSuffix(final byte[] reference, final byte[] alternate, final int matchingPrefix, final int matchingSuffix) { + + final List result = new ArrayList(); + + // edge case: no D or I element + if ( reference.length == alternate.length ) { + result.add(makeElement(State.MATCH, matchingPrefix + matchingSuffix)); + } else { + // add the first M element + if ( matchingPrefix > 0 ) + result.add(makeElement(State.MATCH, matchingPrefix)); + + // add the D or I element + if ( alternate.length > reference.length ) + result.add(makeElement(State.INSERTION, alternate.length - reference.length)); + else // if ( reference.length > alternate.length ) + result.add(makeElement(State.DELETION, reference.length - alternate.length)); + + // add the last M element + if ( matchingSuffix > 0 ) + result.add(makeElement(State.MATCH, matchingSuffix)); + } + + return new Cigar(result); + } + + /** + * Calculates the CIGAR for the alignment from the back track matrix + * + * @param matchingPrefix the prefix match size + * @param matchingSuffix the suffix match size + * @param refLength length of the reference sequence + * @param altLength length of the alternate sequence + * @param sw the Smith-Waterman matrix to use + * @param btrack the back track matrix to use + * @return non-null SWPairwiseAlignmentResult object + */ + protected SWPairwiseAlignmentResult calculateCigar(final int matchingPrefix, final int matchingSuffix, + final int refLength, final int altLength, + final double[] sw, final int[] btrack) { + + final SWPairwiseAlignmentResult SW_result = calculateCigar(refLength, altLength, sw, btrack, OVERHANG_STRATEGY.INDEL); + + final LinkedList lce = new LinkedList(SW_result.cigar.getCigarElements()); + if ( matchingPrefix > 0 ) + lce.addFirst(makeElement(State.MATCH, matchingPrefix)); + if ( matchingSuffix > 0 ) + lce.addLast(makeElement(State.MATCH, matchingSuffix)); + + return new SWPairwiseAlignmentResult(AlignmentUtils.consolidateCigar(new Cigar(lce)), 0); + } +} \ No newline at end of file diff --git a/public/java/src/org/broadinstitute/sting/utils/smithwaterman/SWPairwiseAlignment.java b/public/java/src/org/broadinstitute/sting/utils/smithwaterman/SWPairwiseAlignment.java index 78f81ec5e..84c33d4a5 100644 --- a/public/java/src/org/broadinstitute/sting/utils/smithwaterman/SWPairwiseAlignment.java +++ b/public/java/src/org/broadinstitute/sting/utils/smithwaterman/SWPairwiseAlignment.java @@ -45,19 +45,43 @@ import java.util.*; * Date: Mar 23, 2009 * Time: 1:54:54 PM */ -public final class SWPairwiseAlignment implements SmithWaterman { - private int alignment_offset; // offset of s2 w/respect to s1 - private Cigar alignmentCigar; +public class SWPairwiseAlignment implements SmithWaterman { - private final Parameters parameters; + protected SWPairwiseAlignmentResult alignmentResult; - private static final int MSTATE = 0; - private static final int ISTATE = 1; - private static final int DSTATE = 2; - private static final int CLIP = 3; + protected final Parameters parameters; + + /** + * The state of a trace step through the matrix + */ + protected enum State { + MATCH, + INSERTION, + DELETION, + CLIP + } + + /** + * What strategy should we use when the best path does not start/end at the corners of the matrix? + */ + public enum OVERHANG_STRATEGY { + /* + * Add softclips for the overhangs + */ + SOFTCLIP, + /* + * Treat the overhangs as proper insertions/deletions + */ + INDEL, + /* + * Just ignore the overhangs + */ + IGNORE + } protected static boolean cutoff = false; - private boolean doSoftClipping = true; + + protected OVERHANG_STRATEGY overhang_strategy = OVERHANG_STRATEGY.SOFTCLIP; /** * The SW scoring matrix, stored for debugging purposes if keepScoringMatrix is true @@ -103,10 +127,6 @@ public final class SWPairwiseAlignment implements SmithWaterman { this.parameters = parameters; } - protected void setDoSoftClipping(final boolean doSoftClipping) { - this.doSoftClipping = doSoftClipping; - } - /** * Create a new SW pairwise aligner * @@ -125,42 +145,93 @@ public final class SWPairwiseAlignment implements SmithWaterman { } @Override - public Cigar getCigar() { return alignmentCigar ; } + public Cigar getCigar() { return alignmentResult.cigar ; } @Override - public int getAlignmentStart2wrt1() { return alignment_offset; } + public int getAlignmentStart2wrt1() { return alignmentResult.alignment_offset; } - public void align(final byte[] a, final byte[] b) { - final int n = a.length; - final int m = b.length; + /** + * Aligns the alternate sequence to the reference sequence + * + * @param reference ref sequence + * @param alternate alt sequence + */ + protected void align(final byte[] reference, final byte[] alternate) { + if ( reference == null || reference.length == 0 || alternate == null || alternate.length == 0 ) + throw new IllegalArgumentException("Non-null, non-empty sequences are required for the Smith-Waterman calculation"); + + final int n = reference.length; + final int m = alternate.length; double [] sw = new double[(n+1)*(m+1)]; if ( keepScoringMatrix ) SW = sw; int [] btrack = new int[(n+1)*(m+1)]; - calculateMatrix(a, b, sw, btrack); - calculateCigar(n, m, sw, btrack); // length of the segment (continuous matches, insertions or deletions) + calculateMatrix(reference, alternate, sw, btrack); + alignmentResult = calculateCigar(n, m, sw, btrack, overhang_strategy); // length of the segment (continuous matches, insertions or deletions) } + /** + * Calculates the SW matrices for the given sequences + * + * @param reference ref sequence + * @param alternate alt sequence + * @param sw the Smith-Waterman matrix to populate + * @param btrack the back track matrix to populate + */ + protected void calculateMatrix(final byte[] reference, final byte[] alternate, double[] sw, int[] btrack) { + calculateMatrix(reference, alternate, sw, btrack, overhang_strategy); + } - private void calculateMatrix(final byte[] a, final byte[] b, double [] sw, int [] btrack ) { - final int n = a.length+1; - final int m = b.length+1; + /** + * Calculates the SW matrices for the given sequences + * + * @param reference ref sequence + * @param alternate alt sequence + * @param sw the Smith-Waterman matrix to populate + * @param btrack the back track matrix to populate + * @param overhang_strategy the strategy to use for dealing with overhangs + */ + protected void calculateMatrix(final byte[] reference, final byte[] alternate, double[] sw, int[] btrack, final OVERHANG_STRATEGY overhang_strategy) { + if ( reference.length == 0 || alternate.length == 0 ) + throw new IllegalArgumentException("Non-null, non-empty sequences are required for the Smith-Waterman calculation"); + + final int n = reference.length+1; + final int m = alternate.length+1; //final double MATRIX_MIN_CUTOFF=-1e100; // never let matrix elements drop below this cutoff final double MATRIX_MIN_CUTOFF; // never let matrix elements drop below this cutoff if ( cutoff ) MATRIX_MIN_CUTOFF = 0.0; else MATRIX_MIN_CUTOFF = -1e100; - double [] best_gap_v = new double[m+1]; - Arrays.fill(best_gap_v,-1.0e40); - int [] gap_size_v = new int[m+1]; - double [] best_gap_h = new double[n+1]; + final double[] best_gap_v = new double[m+1]; + Arrays.fill(best_gap_v, -1.0e40); + final int[] gap_size_v = new int[m+1]; + final double[] best_gap_h = new double[n+1]; Arrays.fill(best_gap_h,-1.0e40); - int [] gap_size_h = new int[n+1]; + final int[] gap_size_h = new int[n+1]; + + // we need to initialize the SW matrix with gap penalties if we want to keep track of indels at the edges of alignments + if ( overhang_strategy == OVERHANG_STRATEGY.INDEL ) { + // initialize the first row + sw[1] = parameters.w_open; + double currentValue = parameters.w_open; + for ( int i = 2; i < m; i++ ) { + currentValue += parameters.w_extend; + sw[i] = currentValue; + } + + // initialize the first column + sw[m] = parameters.w_open; + currentValue = parameters.w_open; + for ( int i = 2; i < n; i++ ) { + currentValue += parameters.w_extend; + sw[i*m] = currentValue; + } + } // build smith-waterman matrix and keep backtrack info: for ( int i = 1, row_offset_1 = 0 ; i < n ; i++ ) { // we do NOT update row_offset_1 here, see comment at the end of this outer loop - byte a_base = a[i-1]; // letter in a at the current pos + byte a_base = reference[i-1]; // letter in a at the current pos final int row_offset = row_offset_1 + m; @@ -172,10 +243,10 @@ public final class SWPairwiseAlignment implements SmithWaterman { // data_offset_1 is linearized offset of element [i-1][j-1] - final byte b_base = b[j-1]; // letter in b at the current pos + final byte b_base = alternate[j-1]; // letter in b at the current pos // in other words, step_diag = sw[i-1][j-1] + wd(a_base,b_base); - double step_diag = sw[data_offset_1] + wd(a_base,b_base); + final double step_diag = sw[data_offset_1] + wd(a_base,b_base); // optimized "traversal" of all the matrix cells above the current one (i.e. traversing // all 'step down' events that would end in the current cell. The optimized code @@ -251,65 +322,92 @@ public final class SWPairwiseAlignment implements SmithWaterman { } } + /* + * Class to store the result of calculating the CIGAR from the back track matrix + */ + protected final class SWPairwiseAlignmentResult { + public final Cigar cigar; + public final int alignment_offset; + public SWPairwiseAlignmentResult(final Cigar cigar, final int alignment_offset) { + this.cigar = cigar; + this.alignment_offset = alignment_offset; + } + } - private void calculateCigar(int n, int m, double [] sw, int [] btrack) { + /** + * Calculates the CIGAR for the alignment from the back track matrix + * + * @param refLength length of the reference sequence + * @param altLength length of the alternate sequence + * @param sw the Smith-Waterman matrix to use + * @param btrack the back track matrix to use + * @param overhang_strategy the strategy to use for dealing with overhangs + * @return non-null SWPairwiseAlignmentResult object + */ + protected SWPairwiseAlignmentResult calculateCigar(final int refLength, final int altLength, final double[] sw, final int[] btrack, final OVERHANG_STRATEGY overhang_strategy) { // p holds the position we start backtracking from; we will be assembling a cigar in the backwards order int p1 = 0, p2 = 0; double maxscore = Double.NEGATIVE_INFINITY; // sw scores are allowed to be negative int segment_length = 0; // length of the segment (continuous matches, insertions or deletions) - // look for largest score. we use >= combined with the traversal direction - // to ensure that if two scores are equal, the one closer to diagonal gets picked - for ( int i = 1, data_offset = m+1+m ; i < n+1 ; i++, data_offset += (m+1) ) { - // data_offset is the offset of [i][m] - if ( sw[data_offset] >= maxscore ) { - p1 = i; p2 = m ; maxscore = sw[data_offset]; + // if we want to consider overhangs as legitimate operators, then just start from the corner of the matrix + if ( overhang_strategy == OVERHANG_STRATEGY.INDEL ) { + p1 = refLength; + p2 = altLength; + } else { + // look for largest score. we use >= combined with the traversal direction + // to ensure that if two scores are equal, the one closer to diagonal gets picked + for ( int i = 1, data_offset = altLength+1+altLength ; i < refLength+1 ; i++, data_offset += (altLength+1) ) { + // data_offset is the offset of [i][m] + if ( sw[data_offset] >= maxscore ) { + p1 = i; p2 = altLength ; maxscore = sw[data_offset]; + } } - } - for ( int j = 1, data_offset = n*(m+1)+1 ; j < m+1 ; j++, data_offset++ ) { - // data_offset is the offset of [n][j] - if ( sw[data_offset] > maxscore || sw[data_offset] == maxscore && Math.abs(n-j) < Math.abs(p1 - p2)) { - p1 = n; - p2 = j ; - maxscore = sw[data_offset]; - segment_length = m - j ; // end of sequence 2 is overhanging; we will just record it as 'M' segment + for ( int j = 1, data_offset = refLength*(altLength+1)+1 ; j < altLength+1 ; j++, data_offset++ ) { + // data_offset is the offset of [n][j] + if ( sw[data_offset] > maxscore || sw[data_offset] == maxscore && Math.abs(refLength-j) < Math.abs(p1 - p2)) { + p1 = refLength; + p2 = j ; + maxscore = sw[data_offset]; + segment_length = altLength - j ; // end of sequence 2 is overhanging; we will just record it as 'M' segment + } } } List lce = new ArrayList(5); - if ( segment_length > 0 && doSoftClipping ) { - lce.add(makeElement(CLIP, segment_length)); + if ( segment_length > 0 && overhang_strategy == OVERHANG_STRATEGY.SOFTCLIP ) { + lce.add(makeElement(State.CLIP, segment_length)); segment_length = 0; } // we will be placing all insertions and deletions into sequence b, so the states are named w/regard // to that sequence - int state = MSTATE; + State state = State.MATCH; - int data_offset = p1*(m+1)+p2; // offset of element [p1][p2] + int data_offset = p1*(altLength+1)+p2; // offset of element [p1][p2] do { int btr = btrack[data_offset]; - int new_state; + State new_state; int step_length = 1; if ( btr > 0 ) { - new_state = DSTATE; + new_state = State.DELETION; step_length = btr; } else if ( btr < 0 ) { - new_state = ISTATE; + new_state = State.INSERTION; step_length = (-btr); - } else new_state = MSTATE; // and step_length =1, already set above + } else new_state = State.MATCH; // and step_length =1, already set above // move to next best location in the sw matrix: switch( new_state ) { - case MSTATE: data_offset -= (m+2); p1--; p2--; break; // move back along the diag in the sw matrix - case ISTATE: data_offset -= step_length; p2 -= step_length; break; // move left - case DSTATE: data_offset -= (m+1)*step_length; p1 -= step_length; break; // move up + case MATCH: data_offset -= (altLength+2); p1--; p2--; break; // move back along the diag in the sw matrix + case INSERTION: data_offset -= step_length; p2 -= step_length; break; // move left + case DELETION: data_offset -= (altLength+1)*step_length; p1 -= step_length; break; // move up } // now let's see if the state actually changed: @@ -320,7 +418,7 @@ public final class SWPairwiseAlignment implements SmithWaterman { segment_length = step_length; state = new_state; } -// next condition is equivalent to while ( sw[p1][p2] != 0 ) (with modified p1 and/or p2: + // next condition is equivalent to while ( sw[p1][p2] != 0 ) (with modified p1 and/or p2: } while ( p1 > 0 && p2 > 0 ); // post-process the last segment we are still keeping; @@ -331,28 +429,41 @@ public final class SWPairwiseAlignment implements SmithWaterman { // last 3 bases of the read overlap with/align to the ref), the cigar will be still 5M if // DO_SOFTCLIP is false or 2S3M if DO_SOFTCLIP is true. // The consumers need to check for the alignment offset and deal with it properly. - if (doSoftClipping ) { + final int alignment_offset; + if ( overhang_strategy == OVERHANG_STRATEGY.SOFTCLIP ) { lce.add(makeElement(state, segment_length)); - if ( p2> 0 ) lce.add(makeElement(CLIP, p2)); - alignment_offset = p1 ; - } else { + if ( p2 > 0 ) lce.add(makeElement(State.CLIP, p2)); + alignment_offset = p1; + } else if ( overhang_strategy == OVERHANG_STRATEGY.IGNORE ) { lce.add(makeElement(state, segment_length + p2)); alignment_offset = p1 - p2; + } else { // overhang_strategy == OVERHANG_STRATEGY.INDEL + + // take care of the actual alignment + lce.add(makeElement(state, segment_length)); + + // take care of overhangs at the beginning of the alignment + if ( p1 > 0 ) + lce.add(makeElement(State.DELETION, p1)); + else if ( p2 > 0 ) + lce.add(makeElement(State.INSERTION, p2)); + + alignment_offset = 0; } Collections.reverse(lce); - alignmentCigar = AlignmentUtils.consolidateCigar(new Cigar(lce)); + return new SWPairwiseAlignmentResult(AlignmentUtils.consolidateCigar(new Cigar(lce)), alignment_offset); } - private CigarElement makeElement(int state, int segment_length) { - CigarOperator o = null; - switch(state) { - case MSTATE: o = CigarOperator.M; break; - case ISTATE: o = CigarOperator.I; break; - case DSTATE: o = CigarOperator.D; break; - case CLIP: o = CigarOperator.S; break; + protected CigarElement makeElement(final State state, final int length) { + CigarOperator op = null; + switch (state) { + case MATCH: op = CigarOperator.M; break; + case INSERTION: op = CigarOperator.I; break; + case DELETION: op = CigarOperator.D; break; + case CLIP: op = CigarOperator.S; break; } - return new CigarElement(segment_length,o); + return new CigarElement(length, op); } private double wd(byte x, byte y) { @@ -375,7 +486,7 @@ public final class SWPairwiseAlignment implements SmithWaterman { Cigar cigar = getCigar(); - if ( ! doSoftClipping ) { + if ( overhang_strategy != OVERHANG_STRATEGY.SOFTCLIP ) { // we need to go through all the hassle below only if we do not do softclipping; // otherwise offset is never negative diff --git a/public/java/src/org/broadinstitute/sting/utils/smithwaterman/SmithWaterman.java b/public/java/src/org/broadinstitute/sting/utils/smithwaterman/SmithWaterman.java index 44fd889c5..3a8afca8c 100644 --- a/public/java/src/org/broadinstitute/sting/utils/smithwaterman/SmithWaterman.java +++ b/public/java/src/org/broadinstitute/sting/utils/smithwaterman/SmithWaterman.java @@ -42,6 +42,7 @@ import net.sf.samtools.Cigar; * Time: 8:24 AM */ public interface SmithWaterman { + /** * Get the cigar string for the alignment of this SmithWaterman class * @return a non-null cigar diff --git a/public/java/test/org/broadinstitute/sting/utils/UtilsUnitTest.java b/public/java/test/org/broadinstitute/sting/utils/UtilsUnitTest.java index 3c68b8753..0a6f9898e 100644 --- a/public/java/test/org/broadinstitute/sting/utils/UtilsUnitTest.java +++ b/public/java/test/org/broadinstitute/sting/utils/UtilsUnitTest.java @@ -216,4 +216,24 @@ public class UtilsUnitTest extends BaseTest { } } } + + @DataProvider(name = "trim") + public Object[][] createTrimTestData() { + List tests = new ArrayList(); + + final String s = "AAAA"; + for ( int front = 0; front < s.length(); front++ ) { + for ( int back = 0; back < s.length(); back++ ) { + if ( front + back <= s.length() ) + tests.add(new Object[]{s, front, back}); + } + } + + return tests.toArray(new Object[][]{}); + } + + @Test(dataProvider = "trim", enabled = true) + public void testTrim(final String s, final int frontTrim, final int backTrim) { + Assert.assertEquals(s.length() - frontTrim - backTrim, Utils.trimArray(s.getBytes(), frontTrim, backTrim).length); + } } diff --git a/public/java/test/org/broadinstitute/sting/utils/smithwaterman/SmithWatermanBenchmark.java b/public/java/test/org/broadinstitute/sting/utils/smithwaterman/SmithWatermanBenchmark.java new file mode 100644 index 000000000..ee8f411bf --- /dev/null +++ b/public/java/test/org/broadinstitute/sting/utils/smithwaterman/SmithWatermanBenchmark.java @@ -0,0 +1,88 @@ +/* +* Copyright (c) 2012 The Broad Institute +* +* Permission is hereby granted, free of charge, to any person +* obtaining a copy of this software and associated documentation +* files (the "Software"), to deal in the Software without +* restriction, including without limitation the rights to use, +* copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the +* Software is furnished to do so, subject to the following +* conditions: +* +* The above copyright notice and this permission notice shall be +* included in all copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR +* THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +package org.broadinstitute.sting.utils.smithwaterman; + +import com.google.caliper.Param; +import com.google.caliper.SimpleBenchmark; +import org.broadinstitute.sting.utils.Utils; + +/** + * Caliper microbenchmark of parsing a VCF file + */ +public class SmithWatermanBenchmark extends SimpleBenchmark { + + @Param({"Original", "Greedy"}) + String version; // set automatically by framework + + @Param({"10", "50", "100", "500"}) + int sizeOfMiddleRegion; // set automatically by framework + + @Param({"10", "50", "100", "500"}) + int sizeOfEndRegions; // set automatically by framework + + String refString; + String hapString; + + @Override protected void setUp() { + final StringBuilder ref = new StringBuilder(); + final StringBuilder hap = new StringBuilder(); + + ref.append(Utils.dupString('A', sizeOfEndRegions)); + hap.append(Utils.dupString('A', sizeOfEndRegions)); + + // introduce a SNP + ref.append("X"); + hap.append("Y"); + + ref.append(Utils.dupString('A', sizeOfMiddleRegion)); + hap.append(Utils.dupString('A', sizeOfMiddleRegion)); + + // introduce a SNP + ref.append("X"); + hap.append("Y"); + + ref.append(Utils.dupString('A', sizeOfEndRegions)); + hap.append(Utils.dupString('A', sizeOfEndRegions)); + + refString = ref.toString(); + hapString = hap.toString(); + } + + public void timeSW(int rep) { + for ( int i = 0; i < rep; i++ ) { + final SmithWaterman sw; + if ( version.equals("Greedy") ) + sw = new GlobalEdgeGreedySWPairwiseAlignment(refString.getBytes(), hapString.getBytes()); + else + sw = new SWPairwiseAlignment(refString.getBytes(), hapString.getBytes()); + sw.getCigar(); + } + } + + public static void main(String[] args) { + com.google.caliper.Runner.main(SmithWatermanBenchmark.class, args); + } +} From b4f482a4212984b9b2927063261917f50b76634f Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Mon, 15 Apr 2013 08:20:28 -0400 Subject: [PATCH 09/10] NanoScheduled ActiveRegionTraversal and HaplotypeCaller -- Made CountReadsInActiveRegions Nano schedulable, confirming identical results for linear and nano results -- Made Haplotype NanoScheduled, requiring misc. changes in the map/reduce type so that the map() function returns a List and reduce actually prints out the results to disk -- Tests for NanoScheduling -- CountReadsInActiveRegionsIntegrationTest now does NCT 1, 2, 4 with CountReadsInActiveRegions -- HaplotypeCallerParallelIntegrationTest does NCT 1,2,4 calling on 100kb of PCR free data -- Some misc. code cleanup of HaplotypeCaller -- Analysis scripts to assess performance of nano scheduled HC -- In order to make the haplotype caller thread safe we needed to use an AtomicInteger for the class-specific static ID counter in SeqVertex and MultiDebrujinVertex, avoiding a race condition where multiple new Vertex() could end up with the same id. --- .../haplotypecaller/HaplotypeCaller.java | 45 ++-- .../LikelihoodCalculationEngine.java | 40 +-- .../haplotypecaller/graphs/SeqVertex.java | 8 +- .../readthreading/MultiDeBruijnVertex.java | 7 +- ...aplotypeCallerParallelIntegrationTest.java | 79 ++++++ .../sting/gatk/executive/MicroScheduler.java | 2 +- .../traversals/TraverseActiveRegions.java | 228 +++++++++++++----- .../TraverseActiveRegionsUnitTest.java | 16 +- 8 files changed, 306 insertions(+), 119 deletions(-) create mode 100644 protected/java/test/org/broadinstitute/sting/gatk/walkers/haplotypecaller/HaplotypeCallerParallelIntegrationTest.java diff --git a/protected/java/src/org/broadinstitute/sting/gatk/walkers/haplotypecaller/HaplotypeCaller.java b/protected/java/src/org/broadinstitute/sting/gatk/walkers/haplotypecaller/HaplotypeCaller.java index 33d1104bc..f065a0d7d 100644 --- a/protected/java/src/org/broadinstitute/sting/gatk/walkers/haplotypecaller/HaplotypeCaller.java +++ b/protected/java/src/org/broadinstitute/sting/gatk/walkers/haplotypecaller/HaplotypeCaller.java @@ -139,7 +139,7 @@ import java.util.*; @ActiveRegionTraversalParameters(extension=100, maxRegion=300) @ReadFilters({HCMappingQualityFilter.class}) @Downsample(by= DownsampleType.BY_SAMPLE, toCoverage=250) -public class HaplotypeCaller extends ActiveRegionWalker implements AnnotatorCompatible { +public class HaplotypeCaller extends ActiveRegionWalker, Integer> implements AnnotatorCompatible, NanoSchedulable { // ----------------------------------------------------------------------------------------------- // general haplotype caller arguments // ----------------------------------------------------------------------------------------------- @@ -645,15 +645,16 @@ public class HaplotypeCaller extends ActiveRegionWalker implem // //--------------------------------------------------------------------------------------------------------------- + private final static List NO_CALLS = Collections.emptyList(); @Override - public Integer map( final ActiveRegion originalActiveRegion, final RefMetaDataTracker metaDataTracker ) { + public List map( final ActiveRegion originalActiveRegion, final RefMetaDataTracker metaDataTracker ) { if ( justDetermineActiveRegions ) // we're benchmarking ART and/or the active region determination code in the HC, just leave without doing any work - return 1; + return NO_CALLS; - if( !originalActiveRegion.isActive() ) { return 0; } // Not active so nothing to do! + if( !originalActiveRegion.isActive() ) { return NO_CALLS; } // Not active so nothing to do! - final List activeAllelesToGenotype = new ArrayList(); + final List activeAllelesToGenotype = new ArrayList<>(); if( UG_engine.getUAC().GenotypingMode == GenotypeLikelihoodsCalculationModel.GENOTYPING_MODE.GENOTYPE_GIVEN_ALLELES ) { for( final VariantContext vc : allelesToGenotype ) { if( originalActiveRegion.getLocation().overlapsP( getToolkit().getGenomeLocParser().createGenomeLoc(vc) ) ) { @@ -662,23 +663,23 @@ public class HaplotypeCaller extends ActiveRegionWalker implem } allelesToGenotype.removeAll( activeAllelesToGenotype ); // No alleles found in this region so nothing to do! - if ( activeAllelesToGenotype.isEmpty() ) { return 0; } + if ( activeAllelesToGenotype.isEmpty() ) { return NO_CALLS; } } else { - if( originalActiveRegion.size() == 0 ) { return 0; } // No reads here so nothing to do! + if( originalActiveRegion.size() == 0 ) { return NO_CALLS; } // No reads here so nothing to do! } // run the local assembler, getting back a collection of information on how we should proceed final AssemblyResult assemblyResult = assembleReads(originalActiveRegion, activeAllelesToGenotype); // abort early if something is out of the acceptable range - if( ! assemblyResult.isVariationPresent() ) { return 1; } // only the reference haplotype remains so nothing else to do! - if (dontGenotype) return 1; // user requested we not proceed + if( ! assemblyResult.isVariationPresent() ) { return NO_CALLS; } // only the reference haplotype remains so nothing else to do! + if (dontGenotype) return NO_CALLS; // user requested we not proceed // filter out reads from genotyping which fail mapping quality based criteria final List filteredReads = filterNonPassingReads( assemblyResult.regionForGenotyping ); final Map> perSampleFilteredReadList = splitReadsBySample( filteredReads ); - if( assemblyResult.regionForGenotyping.size() == 0 ) { return 1; } // no reads remain after filtering so nothing else to do! + if( assemblyResult.regionForGenotyping.size() == 0 ) { return NO_CALLS; } // no reads remain after filtering so nothing else to do! // evaluate each sample's reads against all haplotypes //logger.info("Computing read likelihoods with " + assemblyResult.regionForGenotyping.size() + " reads"); @@ -697,12 +698,7 @@ public class HaplotypeCaller extends ActiveRegionWalker implem getToolkit().getGenomeLocParser(), activeAllelesToGenotype ); - for( final VariantContext call : calledHaplotypes.getCalls() ) { - // TODO -- uncomment this line once ART-based walkers have a proper RefMetaDataTracker. - // annotationEngine.annotateDBs(metaDataTracker, getToolkit().getGenomeLocParser().createGenomeLoc(call), call); - vcfWriter.add( call ); - } - + // TODO -- must disable if we are doing NCT, or set the output type of ! presorted if ( bamWriter != null ) { haplotypeBAMWriter.writeReadsAlignedToHaplotypes(assemblyResult.haplotypes, assemblyResult.paddedReferenceLoc, bestHaplotypes, @@ -712,7 +708,7 @@ public class HaplotypeCaller extends ActiveRegionWalker implem if( DEBUG ) { logger.info("----------------------------------------------------------------------------------"); } - return 1; // One active region was processed during this map call + return calledHaplotypes.getCalls(); } private final static class AssemblyResult { @@ -855,8 +851,13 @@ public class HaplotypeCaller extends ActiveRegionWalker implem } @Override - public Integer reduce(Integer cur, Integer sum) { - return cur + sum; + public Integer reduce(List callsInRegion, Integer numCalledRegions) { + for( final VariantContext call : callsInRegion ) { + // TODO -- uncomment this line once ART-based walkers have a proper RefMetaDataTracker. + // annotationEngine.annotateDBs(metaDataTracker, getToolkit().getGenomeLocParser().createGenomeLoc(call), call); + vcfWriter.add( call ); + } + return (callsInRegion.isEmpty() ? 0 : 1) + numCalledRegions; } @Override @@ -872,7 +873,7 @@ public class HaplotypeCaller extends ActiveRegionWalker implem private void finalizeActiveRegion( final ActiveRegion activeRegion ) { if( DEBUG ) { logger.info("Assembling " + activeRegion.getLocation() + " with " + activeRegion.size() + " reads: (with overlap region = " + activeRegion.getExtendedLoc() + ")"); } - final List finalizedReadList = new ArrayList(); + final List finalizedReadList = new ArrayList<>(); final FragmentCollection fragmentCollection = FragmentUtils.create( activeRegion.getReads() ); activeRegion.clearReads(); @@ -883,7 +884,7 @@ public class HaplotypeCaller extends ActiveRegionWalker implem } // Loop through the reads hard clipping the adaptor and low quality tails - final List readsToUse = new ArrayList(finalizedReadList.size()); + final List readsToUse = new ArrayList<>(finalizedReadList.size()); for( final GATKSAMRecord myRead : finalizedReadList ) { final GATKSAMRecord postAdapterRead = ( myRead.getReadUnmappedFlag() ? myRead : ReadClipper.hardClipAdaptorSequence( myRead ) ); if( postAdapterRead != null && !postAdapterRead.isEmpty() && postAdapterRead.getCigar().getReadLength() > 0 ) { @@ -937,7 +938,7 @@ public class HaplotypeCaller extends ActiveRegionWalker implem for( final String sample : samplesList) { List readList = returnMap.get( sample ); if( readList == null ) { - readList = new ArrayList(); + readList = new ArrayList<>(); returnMap.put(sample, readList); } } diff --git a/protected/java/src/org/broadinstitute/sting/gatk/walkers/haplotypecaller/LikelihoodCalculationEngine.java b/protected/java/src/org/broadinstitute/sting/gatk/walkers/haplotypecaller/LikelihoodCalculationEngine.java index fbd9b29d5..d5d5f3c09 100644 --- a/protected/java/src/org/broadinstitute/sting/gatk/walkers/haplotypecaller/LikelihoodCalculationEngine.java +++ b/protected/java/src/org/broadinstitute/sting/gatk/walkers/haplotypecaller/LikelihoodCalculationEngine.java @@ -71,7 +71,20 @@ public class LikelihoodCalculationEngine { private final byte constantGCP; private final double log10globalReadMismappingRate; private final boolean DEBUG; - private final PairHMM pairHMM; + private final PairHMM.HMM_IMPLEMENTATION hmmType; + + private final ThreadLocal pairHMM = new ThreadLocal() { + @Override + protected PairHMM initialValue() { + switch (hmmType) { + case EXACT: return new Log10PairHMM(true); + case ORIGINAL: return new Log10PairHMM(false); + case LOGLESS_CACHING: return new LoglessPairHMM(); + default: + throw new UserException.BadArgumentValue("pairHMM", "Specified pairHMM implementation is unrecognized or incompatible with the HaplotypeCaller. Acceptable options are ORIGINAL, EXACT, CACHING, and LOGLESS_CACHING."); + } + } + }; /** * The expected rate of random sequencing errors for a read originating from its true haplotype. @@ -96,22 +109,9 @@ public class LikelihoodCalculationEngine { * assigned a likelihood of -13. */ public LikelihoodCalculationEngine( final byte constantGCP, final boolean debug, final PairHMM.HMM_IMPLEMENTATION hmmType, final double log10globalReadMismappingRate ) { - switch (hmmType) { - case EXACT: - pairHMM = new Log10PairHMM(true); - break; - case ORIGINAL: - pairHMM = new Log10PairHMM(false); - break; - case LOGLESS_CACHING: - pairHMM = new LoglessPairHMM(); - break; - default: - throw new UserException.BadArgumentValue("pairHMM", "Specified pairHMM implementation is unrecognized or incompatible with the HaplotypeCaller. Acceptable options are ORIGINAL, EXACT, CACHING, and LOGLESS_CACHING."); - } - + this.hmmType = hmmType; this.constantGCP = constantGCP; - DEBUG = debug; + this.DEBUG = debug; this.log10globalReadMismappingRate = log10globalReadMismappingRate; } @@ -143,7 +143,7 @@ public class LikelihoodCalculationEngine { } // initialize arrays to hold the probabilities of being in the match, insertion and deletion cases - pairHMM.initialize(X_METRIC_LENGTH, Y_METRIC_LENGTH); + pairHMM.get().initialize(X_METRIC_LENGTH, Y_METRIC_LENGTH); } public Map computeReadLikelihoods( final List haplotypes, final Map> perSampleReadList ) { @@ -151,7 +151,7 @@ public class LikelihoodCalculationEngine { initializePairHMM(haplotypes, perSampleReadList); // Add likelihoods for each sample's reads to our stratifiedReadMap - final Map stratifiedReadMap = new HashMap(); + final Map stratifiedReadMap = new LinkedHashMap<>(); for( final Map.Entry> sampleEntry : perSampleReadList.entrySet() ) { // evaluate the likelihood of the reads given those haplotypes final PerReadAlleleLikelihoodMap map = computeReadLikelihoods(haplotypes, sampleEntry.getValue()); @@ -170,7 +170,7 @@ public class LikelihoodCalculationEngine { private PerReadAlleleLikelihoodMap computeReadLikelihoods( final List haplotypes, final List reads) { // first, a little set up to get copies of the Haplotypes that are Alleles (more efficient than creating them each time) final int numHaplotypes = haplotypes.size(); - final Map alleleVersions = new HashMap<>(numHaplotypes); + final Map alleleVersions = new LinkedHashMap<>(numHaplotypes); Allele refAllele = null; for ( final Haplotype haplotype : haplotypes ) { final Allele allele = Allele.create(haplotype, true); @@ -202,7 +202,7 @@ public class LikelihoodCalculationEngine { for( int jjj = 0; jjj < numHaplotypes; jjj++ ) { final Haplotype haplotype = haplotypes.get(jjj); final boolean isFirstHaplotype = jjj == 0; - final double log10l = pairHMM.computeReadLikelihoodGivenHaplotypeLog10(haplotype.getBases(), + final double log10l = pairHMM.get().computeReadLikelihoodGivenHaplotypeLog10(haplotype.getBases(), read.getReadBases(), readQuals, readInsQuals, readDelQuals, overallGCP, isFirstHaplotype); if ( haplotype.isNonReference() ) diff --git a/protected/java/src/org/broadinstitute/sting/gatk/walkers/haplotypecaller/graphs/SeqVertex.java b/protected/java/src/org/broadinstitute/sting/gatk/walkers/haplotypecaller/graphs/SeqVertex.java index f192b54aa..083747db4 100644 --- a/protected/java/src/org/broadinstitute/sting/gatk/walkers/haplotypecaller/graphs/SeqVertex.java +++ b/protected/java/src/org/broadinstitute/sting/gatk/walkers/haplotypecaller/graphs/SeqVertex.java @@ -49,6 +49,7 @@ package org.broadinstitute.sting.gatk.walkers.haplotypecaller.graphs; import com.google.java.contract.Requires; import org.broadinstitute.sting.utils.Utils; import java.util.Arrays; +import java.util.concurrent.atomic.AtomicInteger; /** * A graph vertex containing a sequence of bases and a unique ID that @@ -71,8 +72,9 @@ import java.util.Arrays; * @since 03/2013 */ public final class SeqVertex extends BaseVertex { - private static int idCounter = 0; - public final int id; + // Note that using an AtomicInteger is critical to allow multi-threaded HaplotypeCaller + private static final AtomicInteger idCounter = new AtomicInteger(0); + private int id = idCounter.getAndIncrement(); /** * Create a new SeqVertex with sequence and the next available id @@ -80,7 +82,6 @@ public final class SeqVertex extends BaseVertex { */ public SeqVertex(final byte[] sequence) { super(sequence); - this.id = idCounter++; } /** @@ -89,7 +90,6 @@ public final class SeqVertex extends BaseVertex { */ public SeqVertex(final String sequence) { super(sequence); - this.id = idCounter++; } /** diff --git a/protected/java/src/org/broadinstitute/sting/gatk/walkers/haplotypecaller/readthreading/MultiDeBruijnVertex.java b/protected/java/src/org/broadinstitute/sting/gatk/walkers/haplotypecaller/readthreading/MultiDeBruijnVertex.java index 814b3b9a7..5752583c7 100644 --- a/protected/java/src/org/broadinstitute/sting/gatk/walkers/haplotypecaller/readthreading/MultiDeBruijnVertex.java +++ b/protected/java/src/org/broadinstitute/sting/gatk/walkers/haplotypecaller/readthreading/MultiDeBruijnVertex.java @@ -51,6 +51,7 @@ import org.broadinstitute.sting.utils.Utils; import java.util.LinkedList; import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; /** * A DeBruijnVertex that supports multiple copies of the same kmer @@ -65,10 +66,12 @@ import java.util.List; */ final class MultiDeBruijnVertex extends DeBruijnVertex { private final static boolean KEEP_TRACK_OF_READS = false; - private static int idCounter = 0; + + // Note that using an AtomicInteger is critical to allow multi-threaded HaplotypeCaller + private static final AtomicInteger idCounter = new AtomicInteger(0); + private int id = idCounter.getAndIncrement(); private final List reads = new LinkedList(); - private int id = idCounter++; // TODO -- potential race condition problem here /** * Create a new MultiDeBruijnVertex with kmer sequence diff --git a/protected/java/test/org/broadinstitute/sting/gatk/walkers/haplotypecaller/HaplotypeCallerParallelIntegrationTest.java b/protected/java/test/org/broadinstitute/sting/gatk/walkers/haplotypecaller/HaplotypeCallerParallelIntegrationTest.java new file mode 100644 index 000000000..ff5a501cc --- /dev/null +++ b/protected/java/test/org/broadinstitute/sting/gatk/walkers/haplotypecaller/HaplotypeCallerParallelIntegrationTest.java @@ -0,0 +1,79 @@ +/* +* By downloading the PROGRAM you agree to the following terms of use: +* +* BROAD INSTITUTE - SOFTWARE LICENSE AGREEMENT - FOR ACADEMIC NON-COMMERCIAL RESEARCH PURPOSES ONLY +* +* This Agreement is made between the Broad Institute, Inc. with a principal address at 7 Cambridge Center, Cambridge, MA 02142 (BROAD) and the LICENSEE and is effective at the date the downloading is completed (EFFECTIVE DATE). +* +* WHEREAS, LICENSEE desires to license the PROGRAM, as defined hereinafter, and BROAD wishes to have this PROGRAM utilized in the public interest, subject only to the royalty-free, nonexclusive, nontransferable license rights of the United States Government pursuant to 48 CFR 52.227-14; and +* WHEREAS, LICENSEE desires to license the PROGRAM and BROAD desires to grant a license on the following terms and conditions. +* NOW, THEREFORE, in consideration of the promises and covenants made herein, the parties hereto agree as follows: +* +* 1. DEFINITIONS +* 1.1 PROGRAM shall mean copyright in the object code and source code known as GATK2 and related documentation, if any, as they exist on the EFFECTIVE DATE and can be downloaded from http://www.broadinstitute/GATK on the EFFECTIVE DATE. +* +* 2. LICENSE +* 2.1 Grant. Subject to the terms of this Agreement, BROAD hereby grants to LICENSEE, solely for academic non-commercial research purposes, a non-exclusive, non-transferable license to: (a) download, execute and display the PROGRAM and (b) create bug fixes and modify the PROGRAM. +* The LICENSEE may apply the PROGRAM in a pipeline to data owned by users other than the LICENSEE and provide these users the results of the PROGRAM provided LICENSEE does so for academic non-commercial purposes only. For clarification purposes, academic sponsored research is not a commercial use under the terms of this Agreement. +* 2.2 No Sublicensing or Additional Rights. LICENSEE shall not sublicense or distribute the PROGRAM, in whole or in part, without prior written permission from BROAD. LICENSEE shall ensure that all of its users agree to the terms of this Agreement. LICENSEE further agrees that it shall not put the PROGRAM on a network, server, or other similar technology that may be accessed by anyone other than the LICENSEE and its employees and users who have agreed to the terms of this agreement. +* 2.3 License Limitations. Nothing in this Agreement shall be construed to confer any rights upon LICENSEE by implication, estoppel, or otherwise to any computer software, trademark, intellectual property, or patent rights of BROAD, or of any other entity, except as expressly granted herein. LICENSEE agrees that the PROGRAM, in whole or part, shall not be used for any commercial purpose, including without limitation, as the basis of a commercial software or hardware product or to provide services. LICENSEE further agrees that the PROGRAM shall not be copied or otherwise adapted in order to circumvent the need for obtaining a license for use of the PROGRAM. +* +* 3. OWNERSHIP OF INTELLECTUAL PROPERTY +* LICENSEE acknowledges that title to the PROGRAM shall remain with BROAD. The PROGRAM is marked with the following BROAD copyright notice and notice of attribution to contributors. LICENSEE shall retain such notice on all copies. LICENSEE agrees to include appropriate attribution if any results obtained from use of the PROGRAM are included in any publication. +* Copyright 2012 Broad Institute, Inc. +* Notice of attribution: The GATK2 program was made available through the generosity of Medical and Population Genetics program at the Broad Institute, Inc. +* LICENSEE shall not use any trademark or trade name of BROAD, or any variation, adaptation, or abbreviation, of such marks or trade names, or any names of officers, faculty, students, employees, or agents of BROAD except as states above for attribution purposes. +* +* 4. INDEMNIFICATION +* LICENSEE shall indemnify, defend, and hold harmless BROAD, and their respective officers, faculty, students, employees, associated investigators and agents, and their respective successors, heirs and assigns, (Indemnitees), against any liability, damage, loss, or expense (including reasonable attorneys fees and expenses) incurred by or imposed upon any of the Indemnitees in connection with any claims, suits, actions, demands or judgments arising out of any theory of liability (including, without limitation, actions in the form of tort, warranty, or strict liability and regardless of whether such action has any factual basis) pursuant to any right or license granted under this Agreement. +* +* 5. NO REPRESENTATIONS OR WARRANTIES +* THE PROGRAM IS DELIVERED AS IS. BROAD MAKES NO REPRESENTATIONS OR WARRANTIES OF ANY KIND CONCERNING THE PROGRAM OR THE COPYRIGHT, EXPRESS OR IMPLIED, INCLUDING, WITHOUT LIMITATION, WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NONINFRINGEMENT, OR THE ABSENCE OF LATENT OR OTHER DEFECTS, WHETHER OR NOT DISCOVERABLE. BROAD EXTENDS NO WARRANTIES OF ANY KIND AS TO PROGRAM CONFORMITY WITH WHATEVER USER MANUALS OR OTHER LITERATURE MAY BE ISSUED FROM TIME TO TIME. +* IN NO EVENT SHALL BROAD OR ITS RESPECTIVE DIRECTORS, OFFICERS, EMPLOYEES, AFFILIATED INVESTIGATORS AND AFFILIATES BE LIABLE FOR INCIDENTAL OR CONSEQUENTIAL DAMAGES OF ANY KIND, INCLUDING, WITHOUT LIMITATION, ECONOMIC DAMAGES OR INJURY TO PROPERTY AND LOST PROFITS, REGARDLESS OF WHETHER BROAD SHALL BE ADVISED, SHALL HAVE OTHER REASON TO KNOW, OR IN FACT SHALL KNOW OF THE POSSIBILITY OF THE FOREGOING. +* +* 6. ASSIGNMENT +* This Agreement is personal to LICENSEE and any rights or obligations assigned by LICENSEE without the prior written consent of BROAD shall be null and void. +* +* 7. MISCELLANEOUS +* 7.1 Export Control. LICENSEE gives assurance that it will comply with all United States export control laws and regulations controlling the export of the PROGRAM, including, without limitation, all Export Administration Regulations of the United States Department of Commerce. Among other things, these laws and regulations prohibit, or require a license for, the export of certain types of software to specified countries. +* 7.2 Termination. LICENSEE shall have the right to terminate this Agreement for any reason upon prior written notice to BROAD. If LICENSEE breaches any provision hereunder, and fails to cure such breach within thirty (30) days, BROAD may terminate this Agreement immediately. Upon termination, LICENSEE shall provide BROAD with written assurance that the original and all copies of the PROGRAM have been destroyed, except that, upon prior written authorization from BROAD, LICENSEE may retain a copy for archive purposes. +* 7.3 Survival. The following provisions shall survive the expiration or termination of this Agreement: Articles 1, 3, 4, 5 and Sections 2.2, 2.3, 7.3, and 7.4. +* 7.4 Notice. Any notices under this Agreement shall be in writing, shall specifically refer to this Agreement, and shall be sent by hand, recognized national overnight courier, confirmed facsimile transmission, confirmed electronic mail, or registered or certified mail, postage prepaid, return receipt requested. All notices under this Agreement shall be deemed effective upon receipt. +* 7.5 Amendment and Waiver; Entire Agreement. This Agreement may be amended, supplemented, or otherwise modified only by means of a written instrument signed by all parties. Any waiver of any rights or failure to act in a specific instance shall relate only to such instance and shall not be construed as an agreement to waive any rights or fail to act in any other instance, whether or not similar. This Agreement constitutes the entire agreement among the parties with respect to its subject matter and supersedes prior agreements or understandings between the parties relating to its subject matter. +* 7.6 Binding Effect; Headings. This Agreement shall be binding upon and inure to the benefit of the parties and their respective permitted successors and assigns. All headings are for convenience only and shall not affect the meaning of any provision of this Agreement. +* 7.7 Governing Law. This Agreement shall be construed, governed, interpreted and applied in accordance with the internal laws of the Commonwealth of Massachusetts, U.S.A., without regard to conflict of laws principles. +*/ + +package org.broadinstitute.sting.gatk.walkers.haplotypecaller; + +import org.broadinstitute.sting.WalkerTest; +import org.broadinstitute.sting.utils.haplotypeBAMWriter.HaplotypeBAMWriter; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +public class HaplotypeCallerParallelIntegrationTest extends WalkerTest { + @DataProvider(name = "NCTDataProvider") + public Object[][] makeNCTDataProvider() { + List tests = new ArrayList(); + + for ( final int nct : Arrays.asList(1, 2, 4) ) { + tests.add(new Object[]{nct, "c277fd65365d59b734260dd8423313bb"}); + } + + return tests.toArray(new Object[][]{}); + } + + @Test(dataProvider = "NCTDataProvider") + public void testHCNCT(final int nct, final String md5) { + WalkerTestSpec spec = new WalkerTestSpec( + "-T HaplotypeCaller -R " + b37KGReference + " --no_cmdline_in_header -I " + + privateTestDir + "PCRFree.2x250.Illumina.20_10_11.bam -o %s " + + " -L 20:10,000,000-10,100,000 -G none -A -contamination 0.0 -nct " + nct, 1, + Arrays.asList(md5)); + executeTest("HC test parallel HC with NCT with nct " + nct, spec); + } +} diff --git a/public/java/src/org/broadinstitute/sting/gatk/executive/MicroScheduler.java b/public/java/src/org/broadinstitute/sting/gatk/executive/MicroScheduler.java index dc9dfd77e..23b084d66 100644 --- a/public/java/src/org/broadinstitute/sting/gatk/executive/MicroScheduler.java +++ b/public/java/src/org/broadinstitute/sting/gatk/executive/MicroScheduler.java @@ -245,7 +245,7 @@ public abstract class MicroScheduler implements MicroSchedulerMBean { } else if (walker instanceof ReadPairWalker) { return new TraverseReadPairs(); } else if (walker instanceof ActiveRegionWalker) { - return new TraverseActiveRegions(); + return new TraverseActiveRegions(threadAllocation.getNumCPUThreadsPerDataThread()); } else { throw new UnsupportedOperationException("Unable to determine traversal type, the walker is an unknown type."); } diff --git a/public/java/src/org/broadinstitute/sting/gatk/traversals/TraverseActiveRegions.java b/public/java/src/org/broadinstitute/sting/gatk/traversals/TraverseActiveRegions.java index f9a4fcdbb..b47a355be 100644 --- a/public/java/src/org/broadinstitute/sting/gatk/traversals/TraverseActiveRegions.java +++ b/public/java/src/org/broadinstitute/sting/gatk/traversals/TraverseActiveRegions.java @@ -41,12 +41,22 @@ import org.broadinstitute.sting.gatk.walkers.Walker; import org.broadinstitute.sting.utils.GenomeLoc; import org.broadinstitute.sting.utils.SampleUtils; import org.broadinstitute.sting.utils.Utils; -import org.broadinstitute.sting.utils.activeregion.*; +import org.broadinstitute.sting.utils.activeregion.ActiveRegion; +import org.broadinstitute.sting.utils.activeregion.ActivityProfile; +import org.broadinstitute.sting.utils.activeregion.ActivityProfileState; +import org.broadinstitute.sting.utils.activeregion.BandPassActivityProfile; +import org.broadinstitute.sting.utils.nanoScheduler.NSMapFunction; +import org.broadinstitute.sting.utils.nanoScheduler.NSProgressFunction; +import org.broadinstitute.sting.utils.nanoScheduler.NSReduceFunction; +import org.broadinstitute.sting.utils.nanoScheduler.NanoScheduler; import org.broadinstitute.sting.utils.progressmeter.ProgressMeter; import org.broadinstitute.sting.utils.sam.GATKSAMRecord; import java.io.PrintStream; -import java.util.*; +import java.util.Collection; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; /** * Implement active region traversal @@ -67,7 +77,8 @@ import java.util.*; * variable spanOfLastReadSeen * */ -public class TraverseActiveRegions extends TraversalEngine,LocusShardDataProvider> { +public final class TraverseActiveRegions extends TraversalEngine,LocusShardDataProvider> { + private final static boolean DEBUG = false; protected final static Logger logger = Logger.getLogger(TraversalEngine.class); protected final static boolean LOG_READ_CARRYING = false; @@ -84,7 +95,32 @@ public class TraverseActiveRegions extends TraversalEngine walker; + + final NanoScheduler nanoScheduler; + + /** + * Create a single threaded active region traverser + */ + public TraverseActiveRegions() { + this(1); + } + + /** + * Create an active region traverser that uses nThreads for getting its work done + * @param nThreads number of threads + */ + public TraverseActiveRegions(final int nThreads) { + nanoScheduler = new NanoScheduler<>(nThreads); + nanoScheduler.setProgressFunction(new NSProgressFunction() { + @Override + public void progress(ActiveRegion lastActiveRegion) { + if ( lastActiveRegion != null ) + // note, need to use getStopLocation so we don't give an interval to ProgressMeterDaemon + printProgress(lastActiveRegion.getLocation().getStopLocation()); + } + }); + } /** * Have the debugging output streams been initialized already? @@ -98,7 +134,7 @@ public class TraverseActiveRegions extends TraversalEngine)walker; if ( this.walker.wantsExtendedReads() && ! this.walker.wantsNonPrimaryReads() ) { throw new IllegalArgumentException("Active region walker " + this.walker + " requested extended events but not " + "non-primary reads, an inconsistent state. Please modify the walker"); @@ -217,58 +253,108 @@ public class TraverseActiveRegions extends TraversalEngine reads = locusView.getLIBS().transferReadsFromAllPreviousPileups(); - for( final GATKSAMRecord read : reads ) { - if ( ! appearedInLastShard(locOfLastReadAtTraversalStart, read) ) { - rememberLastReadLocation(read); - myReads.add(read); - } - } - - // skip this location -- it's not part of our engine intervals - if ( outsideEngineIntervals(location) ) - continue; - - // we've move across some interval boundary, restart profile - final boolean flushProfile = ! activityProfile.isEmpty() - && ( activityProfile.getContigIndex() != location.getContigIndex() - || location.getStart() != activityProfile.getStop() + 1); - sum = processActiveRegions(walker, sum, flushProfile, false); - - dataProvider.getShard().getReadMetrics().incrementNumIterations(); - - // create reference context. Note that if we have a pileup of "extended events", the context will - // hold the (longest) stretch of deleted reference bases (if deletions are present in the pileup). - final ReferenceContext refContext = referenceView.getReferenceContext(location); - - // Iterate forward to get all reference ordered data covering this location - final RefMetaDataTracker tracker = referenceOrderedDataView.getReferenceOrderedDataAtLocus(locus.getLocation(), refContext); - - // Call the walkers isActive function for this locus and add them to the list to be integrated later - addIsActiveResult(walker, tracker, refContext, locus); - - maxReadsInMemory = Math.max(myReads.size(), maxReadsInMemory); - printProgress(location); - } + nanoScheduler.setDebug(false); + final Iterator activeRegionIterator = new ActiveRegionIterator(dataProvider); + final TraverseActiveRegionMap myMap = new TraverseActiveRegionMap(); + final TraverseActiveRegionReduce myReduce = new TraverseActiveRegionReduce(); + final T result = nanoScheduler.execute(activeRegionIterator, myMap, sum, myReduce); updateCumulativeMetrics(dataProvider.getShard()); - return sum; + return result; + } + + private class ActiveRegionIterator implements Iterator { + private final LocusShardDataProvider dataProvider; + private LinkedList readyActiveRegions = new LinkedList(); + private boolean done = false; + private final LocusView locusView; + private final LocusReferenceView referenceView; + private final ReferenceOrderedView referenceOrderedDataView; + private final GenomeLoc locOfLastReadAtTraversalStart; + + public ActiveRegionIterator( final LocusShardDataProvider dataProvider ) { + this.dataProvider = dataProvider; + locusView = new AllLocusView(dataProvider); + referenceView = new LocusReferenceView( walker, dataProvider ); + referenceOrderedDataView = getReferenceOrderedView(walker, dataProvider, locusView); + + // We keep processing while the next reference location is within the interval + locOfLastReadAtTraversalStart = spanOfLastSeenRead(); + } + + @Override public void remove() { throw new UnsupportedOperationException("Cannot remove from ActiveRegionIterator"); } + + @Override + public ActiveRegion next() { + return readyActiveRegions.pop(); + } + @Override + public boolean hasNext() { + if ( ! readyActiveRegions.isEmpty() ) + return true; + if ( done ) + return false; + else { + + while( locusView.hasNext() ) { + final AlignmentContext locus = locusView.next(); + final GenomeLoc location = locus.getLocation(); + + rememberLastLocusLocation(location); + + // get all of the new reads that appear in the current pileup, and them to our list of reads + // provided we haven't seen them before + final Collection reads = locusView.getLIBS().transferReadsFromAllPreviousPileups(); + for( final GATKSAMRecord read : reads ) { + // note that ActiveRegionShards span entire contigs, so this check is in some + // sense no longer necessary, as any read that appeared in the last shard would now + // by definition be on a different contig. However, the logic here doesn't hurt anything + // and makes us robust should we decided to provide shards that don't fully span + // contigs at some point in the future + if ( ! appearedInLastShard(locOfLastReadAtTraversalStart, read) ) { + rememberLastReadLocation(read); + myReads.add(read); + } + } + + // skip this location -- it's not part of our engine intervals + if ( outsideEngineIntervals(location) ) + continue; + + // we've move across some interval boundary, restart profile + final boolean flushProfile = ! activityProfile.isEmpty() + && ( activityProfile.getContigIndex() != location.getContigIndex() + || location.getStart() != activityProfile.getStop() + 1); + final List newActiveRegions = prepActiveRegionsForProcessing(walker, flushProfile, false); + + dataProvider.getShard().getReadMetrics().incrementNumIterations(); + + // create reference context. Note that if we have a pileup of "extended events", the context will + // hold the (longest) stretch of deleted reference bases (if deletions are present in the pileup). + final ReferenceContext refContext = referenceView.getReferenceContext(location); + + // Iterate forward to get all reference ordered data covering this location + final RefMetaDataTracker tracker = referenceOrderedDataView.getReferenceOrderedDataAtLocus(locus.getLocation(), refContext); + + // Call the walkers isActive function for this locus and add them to the list to be integrated later + addIsActiveResult(walker, tracker, refContext, locus); + + maxReadsInMemory = Math.max(myReads.size(), maxReadsInMemory); + printProgress(location); + + if ( ! newActiveRegions.isEmpty() ) { + readyActiveRegions.addAll(newActiveRegions); + if ( DEBUG ) + for ( final ActiveRegion region : newActiveRegions ) + logger.info("Adding region to queue for processing " + region); + return true; + } + } + + return false; + } + } } /** @@ -276,7 +362,11 @@ public class TraverseActiveRegions extends TraversalEngine walker, T sum) { - return processActiveRegions((ActiveRegionWalker)walker, sum, true, true); + for ( final ActiveRegion region : prepActiveRegionsForProcessing((ActiveRegionWalker)walker, true, true) ) { + final M x = ((ActiveRegionWalker) walker).map(region, null); + sum = walker.reduce( x, sum ); + } + return sum; } // ------------------------------------------------------------------------------------- @@ -504,7 +594,7 @@ public class TraverseActiveRegions extends TraversalEngine walker, T sum, final boolean flushActivityProfile, final boolean forceAllRegionsToBeActive) { + private List prepActiveRegionsForProcessing(final ActiveRegionWalker walker, final boolean flushActivityProfile, final boolean forceAllRegionsToBeActive) { if ( ! walkerHasPresetRegions ) { // We don't have preset regions, so we get our regions from the activity profile final Collection activeRegions = activityProfile.popReadyActiveRegions(getActiveRegionExtension(), getMinRegionSize(), getMaxRegionSize(), flushActivityProfile); @@ -513,21 +603,23 @@ public class TraverseActiveRegions extends TraversalEngine readyRegions = new LinkedList(); while( workQueue.peek() != null ) { final ActiveRegion activeRegion = workQueue.peek(); if ( forceAllRegionsToBeActive || regionCompletelyWithinDeadZone(activeRegion) ) { writeActivityProfile(activeRegion.getSupportingStates()); writeActiveRegion(activeRegion); - sum = processActiveRegion( workQueue.remove(), sum, walker ); + readyRegions.add(prepActiveRegionForProcessing(workQueue.remove(), walker)); } else { break; } } - return sum; + return readyRegions; + } - private T processActiveRegion(final ActiveRegion activeRegion, final T sum, final ActiveRegionWalker walker) { + private ActiveRegion prepActiveRegionForProcessing(final ActiveRegion activeRegion, final ActiveRegionWalker walker) { final List stillLive = new LinkedList(); for ( final GATKSAMRecord read : myReads.popCurrentReads() ) { boolean killed = false; @@ -561,7 +653,21 @@ public class TraverseActiveRegions extends TraversalEngine { + @Override + public M apply(final ActiveRegion activeRegion) { + if ( DEBUG ) logger.info("Executing walker.map for " + activeRegion + " in thread " + Thread.currentThread().getName()); + return walker.map(activeRegion, null); + } + } + + private class TraverseActiveRegionReduce implements NSReduceFunction { + @Override + public T apply(M one, T sum) { + return walker.reduce(one, sum); + } } } diff --git a/public/java/test/org/broadinstitute/sting/gatk/traversals/TraverseActiveRegionsUnitTest.java b/public/java/test/org/broadinstitute/sting/gatk/traversals/TraverseActiveRegionsUnitTest.java index b6106d4bc..2e6705d77 100644 --- a/public/java/test/org/broadinstitute/sting/gatk/traversals/TraverseActiveRegionsUnitTest.java +++ b/public/java/test/org/broadinstitute/sting/gatk/traversals/TraverseActiveRegionsUnitTest.java @@ -77,7 +77,7 @@ public class TraverseActiveRegionsUnitTest extends BaseTest { @DataProvider(name = "TraversalEngineProvider") public Object[][] makeTraversals() { final List traversals = new LinkedList(); - traversals.add(new Object[]{new TraverseActiveRegions()}); + traversals.add(new Object[]{new TraverseActiveRegions<>()}); return traversals.toArray(new Object[][]{}); } @@ -523,8 +523,8 @@ public class TraverseActiveRegionsUnitTest extends BaseTest { final int maxTests = Integer.MAX_VALUE; int nTests = 0; - for ( final int readLength : Arrays.asList(10, 100) ) { - for ( final int skips : Arrays.asList(0, 1, 10) ) { + for ( final int readLength : Arrays.asList(100) ) { + for ( final int skips : Arrays.asList(0, 10) ) { for ( final int start : starts ) { for ( final int nReadsPerLocus : Arrays.asList(1, 2) ) { for ( final int nLoci : Arrays.asList(1, 1000) ) { @@ -536,7 +536,7 @@ public class TraverseActiveRegionsUnitTest extends BaseTest { for ( final GenomeLocSortedSet activeRegions : enumerateActiveRegions(bamBuilder.getAlignmentStart(), bamBuilder.getAlignmentEnd())) { nTests++; if ( nTests < maxTests ) // && nTests == 1238 ) - tests.add(new Object[]{nTests, activeRegions, readStates, bamBuilder}); + tests.add(new Object[]{new TraverseActiveRegions<>(), nTests, activeRegions, readStates, bamBuilder}); } } } @@ -586,7 +586,7 @@ public class TraverseActiveRegionsUnitTest extends BaseTest { @Test(enabled = true && ! DEBUG, dataProvider = "CombinatorialARTTilingProvider") - public void testARTReadsInActiveRegions(final int id, final GenomeLocSortedSet activeRegions, final EnumSet readStates, final ArtificialBAMBuilder bamBuilder) { + public void testARTReadsInActiveRegions(final TraverseActiveRegions traversal, final int id, final GenomeLocSortedSet activeRegions, final EnumSet readStates, final ArtificialBAMBuilder bamBuilder) { logger.warn("Running testARTReadsInActiveRegions id=" + id + " locs " + activeRegions + " against bam " + bamBuilder); final List intervals = Arrays.asList( genomeLocParser.createGenomeLoc("1", bamBuilder.getAlignmentStart(), bamBuilder.getAlignmentEnd()) @@ -595,7 +595,6 @@ public class TraverseActiveRegionsUnitTest extends BaseTest { final DummyActiveRegionWalker walker = new DummyActiveRegionWalker(activeRegions, false); walker.setStates(readStates); - final TraverseActiveRegions traversal = new TraverseActiveRegions(); final Map activeRegionsMap = getActiveRegions(traversal, walker, intervals, bamBuilder.makeTemporarilyBAMFile()); final Set alreadySeenReads = new HashSet(); // for use with the primary / non-primary @@ -640,8 +639,8 @@ public class TraverseActiveRegionsUnitTest extends BaseTest { // // --------------------------------------------------------------------------------------------------------- - @Test(enabled = true && ! DEBUG) - public void ensureAllInsertionReadsAreInActiveRegions() { + @Test(dataProvider = "TraversalEngineProvider", enabled = true && ! DEBUG) + public void ensureAllInsertionReadsAreInActiveRegions(final TraverseActiveRegions traversal) { final int readLength = 10; final int start = 20; @@ -667,7 +666,6 @@ public class TraverseActiveRegionsUnitTest extends BaseTest { final DummyActiveRegionWalker walker = new DummyActiveRegionWalker(activeRegions, false); - final TraverseActiveRegions traversal = new TraverseActiveRegions(); final Map activeRegionsMap = getActiveRegions(traversal, walker, intervals, bamBuilder.makeTemporarilyBAMFile()); final ActiveRegion region = activeRegionsMap.values().iterator().next(); From 39e4396de0189f0acd39af66f08b0932028906cb Mon Sep 17 00:00:00 2001 From: Mark DePristo Date: Thu, 9 May 2013 11:39:19 -0400 Subject: [PATCH 10/10] New ActiveRegionShardBalancer allows efficient NanoScheduling -- Previously we used the LocusShardBalancer for the haplotype caller, which meant that TraverseActiveRegions saw its shards grouped in chunks of 16kb bits on the genome. These locus shards are useful when you want to use the HierarchicalMicroScheduler, as they provide fine-grained accessed to the underlying BAM, but they have two major drawbacks (1) we have to fairly frequently reset our state in TAR to handle moving between shard boundaries and (2) with the nano scheduled TAR we end up blocking at the end of each shard while our threads all finish processing. -- This commit changes the system over to using an ActiveRegionShardBalancers, that combines all of the shard data for a single contig into a single combined shard. This ensures that TAR, and by extensions the HaplotypeCaller, gets all of the data on a single contig together so the the NanoSchedule runs efficiently instead of blocking over and over at shard boundaries. This simple change allows us to scale efficiently to around 8 threads in the nano scheduler: -- See https://www.dropbox.com/s/k7f280pd2zt0lyh/hc_nano_linear_scale.pdf -- See https://www.dropbox.com/s/fflpnan802m2906/hc_nano_log_scale.pdf -- Misc. changes throughout the codebase so we Use the ActiveRegionShardBalancer where appropriate. -- Added unit tests for ActiveRegionShardBalancer to confirm it does the merging as expected. -- Fix bad toString in FilePointer --- .../sting/gatk/GenomeAnalysisEngine.java | 4 +- .../reads/ActiveRegionShardBalancer.java | 85 +++++++++++++++ .../gatk/datasources/reads/FilePointer.java | 4 +- .../ActiveRegionShardBalancerUnitTest.java | 101 ++++++++++++++++++ .../TraverseActiveRegionsUnitTest.java | 2 +- 5 files changed, 191 insertions(+), 5 deletions(-) create mode 100644 public/java/src/org/broadinstitute/sting/gatk/datasources/reads/ActiveRegionShardBalancer.java create mode 100644 public/java/test/org/broadinstitute/sting/gatk/datasources/reads/ActiveRegionShardBalancerUnitTest.java diff --git a/public/java/src/org/broadinstitute/sting/gatk/GenomeAnalysisEngine.java b/public/java/src/org/broadinstitute/sting/gatk/GenomeAnalysisEngine.java index 82bee7826..9dcba25ff 100644 --- a/public/java/src/org/broadinstitute/sting/gatk/GenomeAnalysisEngine.java +++ b/public/java/src/org/broadinstitute/sting/gatk/GenomeAnalysisEngine.java @@ -570,9 +570,9 @@ public class GenomeAnalysisEngine { if (readsDataSource.getSortOrder() != SAMFileHeader.SortOrder.coordinate) throw new UserException.MissortedBAM(SAMFileHeader.SortOrder.coordinate, "Active region walkers can only traverse coordinate-sorted data. Please resort your input BAM file(s) or set the Sort Order tag in the header appropriately."); if(intervals == null) - return readsDataSource.createShardIteratorOverMappedReads(new LocusShardBalancer()); + return readsDataSource.createShardIteratorOverMappedReads(new ActiveRegionShardBalancer()); else - return readsDataSource.createShardIteratorOverIntervals(((ActiveRegionWalker)walker).extendIntervals(intervals, this.genomeLocParser, this.getReferenceDataSource().getReference()), new LocusShardBalancer()); + return readsDataSource.createShardIteratorOverIntervals(((ActiveRegionWalker)walker).extendIntervals(intervals, this.genomeLocParser, this.getReferenceDataSource().getReference()), new ActiveRegionShardBalancer()); } else if(walker instanceof ReadWalker || walker instanceof ReadPairWalker || walker instanceof DuplicateWalker) { // Apply special validation to read pair walkers. diff --git a/public/java/src/org/broadinstitute/sting/gatk/datasources/reads/ActiveRegionShardBalancer.java b/public/java/src/org/broadinstitute/sting/gatk/datasources/reads/ActiveRegionShardBalancer.java new file mode 100644 index 000000000..febdc788e --- /dev/null +++ b/public/java/src/org/broadinstitute/sting/gatk/datasources/reads/ActiveRegionShardBalancer.java @@ -0,0 +1,85 @@ +/* +* Copyright (c) 2012 The Broad Institute +* +* Permission is hereby granted, free of charge, to any person +* obtaining a copy of this software and associated documentation +* files (the "Software"), to deal in the Software without +* restriction, including without limitation the rights to use, +* copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the +* Software is furnished to do so, subject to the following +* conditions: +* +* The above copyright notice and this permission notice shall be +* included in all copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR +* THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +package org.broadinstitute.sting.gatk.datasources.reads; + +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; + +/** + * ActiveRegionShardBalancer + * + * Merges all of the file pointer information for a single contig index into a single + * combined shard. The purpose of doing this is to ensure that the HaplotypeCaller, which + * doesn't support TreeReduction by construction, gets all of the data on a single + * contig together so the the NanoSchedule runs efficiently + */ +public class ActiveRegionShardBalancer extends ShardBalancer { + /** + * Convert iterators of file pointers into balanced iterators of shards. + * @return An iterator over balanced shards. + */ + public Iterator iterator() { + return new Iterator() { + public boolean hasNext() { + return filePointers.hasNext(); + } + + public Shard next() { + FilePointer current = getCombinedFilePointersOnSingleContig(); + + // FilePointers have already been combined as necessary at the IntervalSharder level. No + // need to do so again here. + + return new LocusShard(parser,readsDataSource,current.getLocations(),current.fileSpans); + } + + public void remove() { + throw new UnsupportedOperationException("Unable to remove from shard balancing iterator"); + } + }; + } + + /** + * Combine all of the file pointers in the filePointers iterator into a single combined + * FilePointer that spans all of the file pointers on a single contig + * @return a non-null FilePointer + */ + private FilePointer getCombinedFilePointersOnSingleContig() { + FilePointer current = filePointers.next(); + + final List toCombine = new LinkedList<>(); + toCombine.add(current); + + while ( filePointers.hasNext() && + current.isRegionUnmapped == filePointers.peek().isRegionUnmapped && + (current.getContigIndex() == filePointers.peek().getContigIndex() || current.isRegionUnmapped) ) { + toCombine.add(filePointers.next()); + } + + return FilePointer.union(toCombine, parser); + } +} diff --git a/public/java/src/org/broadinstitute/sting/gatk/datasources/reads/FilePointer.java b/public/java/src/org/broadinstitute/sting/gatk/datasources/reads/FilePointer.java index 56bf5197d..517903da3 100644 --- a/public/java/src/org/broadinstitute/sting/gatk/datasources/reads/FilePointer.java +++ b/public/java/src/org/broadinstitute/sting/gatk/datasources/reads/FilePointer.java @@ -407,10 +407,10 @@ public class FilePointer { @Override public String toString() { StringBuilder builder = new StringBuilder(); - builder.append("FilePointer:%n"); + builder.append("FilePointer:\n"); builder.append("\tlocations = {"); builder.append(Utils.join(";",locations)); - builder.append("}%n\tregions = %n"); + builder.append("}\n\tregions = \n"); for(Map.Entry entry: fileSpans.entrySet()) { builder.append(entry.getKey()); builder.append("= {"); diff --git a/public/java/test/org/broadinstitute/sting/gatk/datasources/reads/ActiveRegionShardBalancerUnitTest.java b/public/java/test/org/broadinstitute/sting/gatk/datasources/reads/ActiveRegionShardBalancerUnitTest.java new file mode 100644 index 000000000..e768faba4 --- /dev/null +++ b/public/java/test/org/broadinstitute/sting/gatk/datasources/reads/ActiveRegionShardBalancerUnitTest.java @@ -0,0 +1,101 @@ +/* +* Copyright (c) 2012 The Broad Institute +* +* Permission is hereby granted, free of charge, to any person +* obtaining a copy of this software and associated documentation +* files (the "Software"), to deal in the Software without +* restriction, including without limitation the rights to use, +* copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the +* Software is furnished to do so, subject to the following +* conditions: +* +* The above copyright notice and this permission notice shall be +* included in all copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR +* THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +package org.broadinstitute.sting.gatk.datasources.reads; + +import net.sf.samtools.SAMFileHeader; +import net.sf.samtools.SAMFileSpan; +import net.sf.samtools.SAMSequenceRecord; +import org.broadinstitute.sting.BaseTest; +import org.broadinstitute.sting.utils.GenomeLoc; +import org.broadinstitute.sting.utils.GenomeLocParser; +import org.broadinstitute.sting.utils.sam.ArtificialSAMUtils; +import org.testng.Assert; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.Test; + +import java.io.FileNotFoundException; +import java.util.*; + +public class ActiveRegionShardBalancerUnitTest extends BaseTest { + // example genome loc parser for this test, can be deleted if you don't use the reference + private GenomeLocParser genomeLocParser; + protected SAMDataSource readsDataSource; + + @BeforeClass + public void setup() throws FileNotFoundException { + // sequence + final SAMFileHeader header = ArtificialSAMUtils.createArtificialSamHeader(10, 0, 10000); + genomeLocParser = new GenomeLocParser(header.getSequenceDictionary()); + readsDataSource = null; + } + + @Test + public void testMergingManyContigs() { + executeTest(genomeLocParser.getContigs().getSequences()); + } + + @Test + public void testMergingAllPointersOnSingleContig() { + executeTest(Arrays.asList(genomeLocParser.getContigs().getSequences().get(1))); + } + + @Test + public void testMergingMultipleDiscontinuousContigs() { + final List all = genomeLocParser.getContigs().getSequences(); + executeTest(Arrays.asList(all.get(1), all.get(3))); + } + + private void executeTest(final Collection records) { + final ActiveRegionShardBalancer balancer = new ActiveRegionShardBalancer(); + + final List> expectedLocs = new LinkedList<>(); + final List pointers = new LinkedList<>(); + + for ( final SAMSequenceRecord record : records ) { + final int size = 10; + int end = 0; + for ( int i = 0; i < record.getSequenceLength(); i += size) { + final int myEnd = i + size - 1; + end = myEnd; + final GenomeLoc loc = genomeLocParser.createGenomeLoc(record.getSequenceName(), i, myEnd); + final Map fileSpans = Collections.emptyMap(); + final FilePointer fp = new FilePointer(fileSpans, Collections.singletonList(loc)); + pointers.add(fp); + } + expectedLocs.add(Collections.singleton(genomeLocParser.createGenomeLoc(record.getSequenceName(), 0, end))); + } + + balancer.initialize(readsDataSource, pointers.iterator(), genomeLocParser); + + int i = 0; + int nShardsFound = 0; + for ( final Shard shard : balancer ) { + nShardsFound++; + Assert.assertEquals(new HashSet<>(shard.getGenomeLocs()), expectedLocs.get(i++)); + } + Assert.assertEquals(nShardsFound, records.size(), "Didn't find exactly one shard for each contig in the sequence dictionary"); + } +} diff --git a/public/java/test/org/broadinstitute/sting/gatk/traversals/TraverseActiveRegionsUnitTest.java b/public/java/test/org/broadinstitute/sting/gatk/traversals/TraverseActiveRegionsUnitTest.java index 2e6705d77..1f5cd6d0e 100644 --- a/public/java/test/org/broadinstitute/sting/gatk/traversals/TraverseActiveRegionsUnitTest.java +++ b/public/java/test/org/broadinstitute/sting/gatk/traversals/TraverseActiveRegionsUnitTest.java @@ -490,7 +490,7 @@ public class TraverseActiveRegionsUnitTest extends BaseTest { traverseActiveRegions.initialize(engine, walker); List providers = new ArrayList(); - for (Shard shard : dataSource.createShardIteratorOverIntervals(new GenomeLocSortedSet(genomeLocParser, intervals), new LocusShardBalancer())) { + for (Shard shard : dataSource.createShardIteratorOverIntervals(new GenomeLocSortedSet(genomeLocParser, intervals), new ActiveRegionShardBalancer())) { for (WindowMaker.WindowMakerIterator window : new WindowMaker(shard, genomeLocParser, dataSource.seek(shard), shard.getGenomeLocs(), samples)) { providers.add(new LocusShardDataProvider(shard, shard.getReadProperties(), genomeLocParser, window.getLocus(), window, reference, new ArrayList())); }